blob: 78ca8b84680a55b4b283d2a7da3ba81209766aef [file] [log] [blame]
/*
* 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();
}