| /* |
| * Unit tests for the avi splitter functions |
| * |
| * Copyright (C) 2007 Google (Lei Zhang) |
| * Copyright (C) 2008 Google (Maarten Lankhorst) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #define COBJMACROS |
| |
| #include "wine/test.h" |
| #include "dshow.h" |
| #include "tlhelp32.h" |
| |
| static HANDLE (WINAPI *pCreateToolhelp32Snapshot)(DWORD, DWORD); |
| static BOOL (WINAPI *pThread32First)(HANDLE, LPTHREADENTRY32); |
| static BOOL (WINAPI *pThread32Next)(HANDLE, LPTHREADENTRY32); |
| |
| static IUnknown *pAviSplitter = NULL; |
| |
| static int count_threads(void) |
| { |
| THREADENTRY32 te; |
| int threads; |
| HANDLE h; |
| |
| h = pCreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); |
| te.dwSize = sizeof(te); |
| |
| if (h == INVALID_HANDLE_VALUE) |
| return -1; |
| |
| pThread32First(h, &te); |
| if (te.th32OwnerProcessID == GetCurrentProcessId()) |
| threads = 1; |
| else |
| threads = 0; |
| |
| while (pThread32Next(h, &te)) |
| if (te.th32OwnerProcessID == GetCurrentProcessId()) |
| ++threads; |
| |
| CloseHandle(h); |
| return threads; |
| } |
| |
| static BOOL create_avisplitter(void) |
| { |
| HRESULT hr; |
| |
| hr = CoCreateInstance(&CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IUnknown, (LPVOID*)&pAviSplitter); |
| return (hr == S_OK && pAviSplitter != NULL); |
| } |
| |
| static void release_avisplitter(void) |
| { |
| HRESULT hr; |
| |
| Sleep(1000); |
| hr = IUnknown_Release(pAviSplitter); |
| |
| /* Looks like wine has a reference leak somewhere on test_threads tests, |
| * it passes in windows |
| */ |
| ok(hr == 0, "IUnknown_Release failed with %d\n", (INT)hr); |
| |
| while (hr > 0) |
| hr = IUnknown_Release(pAviSplitter); |
| pAviSplitter = NULL; |
| } |
| |
| static void test_query_interface(void) |
| { |
| HRESULT hr; |
| ULONG ref; |
| IUnknown *iface= NULL; |
| |
| #define TEST_INTERFACE(riid,expected) do { \ |
| hr = IUnknown_QueryInterface(pAviSplitter, &riid, (void**)&iface); \ |
| ok( hr == expected, #riid" should %s got %08X\n", expected==S_OK ? "exist" : "not be present", GetLastError() ); \ |
| if (hr == S_OK) { \ |
| ref = IUnknown_Release(iface); \ |
| ok(ref == 1, "Reference is %u, expected 1\n", ref); \ |
| } \ |
| iface = NULL; \ |
| } while(0) |
| |
| TEST_INTERFACE(IID_IBaseFilter,S_OK); |
| TEST_INTERFACE(IID_IMediaSeeking,E_NOINTERFACE); |
| TEST_INTERFACE(IID_IKsPropertySet,E_NOINTERFACE); |
| TEST_INTERFACE(IID_IMediaPosition,E_NOINTERFACE); |
| TEST_INTERFACE(IID_IQualityControl,E_NOINTERFACE); |
| TEST_INTERFACE(IID_IQualProp,E_NOINTERFACE); |
| #undef TEST_INTERFACE |
| } |
| |
| static void test_pin(IPin *pin) |
| { |
| IMemInputPin *mpin = NULL; |
| |
| IPin_QueryInterface(pin, &IID_IMemInputPin, (void **)&mpin); |
| |
| ok(mpin == NULL, "IMemInputPin found!\n"); |
| if (mpin) |
| IMemInputPin_Release(mpin); |
| /* TODO */ |
| } |
| |
| static void test_basefilter(void) |
| { |
| IEnumPins *pin_enum = NULL; |
| IBaseFilter *base = NULL; |
| IPin *pins[2]; |
| ULONG ref; |
| HRESULT hr; |
| |
| IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, (void **)&base); |
| if (base == NULL) |
| { |
| /* test_query_interface handles this case */ |
| skip("No IBaseFilter\n"); |
| return; |
| } |
| |
| hr = IBaseFilter_EnumPins(base, NULL); |
| ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr); |
| |
| hr= IBaseFilter_EnumPins(base, &pin_enum); |
| ok(hr == S_OK, "hr = %08x and not S_OK\n", hr); |
| |
| hr = IEnumPins_Next(pin_enum, 1, NULL, NULL); |
| ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr); |
| |
| hr = IEnumPins_Next(pin_enum, 2, pins, NULL); |
| ok(hr == E_INVALIDARG, "hr = %08x and not E_INVALIDARG\n", hr); |
| |
| pins[0] = (void *)0xdead; |
| pins[1] = (void *)0xdeed; |
| |
| hr = IEnumPins_Next(pin_enum, 2, pins, &ref); |
| ok(hr == S_FALSE, "hr = %08x instead of S_FALSE\n", hr); |
| ok(pins[0] != (void *)0xdead && pins[0] != NULL, |
| "pins[0] = %p\n", pins[0]); |
| if (pins[0] != (void *)0xdead && pins[0] != NULL) |
| { |
| test_pin(pins[0]); |
| IPin_Release(pins[0]); |
| } |
| |
| ok(pins[1] == (void *)0xdeed, "pins[1] = %p\n", pins[1]); |
| |
| ref = IEnumPins_Release(pin_enum); |
| ok(ref == 0, "ref is %u and not 0!\n", ref); |
| |
| IBaseFilter_Release(base); |
| } |
| |
| static void test_filesourcefilter(void) |
| { |
| static const WCHAR prefix[] = {'w','i','n',0}; |
| static const struct |
| { |
| const char *label; |
| const char *data; |
| DWORD size; |
| const GUID *subtype; |
| } |
| tests[] = |
| { |
| { |
| "AVI", |
| "\x52\x49\x46\x46xxxx\x41\x56\x49\x20", |
| 12, |
| &MEDIASUBTYPE_Avi, |
| }, |
| { |
| "MPEG1 System", |
| "\x00\x00\x01\xBA\x21\x00\x01\x00\x01\x80\x00\x01\x00\x00\x01\xBB", |
| 16, |
| &MEDIASUBTYPE_MPEG1System, |
| }, |
| { |
| "MPEG1 Video", |
| "\x00\x00\x01\xB3", |
| 4, |
| &MEDIASUBTYPE_MPEG1Video, |
| }, |
| { |
| "MPEG1 Audio", |
| "\xFF\xE0", |
| 2, |
| &MEDIASUBTYPE_MPEG1Audio, |
| }, |
| { |
| "MPEG2 Program", |
| "\x00\x00\x01\xBA\x40", |
| 5, |
| &MEDIASUBTYPE_MPEG2_PROGRAM, |
| }, |
| { |
| "WAVE", |
| "\x52\x49\x46\x46xxxx\x57\x41\x56\x45", |
| 12, |
| &MEDIASUBTYPE_WAVE, |
| }, |
| { |
| "unknown format", |
| "Hello World", |
| 11, |
| NULL, /* FIXME: should be &MEDIASUBTYPE_NULL */ |
| }, |
| }; |
| WCHAR path[MAX_PATH], temp[MAX_PATH]; |
| IFileSourceFilter *filesource; |
| DWORD ret, written; |
| IBaseFilter *base; |
| AM_MEDIA_TYPE mt; |
| OLECHAR *olepath; |
| BOOL success; |
| HANDLE file; |
| HRESULT hr; |
| int i; |
| |
| ret = GetTempPathW(MAX_PATH, temp); |
| ok(ret, "GetTempPathW failed with error %u\n", GetLastError()); |
| ret = GetTempFileNameW(temp, prefix, 0, path); |
| ok(ret, "GetTempFileNameW failed with error %u\n", GetLastError()); |
| |
| for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) |
| { |
| trace("Running test for %s\n", tests[i].label); |
| |
| file = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL, |
| CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); |
| ok(file != INVALID_HANDLE_VALUE, "CreateFileW failed with error %u\n", GetLastError()); |
| success = WriteFile(file, tests[i].data, tests[i].size, &written, NULL); |
| ok(success, "WriteFile failed with error %u\n", GetLastError()); |
| ok(written == tests[i].size, "could not write test data\n"); |
| CloseHandle(file); |
| |
| hr = CoCreateInstance(&CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IBaseFilter, (void **)&base); |
| ok(hr == S_OK, "CoCreateInstance failed with %08x\n", hr); |
| hr = IBaseFilter_QueryInterface(base, &IID_IFileSourceFilter, (void **)&filesource); |
| ok(hr == S_OK, "IBaseFilter_QueryInterface failed with %08x\n", hr); |
| |
| olepath = (void *)0xdeadbeef; |
| hr = IFileSourceFilter_GetCurFile(filesource, &olepath, NULL); |
| ok(hr == S_OK, "expected S_OK, got %08x\n", hr); |
| ok(olepath == NULL, "expected NULL, got %p\n", olepath); |
| |
| hr = IFileSourceFilter_Load(filesource, NULL, NULL); |
| ok(hr == E_POINTER, "expected E_POINTER, got %08x\n", hr); |
| |
| hr = IFileSourceFilter_Load(filesource, path, NULL); |
| ok(hr == S_OK, "IFileSourceFilter_Load failed with %08x\n", hr); |
| |
| hr = IFileSourceFilter_GetCurFile(filesource, NULL, &mt); |
| ok(hr == E_POINTER, "expected E_POINTER, got %08x\n", hr); |
| |
| olepath = NULL; |
| hr = IFileSourceFilter_GetCurFile(filesource, &olepath, NULL); |
| ok(hr == S_OK, "expected S_OK, got %08x\n", hr); |
| CoTaskMemFree(olepath); |
| |
| olepath = NULL; |
| memset(&mt, 0x11, sizeof(mt)); |
| hr = IFileSourceFilter_GetCurFile(filesource, &olepath, &mt); |
| ok(hr == S_OK, "expected S_OK, got %08x\n", hr); |
| ok(!lstrcmpW(olepath, path), |
| "expected %s, got %s\n", wine_dbgstr_w(path), wine_dbgstr_w(olepath)); |
| if (tests[i].subtype) |
| { |
| ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Stream), |
| "expected MEDIATYPE_Stream, got %s\n", wine_dbgstr_guid(&mt.majortype)); |
| ok(IsEqualGUID(&mt.subtype, tests[i].subtype), |
| "expected %s, got %s\n", wine_dbgstr_guid(tests[i].subtype), wine_dbgstr_guid(&mt.subtype)); |
| } |
| CoTaskMemFree(olepath); |
| |
| IFileSourceFilter_Release(filesource); |
| IBaseFilter_Release(base); |
| |
| success = DeleteFileW(path); |
| ok(success, "DeleteFileW failed with error %u\n", GetLastError()); |
| } |
| } |
| |
| static const WCHAR wfile[] = {'t','e','s','t','.','a','v','i',0}; |
| static const char afile[] = "test.avi"; |
| |
| /* This test doesn't use the quartz filtergraph because it makes it impossible |
| * to be certain that a thread is really one owned by the avi splitter. |
| * A lot of the decoder filters will also have their own thread, and Windows' |
| * filtergraph has a separate thread for start/stop/seeking requests. |
| * By avoiding the filtergraph altogether and connecting streams directly to |
| * the null renderer I am sure that this is not the case here. |
| */ |
| static void test_threads(void) |
| { |
| IFileSourceFilter *pfile = NULL; |
| IBaseFilter *preader = NULL, *pavi = NULL; |
| IEnumPins *enumpins = NULL; |
| IPin *filepin = NULL, *avipin = NULL; |
| HRESULT hr; |
| int baselevel, curlevel, expected; |
| HANDLE file = NULL; |
| PIN_DIRECTION dir = PINDIR_OUTPUT; |
| char buffer[13]; |
| DWORD readbytes; |
| FILTER_STATE state; |
| |
| /* We need another way of counting threads on NT4. Skip these tests (for now) */ |
| if (!pCreateToolhelp32Snapshot || !pThread32First || !pThread32Next) |
| { |
| win_skip("Needed thread functions are not available (NT4)\n"); |
| return; |
| } |
| |
| /* Before doing anything (the thread count at the start differs per OS) */ |
| baselevel = count_threads(); |
| |
| file = CreateFileW(wfile, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, |
| NULL, OPEN_EXISTING, 0, NULL); |
| if (file == INVALID_HANDLE_VALUE) |
| { |
| skip("Could not read test file \"%s\", skipping test\n", afile); |
| return; |
| } |
| |
| memset(buffer, 0, 13); |
| readbytes = 12; |
| ReadFile(file, buffer, readbytes, &readbytes, NULL); |
| CloseHandle(file); |
| if (strncmp(buffer, "RIFF", 4) || strcmp(buffer + 8, "AVI ")) |
| { |
| skip("%s is not an avi riff file, not doing the avi splitter test\n", |
| afile); |
| return; |
| } |
| |
| hr = IUnknown_QueryInterface(pAviSplitter, &IID_IFileSourceFilter, |
| (void **)&pfile); |
| ok(hr == E_NOINTERFACE, |
| "Avi splitter returns unexpected error: %08x\n", hr); |
| if (pfile) |
| IFileSourceFilter_Release(pfile); |
| pfile = NULL; |
| |
| hr = CoCreateInstance(&CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, |
| &IID_IBaseFilter, (LPVOID*)&preader); |
| ok(hr == S_OK, "Could not create asynchronous reader: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| hr = IBaseFilter_QueryInterface(preader, &IID_IFileSourceFilter, |
| (void**)&pfile); |
| ok(hr == S_OK, "Could not get IFileSourceFilter: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| hr = IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, |
| (void**)&pavi); |
| ok(hr == S_OK, "Could not get base filter: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| hr = IFileSourceFilter_Load(pfile, wfile, NULL); |
| if (hr != S_OK) |
| { |
| trace("Could not load file\n"); |
| goto fail; |
| } |
| |
| hr = IBaseFilter_EnumPins(preader, &enumpins); |
| ok(hr == S_OK, "No enumpins: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| hr = IEnumPins_Next(enumpins, 1, &filepin, NULL); |
| ok(hr == S_OK, "No pin: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| IEnumPins_Release(enumpins); |
| enumpins = NULL; |
| |
| hr = IBaseFilter_EnumPins(pavi, &enumpins); |
| ok(hr == S_OK, "No enumpins: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| hr = IEnumPins_Next(enumpins, 1, &avipin, NULL); |
| ok(hr == S_OK, "No pin: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| curlevel = count_threads(); |
| ok(curlevel == baselevel, |
| "The thread count should be %d not %d\n", baselevel, curlevel); |
| |
| hr = IPin_Connect(filepin, avipin, NULL); |
| ok(hr == S_OK, "Could not connect: %08x\n", hr); |
| if (hr != S_OK) |
| goto fail; |
| |
| expected = 1 + baselevel; |
| curlevel = count_threads(); |
| ok(curlevel == expected, |
| "The thread count should be %d not %d\n", expected, curlevel); |
| |
| IPin_Release(avipin); |
| avipin = NULL; |
| |
| IEnumPins_Reset(enumpins); |
| |
| /* Windows puts the pins in the order: Outputpins - Inputpin, |
| * wine does the reverse, just don't test it for now |
| * Hate to admit it, but windows way makes more sense |
| */ |
| while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK) |
| { |
| IPin_QueryDirection(avipin, &dir); |
| if (dir == PINDIR_OUTPUT) |
| { |
| /* Well, connect it to a null renderer! */ |
| IBaseFilter *pnull = NULL; |
| IEnumPins *nullenum = NULL; |
| IPin *nullpin = NULL; |
| |
| hr = CoCreateInstance(&CLSID_NullRenderer, NULL, |
| CLSCTX_INPROC_SERVER, &IID_IBaseFilter, (LPVOID*)&pnull); |
| ok(hr == S_OK, "Could not create null renderer: %08x\n", hr); |
| if (hr != S_OK) |
| break; |
| |
| IBaseFilter_EnumPins(pnull, &nullenum); |
| IEnumPins_Next(nullenum, 1, &nullpin, NULL); |
| IEnumPins_Release(nullenum); |
| IPin_QueryDirection(nullpin, &dir); |
| |
| hr = IPin_Connect(avipin, nullpin, NULL); |
| ok(hr == S_OK, "Failed to connect output pin: %08x\n", hr); |
| IPin_Release(nullpin); |
| if (hr != S_OK) |
| { |
| IBaseFilter_Release(pnull); |
| break; |
| } |
| IBaseFilter_Run(pnull, 0); |
| ++expected; |
| } |
| |
| IPin_Release(avipin); |
| avipin = NULL; |
| } |
| |
| if (avipin) |
| IPin_Release(avipin); |
| avipin = NULL; |
| |
| if (hr != S_OK) |
| goto fail2; |
| /* At this point there is a minimalistic connected avi splitter that can |
| * be used for all sorts of source filter tests. However that still needs |
| * to be written at a later time. |
| * |
| * Interesting tests: |
| * - Can you disconnect an output pin while running? |
| * Expecting: Yes |
| * - Can you disconnect the pullpin while running? |
| * Expecting: No |
| * - Is the reference count incremented during playback or when connected? |
| * Does this happen once for every output pin? Or is there something else |
| * going on. |
| * Expecting: You tell me |
| */ |
| |
| IBaseFilter_Run(preader, 0); |
| IBaseFilter_Run(pavi, 0); |
| IBaseFilter_GetState(pavi, INFINITE, &state); |
| |
| curlevel = count_threads(); |
| ok(curlevel == expected, |
| "The thread count should be %d not %d\n", expected, curlevel); |
| |
| IBaseFilter_Pause(pavi); |
| IBaseFilter_Pause(preader); |
| IBaseFilter_Stop(pavi); |
| IBaseFilter_Stop(preader); |
| IBaseFilter_GetState(pavi, INFINITE, &state); |
| IBaseFilter_GetState(preader, INFINITE, &state); |
| |
| fail2: |
| IEnumPins_Reset(enumpins); |
| while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK) |
| { |
| IPin *to = NULL; |
| |
| IPin_QueryDirection(avipin, &dir); |
| IPin_ConnectedTo(avipin, &to); |
| if (to) |
| { |
| IPin_Release(to); |
| |
| if (dir == PINDIR_OUTPUT) |
| { |
| PIN_INFO info; |
| IPin_QueryPinInfo(to, &info); |
| |
| /* Release twice: Once normal, second from the |
| * previous while loop |
| */ |
| IBaseFilter_Stop(info.pFilter); |
| IPin_Disconnect(to); |
| IPin_Disconnect(avipin); |
| IBaseFilter_Release(info.pFilter); |
| IBaseFilter_Release(info.pFilter); |
| } |
| else |
| { |
| IPin_Disconnect(to); |
| IPin_Disconnect(avipin); |
| } |
| } |
| IPin_Release(avipin); |
| avipin = NULL; |
| } |
| |
| fail: |
| if (hr != S_OK) |
| skip("Prerequisites not matched, skipping remainder of test\n"); |
| if (enumpins) |
| IEnumPins_Release(enumpins); |
| |
| if (avipin) |
| IPin_Release(avipin); |
| if (filepin) |
| { |
| IPin *to = NULL; |
| |
| IPin_ConnectedTo(filepin, &to); |
| if (to) |
| { |
| IPin_Disconnect(filepin); |
| IPin_Disconnect(to); |
| } |
| IPin_Release(filepin); |
| } |
| |
| if (preader) |
| IBaseFilter_Release(preader); |
| if (pavi) |
| IBaseFilter_Release(pavi); |
| if (pfile) |
| IFileSourceFilter_Release(pfile); |
| |
| curlevel = count_threads(); |
| todo_wine |
| ok(curlevel == baselevel, |
| "The thread count should be %d not %d\n", baselevel, curlevel); |
| } |
| |
| START_TEST(avisplitter) |
| { |
| HMODULE kernel32 = GetModuleHandleA("kernel32.dll"); |
| |
| pCreateToolhelp32Snapshot = (void*)GetProcAddress(kernel32, "CreateToolhelp32Snapshot"); |
| pThread32First = (void*)GetProcAddress(kernel32, "Thread32First"); |
| pThread32Next = (void*)GetProcAddress(kernel32, "Thread32Next"); |
| |
| CoInitialize(NULL); |
| |
| if (!create_avisplitter()) |
| { |
| skip("Could not create avisplitter\n"); |
| return; |
| } |
| |
| test_query_interface(); |
| test_basefilter(); |
| test_filesourcefilter(); |
| test_threads(); |
| |
| release_avisplitter(); |
| |
| CoUninitialize(); |
| } |