1 /*
2  * Unit tests for the avi splitter functions
3  *
4  * Copyright (C) 2007 Google (Lei Zhang)
5  * Copyright (C) 2008 Google (Maarten Lankhorst)
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #define COBJMACROS
23 
24 #include "wine/test.h"
25 #include "dshow.h"
26 #include "tlhelp32.h"
27 
28 static IUnknown *pAviSplitter = NULL;
29 
30 static BOOL create_avisplitter(void)
31 {
32     HRESULT hr;
33 
34     hr = CoCreateInstance(&CLSID_AviSplitter, NULL, CLSCTX_INPROC_SERVER,
35                           &IID_IUnknown, (LPVOID*)&pAviSplitter);
36     return (hr == S_OK && pAviSplitter != NULL);
37 }
38 
39 static void release_avisplitter(void)
40 {
41     HRESULT hr;
42 
43     Sleep(1000);
44     hr = IUnknown_Release(pAviSplitter);
45 
46     /* Looks like wine has a reference leak somewhere on test_threads tests,
47      * it passes in windows
48      */
49     ok(hr == 0, "IUnknown_Release failed with %d\n", (INT)hr);
50 
51     while (hr > 0)
52         hr = IUnknown_Release(pAviSplitter);
53     pAviSplitter = NULL;
54 }
55 
56 static void test_query_interface(void)
57 {
58     HRESULT hr;
59     ULONG ref;
60     IUnknown *iface= NULL;
61 
62 #define TEST_INTERFACE(riid,expected) do { \
63     hr = IUnknown_QueryInterface(pAviSplitter, &riid, (void**)&iface); \
64     ok( hr == expected, #riid" should %s got %08X\n", expected==S_OK ? "exist" : "not be present", GetLastError() ); \
65     if (hr == S_OK) { \
66         ref = IUnknown_Release(iface); \
67         ok(ref == 1, "Reference is %u, expected 1\n", ref); \
68     } \
69     iface = NULL; \
70     } while(0)
71 
72     TEST_INTERFACE(IID_IBaseFilter,S_OK);
73     TEST_INTERFACE(IID_IMediaSeeking,E_NOINTERFACE);
74     TEST_INTERFACE(IID_IKsPropertySet,E_NOINTERFACE);
75     TEST_INTERFACE(IID_IMediaPosition,E_NOINTERFACE);
76     TEST_INTERFACE(IID_IQualityControl,E_NOINTERFACE);
77     TEST_INTERFACE(IID_IQualProp,E_NOINTERFACE);
78 #undef TEST_INTERFACE
79 }
80 
81 static void test_pin(IPin *pin)
82 {
83     IMemInputPin *mpin = NULL;
84 
85     IPin_QueryInterface(pin, &IID_IMemInputPin, (void **)&mpin);
86 
87     ok(mpin == NULL, "IMemInputPin found!\n");
88     if (mpin)
89         IMemInputPin_Release(mpin);
90     /* TODO */
91 }
92 
93 static void test_basefilter(void)
94 {
95     IEnumPins *pin_enum = NULL;
96     IBaseFilter *base = NULL;
97     IPin *pins[2];
98     ULONG ref;
99     HRESULT hr;
100 
101     IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter, (void **)&base);
102     if (base == NULL)
103     {
104         /* test_query_interface handles this case */
105         skip("No IBaseFilter\n");
106         return;
107     }
108 
109     hr = IBaseFilter_EnumPins(base, NULL);
110     ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr);
111 
112     hr= IBaseFilter_EnumPins(base, &pin_enum);
113     ok(hr == S_OK, "hr = %08x and not S_OK\n", hr);
114 
115     hr = IEnumPins_Next(pin_enum, 1, NULL, NULL);
116     ok(hr == E_POINTER, "hr = %08x and not E_POINTER\n", hr);
117 
118     hr = IEnumPins_Next(pin_enum, 2, pins, NULL);
119     ok(hr == E_INVALIDARG, "hr = %08x and not E_INVALIDARG\n", hr);
120 
121     pins[0] = (void *)0xdead;
122     pins[1] = (void *)0xdeed;
123 
124     hr = IEnumPins_Next(pin_enum, 2, pins, &ref);
125     ok(hr == S_FALSE, "hr = %08x instead of S_FALSE\n", hr);
126     ok(pins[0] != (void *)0xdead && pins[0] != NULL,
127         "pins[0] = %p\n", pins[0]);
128     if (pins[0] != (void *)0xdead && pins[0] != NULL)
129     {
130         test_pin(pins[0]);
131         IPin_Release(pins[0]);
132     }
133 
134     ok(pins[1] == (void *)0xdeed, "pins[1] = %p\n", pins[1]);
135 
136     ref = IEnumPins_Release(pin_enum);
137     ok(ref == 0, "ref is %u and not 0!\n", ref);
138 
139     IBaseFilter_Release(base);
140 }
141 
142 static void test_filesourcefilter(void)
143 {
144     static const WCHAR prefix[] = {'w','i','n',0};
145     static const struct
146     {
147         const char *label;
148         const char *data;
149         DWORD size;
150         const GUID *subtype;
151     }
152     tests[] =
153     {
154         {
155             "AVI",
156             "\x52\x49\x46\x46xxxx\x41\x56\x49\x20",
157             12,
158             &MEDIASUBTYPE_Avi,
159         },
160         {
161             "MPEG1 System",
162             "\x00\x00\x01\xBA\x21\x00\x01\x00\x01\x80\x00\x01\x00\x00\x01\xBB",
163             16,
164             &MEDIASUBTYPE_MPEG1System,
165         },
166         {
167             "MPEG1 Video",
168             "\x00\x00\x01\xB3",
169             4,
170             &MEDIASUBTYPE_MPEG1Video,
171         },
172         {
173             "MPEG1 Audio",
174             "\xFF\xE0",
175             2,
176             &MEDIASUBTYPE_MPEG1Audio,
177         },
178         {
179             "MPEG2 Program",
180             "\x00\x00\x01\xBA\x40",
181             5,
182             &MEDIASUBTYPE_MPEG2_PROGRAM,
183         },
184         {
185             "WAVE",
186             "\x52\x49\x46\x46xxxx\x57\x41\x56\x45",
187             12,
188             &MEDIASUBTYPE_WAVE,
189         },
190         {
191             "unknown format",
192             "Hello World",
193             11,
194             NULL, /* FIXME: should be &MEDIASUBTYPE_NULL */
195         },
196     };
197     WCHAR path[MAX_PATH], temp[MAX_PATH];
198     IFileSourceFilter *filesource;
199     DWORD ret, written;
200     IBaseFilter *base;
201     AM_MEDIA_TYPE mt;
202     OLECHAR *olepath;
203     BOOL success;
204     HANDLE file;
205     HRESULT hr;
206     int i;
207 
208     ret = GetTempPathW(MAX_PATH, temp);
209     ok(ret, "GetTempPathW failed with error %u\n", GetLastError());
210     ret = GetTempFileNameW(temp, prefix, 0, path);
211     ok(ret, "GetTempFileNameW failed with error %u\n", GetLastError());
212 
213     for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
214     {
215         trace("Running test for %s\n", tests[i].label);
216 
217         file = CreateFileW(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
218                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
219         ok(file != INVALID_HANDLE_VALUE, "CreateFileW failed with error %u\n", GetLastError());
220         success = WriteFile(file, tests[i].data, tests[i].size, &written, NULL);
221         ok(success, "WriteFile failed with error %u\n", GetLastError());
222         ok(written == tests[i].size, "could not write test data\n");
223         CloseHandle(file);
224 
225         hr = CoCreateInstance(&CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER,
226                               &IID_IBaseFilter, (void **)&base);
227         ok(hr == S_OK, "CoCreateInstance failed with %08x\n", hr);
228         hr = IBaseFilter_QueryInterface(base, &IID_IFileSourceFilter, (void **)&filesource);
229         ok(hr == S_OK, "IBaseFilter_QueryInterface failed with %08x\n", hr);
230 
231         olepath = (void *)0xdeadbeef;
232         hr = IFileSourceFilter_GetCurFile(filesource, &olepath, NULL);
233         ok(hr == S_OK, "expected S_OK, got %08x\n", hr);
234         ok(olepath == NULL, "expected NULL, got %p\n", olepath);
235 
236         hr = IFileSourceFilter_Load(filesource, NULL, NULL);
237         ok(hr == E_POINTER, "expected E_POINTER, got %08x\n", hr);
238 
239         hr = IFileSourceFilter_Load(filesource, path, NULL);
240         ok(hr == S_OK, "IFileSourceFilter_Load failed with %08x\n", hr);
241 
242         hr = IFileSourceFilter_GetCurFile(filesource, NULL, &mt);
243         ok(hr == E_POINTER, "expected E_POINTER, got %08x\n", hr);
244 
245         olepath = NULL;
246         hr = IFileSourceFilter_GetCurFile(filesource, &olepath, NULL);
247         ok(hr == S_OK, "expected S_OK, got %08x\n", hr);
248         CoTaskMemFree(olepath);
249 
250         olepath = NULL;
251         memset(&mt, 0x11, sizeof(mt));
252         hr = IFileSourceFilter_GetCurFile(filesource, &olepath, &mt);
253         ok(hr == S_OK, "expected S_OK, got %08x\n", hr);
254         ok(!lstrcmpW(olepath, path),
255            "expected %s, got %s\n", wine_dbgstr_w(path), wine_dbgstr_w(olepath));
256         if (tests[i].subtype)
257         {
258             ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Stream),
259                "expected MEDIATYPE_Stream, got %s\n", wine_dbgstr_guid(&mt.majortype));
260             ok(IsEqualGUID(&mt.subtype, tests[i].subtype),
261                "expected %s, got %s\n", wine_dbgstr_guid(tests[i].subtype), wine_dbgstr_guid(&mt.subtype));
262         }
263         CoTaskMemFree(olepath);
264 
265         IFileSourceFilter_Release(filesource);
266         IBaseFilter_Release(base);
267 
268         success = DeleteFileW(path);
269         ok(success, "DeleteFileW failed with error %u\n", GetLastError());
270     }
271 }
272 
273 static const WCHAR avifile[] = {'t','e','s','t','.','a','v','i',0};
274 
275 static WCHAR *load_resource(const WCHAR *name)
276 {
277     static WCHAR pathW[MAX_PATH];
278     DWORD written;
279     HANDLE file;
280     HRSRC res;
281     void *ptr;
282 
283     GetTempPathW(sizeof(pathW)/sizeof(WCHAR), pathW);
284     lstrcatW(pathW, name);
285 
286     file = CreateFileW(pathW, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
287     ok(file != INVALID_HANDLE_VALUE, "file creation failed, at %s, error %d\n", wine_dbgstr_w(pathW),
288         GetLastError());
289 
290     res = FindResourceW(NULL, name, (LPCWSTR)RT_RCDATA);
291     ok( res != 0, "couldn't find resource\n" );
292     ptr = LockResource( LoadResource( GetModuleHandleA(NULL), res ));
293     WriteFile( file, ptr, SizeofResource( GetModuleHandleA(NULL), res ), &written, NULL );
294     ok( written == SizeofResource( GetModuleHandleA(NULL), res ), "couldn't write resource\n" );
295     CloseHandle( file );
296 
297     return pathW;
298 }
299 
300 static void test_filter_graph(void)
301 {
302     IFileSourceFilter *pfile = NULL;
303     IBaseFilter *preader = NULL, *pavi = NULL;
304     IEnumPins *enumpins = NULL;
305     IPin *filepin = NULL, *avipin = NULL;
306     HRESULT hr;
307     HANDLE file = NULL;
308     PIN_DIRECTION dir = PINDIR_OUTPUT;
309     char buffer[13];
310     DWORD readbytes;
311     FILTER_STATE state;
312 
313     WCHAR *filename = load_resource(avifile);
314 
315     file = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE,
316         NULL, OPEN_EXISTING, 0, NULL);
317     if (file == INVALID_HANDLE_VALUE)
318     {
319         skip("Could not read test file \"%s\", skipping test\n", wine_dbgstr_w(filename));
320         DeleteFileW(filename);
321         return;
322     }
323 
324     memset(buffer, 0, 13);
325     readbytes = 12;
326     ReadFile(file, buffer, readbytes, &readbytes, NULL);
327     CloseHandle(file);
328     if (strncmp(buffer, "RIFF", 4) || strcmp(buffer + 8, "AVI "))
329     {
330         skip("%s is not an avi riff file, not doing the avi splitter test\n",
331             wine_dbgstr_w(filename));
332         DeleteFileW(filename);
333         return;
334     }
335 
336     hr = IUnknown_QueryInterface(pAviSplitter, &IID_IFileSourceFilter,
337         (void **)&pfile);
338     ok(hr == E_NOINTERFACE,
339         "Avi splitter returns unexpected error: %08x\n", hr);
340     if (pfile)
341         IFileSourceFilter_Release(pfile);
342     pfile = NULL;
343 
344     hr = CoCreateInstance(&CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER,
345         &IID_IBaseFilter, (LPVOID*)&preader);
346     ok(hr == S_OK, "Could not create asynchronous reader: %08x\n", hr);
347     if (hr != S_OK)
348         goto fail;
349 
350     hr = IBaseFilter_QueryInterface(preader, &IID_IFileSourceFilter,
351         (void**)&pfile);
352     ok(hr == S_OK, "Could not get IFileSourceFilter: %08x\n", hr);
353     if (hr != S_OK)
354         goto fail;
355 
356     hr = IUnknown_QueryInterface(pAviSplitter, &IID_IBaseFilter,
357         (void**)&pavi);
358     ok(hr == S_OK, "Could not get base filter: %08x\n", hr);
359     if (hr != S_OK)
360         goto fail;
361 
362     hr = IFileSourceFilter_Load(pfile, filename, NULL);
363     if (hr != S_OK)
364     {
365         trace("Could not load file: %08x\n", hr);
366         goto fail;
367     }
368 
369     hr = IBaseFilter_EnumPins(preader, &enumpins);
370     ok(hr == S_OK, "No enumpins: %08x\n", hr);
371     if (hr != S_OK)
372         goto fail;
373 
374     hr = IEnumPins_Next(enumpins, 1, &filepin, NULL);
375     ok(hr == S_OK, "No pin: %08x\n", hr);
376     if (hr != S_OK)
377         goto fail;
378 
379     IEnumPins_Release(enumpins);
380     enumpins = NULL;
381 
382     hr = IBaseFilter_EnumPins(pavi, &enumpins);
383     ok(hr == S_OK, "No enumpins: %08x\n", hr);
384     if (hr != S_OK)
385         goto fail;
386 
387     hr = IEnumPins_Next(enumpins, 1, &avipin, NULL);
388     ok(hr == S_OK, "No pin: %08x\n", hr);
389     if (hr != S_OK)
390         goto fail;
391 
392     hr = IPin_Connect(filepin, avipin, NULL);
393     ok(hr == S_OK, "Could not connect: %08x\n", hr);
394     if (hr != S_OK)
395         goto fail;
396 
397     IPin_Release(avipin);
398     avipin = NULL;
399 
400     IEnumPins_Reset(enumpins);
401 
402     /* Windows puts the pins in the order: Outputpins - Inputpin,
403      * wine does the reverse, just don't test it for now
404      * Hate to admit it, but windows way makes more sense
405      */
406     while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK)
407     {
408         IPin_QueryDirection(avipin, &dir);
409         if (dir == PINDIR_OUTPUT)
410         {
411             /* Well, connect it to a null renderer! */
412             IBaseFilter *pnull = NULL;
413             IEnumPins *nullenum = NULL;
414             IPin *nullpin = NULL;
415 
416             hr = CoCreateInstance(&CLSID_NullRenderer, NULL,
417                 CLSCTX_INPROC_SERVER, &IID_IBaseFilter, (LPVOID*)&pnull);
418             if (hr == REGDB_E_CLASSNOTREG)
419             {
420                 win_skip("Null renderer not registered, skipping\n");
421                 break;
422             }
423             ok(hr == S_OK, "Could not create null renderer: %08x\n", hr);
424 
425             hr = IBaseFilter_EnumPins(pnull, &nullenum);
426             ok(hr == S_OK, "Failed to enum pins, hr %#x.\n", hr);
427             hr = IEnumPins_Next(nullenum, 1, &nullpin, NULL);
428             ok(hr == S_OK, "Failed to get next pin, hr %#x.\n", hr);
429             IEnumPins_Release(nullenum);
430             IPin_QueryDirection(nullpin, &dir);
431 
432             hr = IPin_Connect(avipin, nullpin, NULL);
433             ok(hr == S_OK, "Failed to connect output pin: %08x\n", hr);
434             IPin_Release(nullpin);
435             if (hr != S_OK)
436             {
437                 IBaseFilter_Release(pnull);
438                 break;
439             }
440             IBaseFilter_Run(pnull, 0);
441         }
442 
443         IPin_Release(avipin);
444         avipin = NULL;
445     }
446 
447     if (avipin)
448         IPin_Release(avipin);
449     avipin = NULL;
450 
451     if (hr != S_OK)
452         goto fail2;
453     /* At this point there is a minimalistic connected avi splitter that can
454      * be used for all sorts of source filter tests. However that still needs
455      * to be written at a later time.
456      *
457      * Interesting tests:
458      * - Can you disconnect an output pin while running?
459      *   Expecting: Yes
460      * - Can you disconnect the pullpin while running?
461      *   Expecting: No
462      * - Is the reference count incremented during playback or when connected?
463      *   Does this happen once for every output pin? Or is there something else
464      *   going on.
465      *   Expecting: You tell me
466      */
467 
468     IBaseFilter_Run(preader, 0);
469     IBaseFilter_Run(pavi, 0);
470     IBaseFilter_GetState(pavi, INFINITE, &state);
471 
472     IBaseFilter_Pause(pavi);
473     IBaseFilter_Pause(preader);
474     IBaseFilter_Stop(pavi);
475     IBaseFilter_Stop(preader);
476     IBaseFilter_GetState(pavi, INFINITE, &state);
477     IBaseFilter_GetState(preader, INFINITE, &state);
478 
479 fail2:
480     IEnumPins_Reset(enumpins);
481     while (IEnumPins_Next(enumpins, 1, &avipin, NULL) == S_OK)
482     {
483         IPin *to = NULL;
484 
485         IPin_QueryDirection(avipin, &dir);
486         IPin_ConnectedTo(avipin, &to);
487         if (to)
488         {
489             IPin_Release(to);
490 
491             if (dir == PINDIR_OUTPUT)
492             {
493                 PIN_INFO info;
494 
495                 hr = IPin_QueryPinInfo(to, &info);
496                 ok(hr == S_OK, "Failed to query pin info, hr %#x.\n", hr);
497 
498                 /* Release twice: Once normal, second from the
499                  * previous while loop
500                  */
501                 IBaseFilter_Stop(info.pFilter);
502                 IPin_Disconnect(to);
503                 IPin_Disconnect(avipin);
504                 IBaseFilter_Release(info.pFilter);
505                 IBaseFilter_Release(info.pFilter);
506             }
507             else
508             {
509                 IPin_Disconnect(to);
510                 IPin_Disconnect(avipin);
511             }
512         }
513         IPin_Release(avipin);
514         avipin = NULL;
515     }
516 
517 fail:
518     if (hr != S_OK)
519         skip("Prerequisites not matched, skipping remainder of test\n");
520     if (enumpins)
521         IEnumPins_Release(enumpins);
522 
523     if (avipin)
524         IPin_Release(avipin);
525     if (filepin)
526     {
527         IPin *to = NULL;
528 
529         IPin_ConnectedTo(filepin, &to);
530         if (to)
531         {
532             IPin_Disconnect(filepin);
533             IPin_Disconnect(to);
534         }
535         IPin_Release(filepin);
536     }
537 
538     if (preader)
539         IBaseFilter_Release(preader);
540     if (pavi)
541         IBaseFilter_Release(pavi);
542     if (pfile)
543         IFileSourceFilter_Release(pfile);
544 
545     DeleteFileW(filename);
546 }
547 
548 START_TEST(avisplitter)
549 {
550     CoInitialize(NULL);
551 
552     if (!create_avisplitter())
553     {
554         skip("Could not create avisplitter\n");
555         return;
556     }
557 
558     test_query_interface();
559     test_basefilter();
560     test_filesourcefilter();
561     test_filter_graph();
562 
563     release_avisplitter();
564 
565     CoUninitialize();
566 }
567