We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface; + + BOOL bitmap_grab_mode; + const GUID *time_format; + LONGLONG cur_pos; + HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) @@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{ + REFERENCE_TIME start_time, end_time; + struct testfilter *filter = arg; + IMemAllocator *allocator; + IMediaSample *sample; + unsigned i; + HRESULT hr; + DWORD fill; + BYTE *data; + + hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos; + while (hr == S_OK) + { + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + if (hr == VFW_E_NOT_COMMITTED) + { + IMemAllocator_Commit(allocator); + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); + } + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaSample_GetPointer(sample, &data); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb; + for (i = 0; i < 640 * 480 * 3; i += 3) + { + data[i] = fill ^ i; + data[i + 1] = fill >> 8 ^ i; + data[i + 2] = fill >> 16 ^ i; + } + + hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + end_time = start_time + 400000; + hr = IMediaSample_SetTime(sample, &start_time, &end_time); + ok(hr == S_OK, "Got hr %#x.\n", hr); + start_time = end_time; + + if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId()); + hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample); + if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr); + + IMediaSample_Release(sample); + } + + IMemAllocator_Release(allocator); + return hr; +} + +static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + if (!filter->bitmap_grab_mode) return S_OK; + + hr = BaseOutputPinImpl_Active(&filter->source); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL); + ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError()); + + return S_OK; +} + +static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_strmbase_filter(iface); + HRESULT hr; + + if (filter->thread) + { + WaitForSingleObject(filter->thread, INFINITE); + CloseHandle(filter->thread); + filter->thread = NULL; + } + if (!filter->bitmap_grab_mode) + return S_OK; + + hr = BaseOutputPinImpl_Inactive(&filter->source); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return S_OK; +} + static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy, + .filter_init_stream = testfilter_init_stream, + .filter_cleanup_stream = testfilter_cleanup_stream };
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface) @@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640, - .bmiHeader.biHeight = 480, + .bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB, @@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface, + IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested) +{ + ALLOCATOR_PROPERTIES actual; + + if (!requested->cbAlign) + requested->cbAlign = 1; + + if (requested->cbBuffer < 640 * 480 * 3) + requested->cbBuffer = 640 * 480 * 3; + + if (!requested->cBuffers) + requested->cBuffers = 1; + + return IMemAllocator_SetProperties(allocator, requested, &actual); +} + static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) { - return S_OK; + ALLOCATOR_PROPERTIES props = {0}; + HRESULT hr; + + hr = BaseOutputPinImpl_InitAllocator(iface, allocator); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + IMemInputPin_GetAllocatorRequirements(peer, &props); + hr = testsource_DecideBufferSize(iface, *allocator, &props); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); }
static const struct strmbase_source_ops testsource_ops = @@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection, + .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator, };
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) { + struct testfilter *filter = impl_from_IMediaSeeking(iface); + + if (filter->bitmap_grab_mode) + { + if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n"); + *caps = 0; /* Doesn't seem to have any effect, despite being called */ + return S_OK; + } + ok(0, "Unexpected call.\n"); return E_NOTIMPL; } @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) { + struct testfilter *filter = impl_from_IMediaSeeking(iface); + + if (filter->bitmap_grab_mode) + { + if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format)); + ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format)); + return S_OK; + } + ok(0, "Unexpected call.\n"); return E_NOTIMPL; } @@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) { + struct testfilter *filter = impl_from_IMediaSeeking(iface); + + if (filter->bitmap_grab_mode) + { + if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n"); + *format = *filter->time_format; + return S_OK; + } + ok(0, "Unexpected call.\n"); return E_NOTIMPL; }
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) { + struct testfilter *filter = impl_from_IMediaSeeking(iface); + + if (filter->bitmap_grab_mode) + { + if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format)); + return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE; + } + ok(0, "Unexpected call.\n"); return E_NOTIMPL; } @@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) { - ok(0, "Unexpected call.\n"); - return E_NOTIMPL; + struct testfilter *filter = impl_from_IMediaSeeking(iface); + + if (winetest_debug > 1) + trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n", + wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags); + + if (filter->bitmap_grab_mode) + { + ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop)); + ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime), + "Unexpected current_flags 0x%08x.\n", current_flags); + ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags); + + if (filter->thread) + { + IPin_BeginFlush(filter->source.pin.peer); + WaitForSingleObject(filter->thread, INFINITE); + CloseHandle(filter->thread); + filter->cur_pos = *current; + IPin_EndFlush(filter->source.pin.peer); + + filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL); + ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError()); + } + else + filter->cur_pos = *current; + } + + return S_OK; }
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop) @@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl; + filter->cur_pos = 0xdeadbeef; + filter->time_format = &TIME_FORMAT_MEDIA_TIME; }
static WCHAR test_avi_filename[MAX_PATH]; @@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{ + static const GUID *time_formats[] = + { + &TIME_FORMAT_NONE, + &TIME_FORMAT_FRAME, + &TIME_FORMAT_SAMPLE, + &TIME_FORMAT_FIELD, + &TIME_FORMAT_BYTE, + &TIME_FORMAT_MEDIA_TIME + }; + struct testfilter testfilter; + IMediaDet *detector; + AM_MEDIA_TYPE mt; + double duration; + IUnknown *unk; + unsigned i; + HRESULT hr; + LONG count; + ULONG ref; + GUID guid; + BSTR str; + + hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, + &IID_IMediaDet, (void **)&detector); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + + /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ + testfilter_init(&testfilter); + testfilter.bitmap_grab_mode = TRUE; + hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + + ref = IMediaDet_Release(detector); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %d.\n", ref); + + /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */ + for (i = 0; i < ARRAY_SIZE(time_formats); i++) + { + hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, + &IID_IMediaDet, (void **)&detector); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + testfilter_init(&testfilter); + testfilter.bitmap_grab_mode = TRUE; + testfilter.time_format = time_formats[i]; + hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0); + if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME) + { + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + } + else + ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr); + + ref = IMediaDet_Release(detector); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %d.\n", ref); + } + + hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, + &IID_IMediaDet, (void **)&detector); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + testfilter_init(&testfilter); + testfilter.bitmap_grab_mode = TRUE; + hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + + /* These still work */ + hr = IMediaDet_get_Filter(detector, &unk); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IUnknown_Release(unk); + hr = IMediaDet_get_Filename(detector, &str); + ok(hr == S_OK, "Got hr %#x.\n", hr); + SysFreeString(str); + hr = IMediaDet_get_CurrentStream(detector, &count); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(count == 0, "Got stream %d.\n", count); + + /* These don't work anymore */ + hr = IMediaDet_get_OutputStreams(detector, &count); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_get_FrameRate(detector, &duration); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_get_StreamLength(detector, &duration); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_get_StreamMediaType(detector, &mt); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + if (SUCCEEDED(hr)) FreeMediaType(&mt); + hr = IMediaDet_get_StreamType(detector, &guid); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_get_StreamTypeB(detector, &str); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + if (SUCCEEDED(hr)) SysFreeString(str); + hr = IMediaDet_put_CurrentStream(detector, 0); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + + /* Changing filter resets bitmap grab mode */ + testfilter.bitmap_grab_mode = FALSE; + hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_get_OutputStreams(detector, &count); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(count == 1, "Got %d streams.\n", count); + + ref = IMediaDet_Release(detector); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); + ok(!ref, "Got outstanding refcount %d.\n", ref); +} + START_TEST(mediadet) { IMediaDet *detector; @@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins(); + test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/mediadet.c | 191 ++++++++++++++++++++++++++++++++++-- dlls/qedit/tests/mediadet.c | 38 ++++--- 2 files changed, 203 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index bacf520..11498bd 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -40,6 +40,7 @@ typedef struct MediaDetImpl { IGraphBuilder *graph; IBaseFilter *source; IBaseFilter *splitter; + ISampleGrabber *grabber; WCHAR *filename; LONG num_streams; LONG cur_stream; @@ -64,6 +65,8 @@ static void MD_cleanup(MediaDetImpl *This) This->source = NULL; if (This->splitter) IBaseFilter_Release(This->splitter); This->splitter = NULL; + if (This->grabber) ISampleGrabber_Release(This->grabber); + This->grabber = NULL; if (This->graph) IGraphBuilder_Release(This->graph); This->graph = NULL; free(This->filename); @@ -101,6 +104,38 @@ static HRESULT get_filter_info(IMoniker *moniker, GUID *clsid, VARIANT *var) return hr; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{ + IEnumPins *pins; + HRESULT hr; + IPin *pin; + + if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins))) + return hr; + + while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK) + { + PIN_DIRECTION dir; + + hr = IPin_QueryDirection(pin, &dir); + if (FAILED(hr)) + { + IPin_Release(pin); + IEnumPins_Release(pins); + return hr; + } + if (dir == pin_dir) + { + *out = pin; + IEnumPins_Release(pins); + return S_OK; + } + IPin_Release(pin); + } + IEnumPins_Release(pins); + return E_NOTIMPL; +} + static HRESULT get_pin_media_type(IPin *pin, AM_MEDIA_TYPE *out) { IEnumMediaTypes *enummt; @@ -224,6 +259,71 @@ next: return hr; }
+static HRESULT seek_source(MediaDetImpl *detector, double seek_time) +{ + FILTER_STATE state; + LONGLONG pos, stop; + IMediaControl *mc; + IMediaSeeking *ms; + IMediaFilter *mf; + GUID format; + HRESULT hr; + + if (FAILED(hr = IPin_QueryInterface(detector->cur_pin, &IID_IMediaSeeking, (void**)&ms))) + return hr; + + if (IMediaSeeking_IsUsingTimeFormat(ms, &TIME_FORMAT_MEDIA_TIME) != S_OK && + (FAILED(IMediaSeeking_GetTimeFormat(ms, &format)) || + !IsEqualGUID(&format, &TIME_FORMAT_MEDIA_TIME))) + { + /* Windows doesn't implement it */ + hr = E_NOTIMPL; + } + IMediaSeeking_Release(ms); + if (FAILED(hr)) + return hr; + + if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaControl, (void**)&mc))) + return hr; + + if (FAILED(hr = IMediaControl_Stop(mc))) + goto done; + + if (seek_time >= 0.0) + { + if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaSeeking, (void**)&ms))) + goto done; + + stop = pos = seek_time * 10000000.0; + hr = IMediaSeeking_SetPositions(ms, &pos, AM_SEEKING_AbsolutePositioning, + &stop, AM_SEEKING_AbsolutePositioning); + IMediaSeeking_Release(ms); + if (FAILED(hr)) + goto done; + } + + if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaFilter, (void**)&mf))) + goto done; + hr = IMediaFilter_SetSyncSource(mf, NULL); + IMediaFilter_Release(mf); + if (FAILED(hr)) goto done; + + if (FAILED(hr = IMediaControl_Pause(mc))) + goto done; + + /* Testing on Windows shows it waits up to 37500 ms */ + if (FAILED(hr = IMediaControl_GetState(mc, 37500, (OAFilterState*)&state))) + { + if (hr == VFW_S_STATE_INTERMEDIATE) hr = VFW_E_TIME_EXPIRED; + goto done; + } + + hr = (state == State_Paused) ? S_OK : E_FAIL; +done: + IMediaControl_Release(mc); + return hr; +} + /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) { @@ -371,7 +471,7 @@ static HRESULT WINAPI MediaDet_get_OutputStreams(IMediaDet* iface, LONG *pVal)
TRACE("(%p)\n", This);
- if (!This->splitter) + if (This->grabber || !This->splitter) return E_INVALIDARG;
if (This->num_streams != -1) @@ -466,6 +566,9 @@ static HRESULT WINAPI MediaDet_put_CurrentStream(IMediaDet* iface, LONG newVal)
TRACE("(%p)->(%d)\n", This, newVal);
+ if (This->grabber) + return E_INVALIDARG; + if (This->num_streams == -1) { LONG n; @@ -495,6 +598,8 @@ static HRESULT WINAPI MediaDet_get_StreamType(IMediaDet *iface, GUID *majortype)
if (!majortype) return E_POINTER; + if (detector->grabber) + return E_INVALIDARG;
if (SUCCEEDED(hr = IMediaDet_get_StreamMediaType(iface, &mt))) { @@ -533,7 +638,7 @@ static HRESULT WINAPI MediaDet_get_StreamLength(IMediaDet *iface, double *length if (!length) return E_POINTER;
- if (!detector->cur_pin) + if (detector->grabber || !detector->cur_pin) return E_INVALIDARG;
if (SUCCEEDED(hr = IPin_QueryInterface(detector->cur_pin, @@ -647,7 +752,7 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, if (!pVal) return E_POINTER;
- if (!This->cur_pin) + if (This->grabber || !This->cur_pin) return E_INVALIDARG;
return get_pin_media_type(This->cur_pin, pVal); @@ -672,6 +777,8 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
if (!pVal) return E_POINTER; + if (This->grabber) + return E_INVALIDARG;
hr = MediaDet_get_StreamMediaType(iface, &mt); if (FAILED(hr)) @@ -693,9 +800,81 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) static HRESULT WINAPI MediaDet_EnterBitmapGrabMode(IMediaDet* iface, double SeekTime) { - MediaDetImpl *This = impl_from_IMediaDet(iface); - FIXME("(%p)->(%f): not implemented!\n", This, SeekTime); - return E_NOTIMPL; + MediaDetImpl *detector = impl_from_IMediaDet(iface); + IPin *sg_inpin, *sg_outpin, *null_pin; + IBaseFilter *sg_filter, *null_filter; + ISampleGrabber *sg; + AM_MEDIA_TYPE mt; + GUID major_type; + HRESULT hr; + + TRACE("(%p)->(%f)\n", detector, SeekTime); + + if (detector->grabber) + return S_OK; + if (!detector->cur_pin) + return E_INVALIDARG; + if (FAILED(hr = get_pin_media_type(detector->cur_pin, &mt))) + return hr; + major_type = mt.majortype; + FreeMediaType(&mt); + if (!IsEqualGUID(&major_type, &MEDIATYPE_Video)) + return VFW_E_INVALIDMEDIATYPE; + + hr = CoCreateInstance(&CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void**)&sg_filter); + if (FAILED(hr)) return hr; + + if (FAILED(hr = IBaseFilter_QueryInterface(sg_filter, &IID_ISampleGrabber, (void**)&sg))) + { + IBaseFilter_Release(sg_filter); + return hr; + } + + memset(&mt, 0, sizeof(mt)); + mt.majortype = MEDIATYPE_Video; + mt.subtype = MEDIASUBTYPE_RGB24; + mt.formattype = FORMAT_VideoInfo; + if (FAILED(hr = ISampleGrabber_SetMediaType(sg, &mt)) || + FAILED(hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, + &IID_IBaseFilter, (void**)&null_filter))) + { + ISampleGrabber_Release(sg); + IBaseFilter_Release(sg_filter); + return hr; + } + ISampleGrabber_SetBufferSamples(sg, TRUE); + + sg_inpin = sg_outpin = null_pin = NULL; + if (FAILED(hr = get_first_pin(sg_filter, PINDIR_INPUT, &sg_inpin))) goto err; + if (FAILED(hr = get_first_pin(sg_filter, PINDIR_OUTPUT, &sg_outpin))) goto err; + if (FAILED(hr = get_first_pin(null_filter, PINDIR_INPUT, &null_pin))) goto err; + + if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, sg_filter, L"BitBucket"))) + { + if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, null_filter, L"NullRenderer"))) + { + if (SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, detector->cur_pin, sg_inpin)) && + SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, sg_outpin, null_pin)) && + SUCCEEDED(hr = seek_source(detector, SeekTime))) + { + detector->grabber = sg; + goto done; + } + IGraphBuilder_RemoveFilter(detector->graph, null_filter); + } + IGraphBuilder_RemoveFilter(detector->graph, sg_filter); + } + +err: + ISampleGrabber_Release(sg); +done: + if (null_pin) IPin_Release(null_pin); + if (sg_outpin) IPin_Release(sg_outpin); + if (sg_inpin) IPin_Release(sg_inpin); + IBaseFilter_Release(null_filter); + IBaseFilter_Release(sg_filter); + return hr; }
static const IMediaDetVtbl IMediaDet_VTable = diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 010b746..afad173 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1335,7 +1335,7 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); @@ -1344,10 +1344,10 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ref = IMediaDet_Release(detector); @@ -1371,11 +1371,11 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0); if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME) { - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); } else ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr); @@ -1396,11 +1396,11 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
/* These still work */ hr = IMediaDet_get_Filter(detector, &unk); @@ -1415,21 +1415,19 @@ static void test_bitmap_grab_mode(void)
/* These don't work anymore */ hr = IMediaDet_get_OutputStreams(detector, &count); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_FrameRate(detector, &duration); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamLength(detector, &duration); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamMediaType(detector, &mt); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); - if (SUCCEEDED(hr)) FreeMediaType(&mt); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamType(detector, &guid); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamTypeB(detector, &str); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); - if (SUCCEEDED(hr)) SysFreeString(str); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_put_CurrentStream(detector, 0); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 191 ++++++++++++++++++++++++++++++++++-- dlls/qedit/tests/mediadet.c | 38 ++++--- 2 files changed, 203 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index bacf520..11498bd 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -40,6 +40,7 @@ typedef struct MediaDetImpl { IGraphBuilder *graph; IBaseFilter *source; IBaseFilter *splitter;
- ISampleGrabber *grabber; WCHAR *filename; LONG num_streams; LONG cur_stream;
@@ -64,6 +65,8 @@ static void MD_cleanup(MediaDetImpl *This) This->source = NULL; if (This->splitter) IBaseFilter_Release(This->splitter); This->splitter = NULL;
- if (This->grabber) ISampleGrabber_Release(This->grabber);
- This->grabber = NULL; if (This->graph) IGraphBuilder_Release(This->graph); This->graph = NULL; free(This->filename);
@@ -101,6 +104,38 @@ static HRESULT get_filter_info(IMoniker *moniker, GUID *clsid, VARIANT *var) return hr; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
static HRESULT get_pin_media_type(IPin *pin, AM_MEDIA_TYPE *out) { IEnumMediaTypes *enummt; @@ -224,6 +259,71 @@ next: return hr; }
+static HRESULT seek_source(MediaDetImpl *detector, double seek_time)
"seek_source" is a bit of a weird name for this, because you're really seeking the graph; it's just that seeking the graph implicitly means seeking the source.
+{
- FILTER_STATE state;
- LONGLONG pos, stop;
- IMediaControl *mc;
- IMediaSeeking *ms;
- IMediaFilter *mf;
- GUID format;
- HRESULT hr;
- if (FAILED(hr = IPin_QueryInterface(detector->cur_pin, &IID_IMediaSeeking, (void**)&ms)))
return hr;
- if (IMediaSeeking_IsUsingTimeFormat(ms, &TIME_FORMAT_MEDIA_TIME) != S_OK &&
(FAILED(IMediaSeeking_GetTimeFormat(ms, &format)) ||
!IsEqualGUID(&format, &TIME_FORMAT_MEDIA_TIME)))
- {
/* Windows doesn't implement it */
hr = E_NOTIMPL;
- }
- IMediaSeeking_Release(ms);
- if (FAILED(hr))
return hr;
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaControl, (void**)&mc)))
return hr;
- if (FAILED(hr = IMediaControl_Stop(mc)))
goto done;
Why do you need to stop the graph?
- if (seek_time >= 0.0)
- {
if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaSeeking, (void**)&ms)))
goto done;
stop = pos = seek_time * 10000000.0;
hr = IMediaSeeking_SetPositions(ms, &pos, AM_SEEKING_AbsolutePositioning,
&stop, AM_SEEKING_AbsolutePositioning);
IMediaSeeking_Release(ms);
if (FAILED(hr))
goto done;
- }
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaFilter, (void**)&mf)))
goto done;
- hr = IMediaFilter_SetSyncSource(mf, NULL);
- IMediaFilter_Release(mf);
- if (FAILED(hr)) goto done;
- if (FAILED(hr = IMediaControl_Pause(mc)))
goto done;
Or pause it?
- /* Testing on Windows shows it waits up to 37500 ms */
- if (FAILED(hr = IMediaControl_GetState(mc, 37500, (OAFilterState*)&state)))
- {
if (hr == VFW_S_STATE_INTERMEDIATE) hr = VFW_E_TIME_EXPIRED;
goto done;
- }
- hr = (state == State_Paused) ? S_OK : E_FAIL;
Can this even happen?
+done:
I don't think that labels are generally worthwhile, not when there's only one line of cleanup.
- IMediaControl_Release(mc);
- return hr;
+}
/* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) { @@ -371,7 +471,7 @@ static HRESULT WINAPI MediaDet_get_OutputStreams(IMediaDet* iface, LONG *pVal)
TRACE("(%p)\n", This);
- if (!This->splitter)
if (This->grabber || !This->splitter) return E_INVALIDARG;
if (This->num_streams != -1)
@@ -466,6 +566,9 @@ static HRESULT WINAPI MediaDet_put_CurrentStream(IMediaDet* iface, LONG newVal)
TRACE("(%p)->(%d)\n", This, newVal);
- if (This->grabber)
return E_INVALIDARG;
- if (This->num_streams == -1) { LONG n;
@@ -495,6 +598,8 @@ static HRESULT WINAPI MediaDet_get_StreamType(IMediaDet *iface, GUID *majortype)
if (!majortype) return E_POINTER;
if (detector->grabber)
return E_INVALIDARG;
if (SUCCEEDED(hr = IMediaDet_get_StreamMediaType(iface, &mt))) {
@@ -533,7 +638,7 @@ static HRESULT WINAPI MediaDet_get_StreamLength(IMediaDet *iface, double *length if (!length) return E_POINTER;
- if (!detector->cur_pin)
if (detector->grabber || !detector->cur_pin) return E_INVALIDARG;
if (SUCCEEDED(hr = IPin_QueryInterface(detector->cur_pin,
@@ -647,7 +752,7 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, if (!pVal) return E_POINTER;
- if (!This->cur_pin)
if (This->grabber || !This->cur_pin) return E_INVALIDARG;
return get_pin_media_type(This->cur_pin, pVal);
@@ -672,6 +777,8 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
if (!pVal) return E_POINTER;
if (This->grabber)
return E_INVALIDARG;
hr = MediaDet_get_StreamMediaType(iface, &mt); if (FAILED(hr))
@@ -693,9 +800,81 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) static HRESULT WINAPI MediaDet_EnterBitmapGrabMode(IMediaDet* iface, double SeekTime) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f): not implemented!\n", This, SeekTime);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- IPin *sg_inpin, *sg_outpin, *null_pin;
- IBaseFilter *sg_filter, *null_filter;
- ISampleGrabber *sg;
- AM_MEDIA_TYPE mt;
- GUID major_type;
- HRESULT hr;
- TRACE("(%p)->(%f)\n", detector, SeekTime);
Use "%.16e" for doubles, please; we'd like to keep full precision.
- if (detector->grabber)
return S_OK;
- if (!detector->cur_pin)
return E_INVALIDARG;
- if (FAILED(hr = get_pin_media_type(detector->cur_pin, &mt)))
return hr;
- major_type = mt.majortype;
- FreeMediaType(&mt);
- if (!IsEqualGUID(&major_type, &MEDIATYPE_Video))
return VFW_E_INVALIDMEDIATYPE;
Can this be tested?
- hr = CoCreateInstance(&CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&sg_filter);
I'd be more than a little tempted ot use sample_grabber_create() directly, bypassing COM overhead...
- if (FAILED(hr)) return hr;
- if (FAILED(hr = IBaseFilter_QueryInterface(sg_filter, &IID_ISampleGrabber, (void**)&sg)))
- {
IBaseFilter_Release(sg_filter);
return hr;
- }
- memset(&mt, 0, sizeof(mt));
- mt.majortype = MEDIATYPE_Video;
- mt.subtype = MEDIASUBTYPE_RGB24;
- mt.formattype = FORMAT_VideoInfo;
- if (FAILED(hr = ISampleGrabber_SetMediaType(sg, &mt)) ||
FAILED(hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&null_filter)))
And the same here...
- {
ISampleGrabber_Release(sg);
IBaseFilter_Release(sg_filter);
return hr;
- }
- ISampleGrabber_SetBufferSamples(sg, TRUE);
- sg_inpin = sg_outpin = null_pin = NULL;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_INPUT, &sg_inpin))) goto err;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_OUTPUT, &sg_outpin))) goto err;
- if (FAILED(hr = get_first_pin(null_filter, PINDIR_INPUT, &null_pin))) goto err;
IBaseFilter::FindPin() would be simpler. Also, it's our code; the calls shouldn't fail, which means at best they deserve an assert().
- if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, sg_filter, L"BitBucket")))
- {
I would probably stick with FAILED() error handling here (i.e. reverse it); it's consistent with the above and it keeps the indentation level low.
if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, null_filter, L"NullRenderer")))
{
if (SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, detector->cur_pin, sg_inpin)) &&
SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, sg_outpin, null_pin)) &&
At least the latter can be ConnectDirect().
SUCCEEDED(hr = seek_source(detector, SeekTime)))
{
detector->grabber = sg;
goto done;
}
IGraphBuilder_RemoveFilter(detector->graph, null_filter);
}
IGraphBuilder_RemoveFilter(detector->graph, sg_filter);
- }
+err:
- ISampleGrabber_Release(sg);
+done:
- if (null_pin) IPin_Release(null_pin);
- if (sg_outpin) IPin_Release(sg_outpin);
- if (sg_inpin) IPin_Release(sg_inpin);
- IBaseFilter_Release(null_filter);
- IBaseFilter_Release(sg_filter);
- return hr;
}
static const IMediaDetVtbl IMediaDet_VTable = diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 010b746..afad173 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1335,7 +1335,7 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1344,10 +1344,10 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ref = IMediaDet_Release(detector);
@@ -1371,11 +1371,11 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0); if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME) {
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); } else ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
@@ -1396,11 +1396,11 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
/* These still work */ hr = IMediaDet_get_Filter(detector, &unk);
@@ -1415,21 +1415,19 @@ static void test_bitmap_grab_mode(void)
/* These don't work anymore */ hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
On 20/10/2020 20:11, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 191 ++++++++++++++++++++++++++++++++++-- dlls/qedit/tests/mediadet.c | 38 ++++--- 2 files changed, 203 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index bacf520..11498bd 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -40,6 +40,7 @@ typedef struct MediaDetImpl { IGraphBuilder *graph; IBaseFilter *source; IBaseFilter *splitter;
- ISampleGrabber *grabber; WCHAR *filename; LONG num_streams; LONG cur_stream;
@@ -64,6 +65,8 @@ static void MD_cleanup(MediaDetImpl *This) This->source = NULL; if (This->splitter) IBaseFilter_Release(This->splitter); This->splitter = NULL;
- if (This->grabber) ISampleGrabber_Release(This->grabber);
- This->grabber = NULL; if (This->graph) IGraphBuilder_Release(This->graph); This->graph = NULL; free(This->filename);
@@ -101,6 +104,38 @@ static HRESULT get_filter_info(IMoniker *moniker, GUID *clsid, VARIANT *var) return hr; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
- static HRESULT get_pin_media_type(IPin *pin, AM_MEDIA_TYPE *out) { IEnumMediaTypes *enummt;
@@ -224,6 +259,71 @@ next: return hr; }
+static HRESULT seek_source(MediaDetImpl *detector, double seek_time)
"seek_source" is a bit of a weird name for this, because you're really seeking the graph; it's just that seeking the graph implicitly means seeking the source.
+{
- FILTER_STATE state;
- LONGLONG pos, stop;
- IMediaControl *mc;
- IMediaSeeking *ms;
- IMediaFilter *mf;
- GUID format;
- HRESULT hr;
- if (FAILED(hr = IPin_QueryInterface(detector->cur_pin, &IID_IMediaSeeking, (void**)&ms)))
return hr;
- if (IMediaSeeking_IsUsingTimeFormat(ms, &TIME_FORMAT_MEDIA_TIME) != S_OK &&
(FAILED(IMediaSeeking_GetTimeFormat(ms, &format)) ||
!IsEqualGUID(&format, &TIME_FORMAT_MEDIA_TIME)))
- {
/* Windows doesn't implement it */
hr = E_NOTIMPL;
- }
- IMediaSeeking_Release(ms);
- if (FAILED(hr))
return hr;
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaControl, (void**)&mc)))
return hr;
- if (FAILED(hr = IMediaControl_Stop(mc)))
goto done;
Why do you need to stop the graph?
This is one of those bugs uncovered by the realistic custom filter :-)
If I don't stop it, there's a race condition with the Sample Grabber. The SG just gets the last Paused sample. When the filter seeks, it gets the sample that blocked first (because it now returned), instead of the one on the new seek, which is sent after it. It happens at the same time but it's slower than the test, so it always failed for me.
When I traced Windows with strmbase to see what it calls on my custom filter, I also got a Stop() when seeking.
This is one of the reasons I'd really prefer to keep the custom filter the way it is right now. This bug wouldn't even be tested for if we didn't ran a realistic loop and flush, for example.
- if (seek_time >= 0.0)
- {
if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaSeeking, (void**)&ms)))
goto done;
stop = pos = seek_time * 10000000.0;
hr = IMediaSeeking_SetPositions(ms, &pos, AM_SEEKING_AbsolutePositioning,
&stop, AM_SEEKING_AbsolutePositioning);
IMediaSeeking_Release(ms);
if (FAILED(hr))
goto done;
- }
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaFilter, (void**)&mf)))
goto done;
- hr = IMediaFilter_SetSyncSource(mf, NULL);
- IMediaFilter_Release(mf);
- if (FAILED(hr)) goto done;
- if (FAILED(hr = IMediaControl_Pause(mc)))
goto done;
Or pause it?
Same as above: if we don't pause it, it just pumps out samples, so we need to freeze it (if we didn't send samples in a loop with the filter, it wouldn't test for this, so another reason for the loop).
I can probably add a test to check that the graph is indeed paused, because that's what Windows does (it doesn't use the OneShot capability of the Sample Grabber). This matters since the application can potentially grab the Sample Grabber and screw with it.
- /* Testing on Windows shows it waits up to 37500 ms */
- if (FAILED(hr = IMediaControl_GetState(mc, 37500, (OAFilterState*)&state)))
- {
if (hr == VFW_S_STATE_INTERMEDIATE) hr = VFW_E_TIME_EXPIRED;
goto done;
- }
- hr = (state == State_Paused) ? S_OK : E_FAIL;
Can this even happen?
Uh, I remember I hacked some stuff when testing on Windows and managed to get it return E_FAIL when I overrode the state in testfilter_init_stream. But it's probably not important so I'll leave it out.
+done:
I don't think that labels are generally worthwhile, not when there's only one line of cleanup.
But it's called from a lot of places and it simplifies the code, otherwise I'd have to open braces and add two lines on each of them. That would be almost 20 extra lines of code. Are you sure I should get rid of it?
- IMediaControl_Release(mc);
- return hr;
+}
- /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) {
@@ -371,7 +471,7 @@ static HRESULT WINAPI MediaDet_get_OutputStreams(IMediaDet* iface, LONG *pVal)
TRACE("(%p)\n", This);
- if (!This->splitter)
if (This->grabber || !This->splitter) return E_INVALIDARG;
if (This->num_streams != -1)
@@ -466,6 +566,9 @@ static HRESULT WINAPI MediaDet_put_CurrentStream(IMediaDet* iface, LONG newVal)
TRACE("(%p)->(%d)\n", This, newVal);
- if (This->grabber)
return E_INVALIDARG;
if (This->num_streams == -1) { LONG n;
@@ -495,6 +598,8 @@ static HRESULT WINAPI MediaDet_get_StreamType(IMediaDet *iface, GUID *majortype)
if (!majortype) return E_POINTER;
if (detector->grabber)
return E_INVALIDARG; if (SUCCEEDED(hr = IMediaDet_get_StreamMediaType(iface, &mt))) {
@@ -533,7 +638,7 @@ static HRESULT WINAPI MediaDet_get_StreamLength(IMediaDet *iface, double *length if (!length) return E_POINTER;
- if (!detector->cur_pin)
if (detector->grabber || !detector->cur_pin) return E_INVALIDARG;
if (SUCCEEDED(hr = IPin_QueryInterface(detector->cur_pin,
@@ -647,7 +752,7 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, if (!pVal) return E_POINTER;
- if (!This->cur_pin)
if (This->grabber || !This->cur_pin) return E_INVALIDARG;
return get_pin_media_type(This->cur_pin, pVal);
@@ -672,6 +777,8 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
if (!pVal) return E_POINTER;
if (This->grabber)
return E_INVALIDARG; hr = MediaDet_get_StreamMediaType(iface, &mt); if (FAILED(hr))
@@ -693,9 +800,81 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) static HRESULT WINAPI MediaDet_EnterBitmapGrabMode(IMediaDet* iface, double SeekTime) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f): not implemented!\n", This, SeekTime);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- IPin *sg_inpin, *sg_outpin, *null_pin;
- IBaseFilter *sg_filter, *null_filter;
- ISampleGrabber *sg;
- AM_MEDIA_TYPE mt;
- GUID major_type;
- HRESULT hr;
- TRACE("(%p)->(%f)\n", detector, SeekTime);
Use "%.16e" for doubles, please; we'd like to keep full precision.
- if (detector->grabber)
return S_OK;
- if (!detector->cur_pin)
return E_INVALIDARG;
- if (FAILED(hr = get_pin_media_type(detector->cur_pin, &mt)))
return hr;
- major_type = mt.majortype;
- FreeMediaType(&mt);
- if (!IsEqualGUID(&major_type, &MEDIATYPE_Video))
return VFW_E_INVALIDMEDIATYPE;
Can this be tested?
I think so, I think I'll change just the major type to check that it's the only thing being tested.
- hr = CoCreateInstance(&CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&sg_filter);
I'd be more than a little tempted ot use sample_grabber_create() directly, bypassing COM overhead...
- if (FAILED(hr)) return hr;
- if (FAILED(hr = IBaseFilter_QueryInterface(sg_filter, &IID_ISampleGrabber, (void**)&sg)))
- {
IBaseFilter_Release(sg_filter);
return hr;
- }
- memset(&mt, 0, sizeof(mt));
- mt.majortype = MEDIATYPE_Video;
- mt.subtype = MEDIASUBTYPE_RGB24;
- mt.formattype = FORMAT_VideoInfo;
- if (FAILED(hr = ISampleGrabber_SetMediaType(sg, &mt)) ||
FAILED(hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&null_filter)))
And the same here...
- {
ISampleGrabber_Release(sg);
IBaseFilter_Release(sg_filter);
return hr;
- }
- ISampleGrabber_SetBufferSamples(sg, TRUE);
- sg_inpin = sg_outpin = null_pin = NULL;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_INPUT, &sg_inpin))) goto err;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_OUTPUT, &sg_outpin))) goto err;
- if (FAILED(hr = get_first_pin(null_filter, PINDIR_INPUT, &null_pin))) goto err;
IBaseFilter::FindPin() would be simpler. Also, it's our code; the calls shouldn't fail, which means at best they deserve an assert().
- if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, sg_filter, L"BitBucket")))
- {
I would probably stick with FAILED() error handling here (i.e. reverse it); it's consistent with the above and it keeps the indentation level low.
if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, null_filter, L"NullRenderer")))
{
if (SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, detector->cur_pin, sg_inpin)) &&
SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, sg_outpin, null_pin)) &&
At least the latter can be ConnectDirect().
SUCCEEDED(hr = seek_source(detector, SeekTime)))
{
detector->grabber = sg;
goto done;
}
IGraphBuilder_RemoveFilter(detector->graph, null_filter);
}
IGraphBuilder_RemoveFilter(detector->graph, sg_filter);
- }
+err:
- ISampleGrabber_Release(sg);
+done:
if (null_pin) IPin_Release(null_pin);
if (sg_outpin) IPin_Release(sg_outpin);
if (sg_inpin) IPin_Release(sg_inpin);
IBaseFilter_Release(null_filter);
IBaseFilter_Release(sg_filter);
return hr; }
static const IMediaDetVtbl IMediaDet_VTable =
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 010b746..afad173 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1335,7 +1335,7 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1344,10 +1344,10 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ref = IMediaDet_Release(detector);
@@ -1371,11 +1371,11 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0); if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME) {
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); } else ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
@@ -1396,11 +1396,11 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
/* These still work */ hr = IMediaDet_get_Filter(detector, &unk);
@@ -1415,21 +1415,19 @@ static void test_bitmap_grab_mode(void)
/* These don't work anymore */ hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
On 10/21/20 7:49 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 20:11, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 191 ++++++++++++++++++++++++++++++++++-- dlls/qedit/tests/mediadet.c | 38 ++++--- 2 files changed, 203 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index bacf520..11498bd 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -40,6 +40,7 @@ typedef struct MediaDetImpl { IGraphBuilder *graph; IBaseFilter *source; IBaseFilter *splitter;
- ISampleGrabber *grabber; WCHAR *filename; LONG num_streams; LONG cur_stream;
@@ -64,6 +65,8 @@ static void MD_cleanup(MediaDetImpl *This) This->source = NULL; if (This->splitter) IBaseFilter_Release(This->splitter); This->splitter = NULL;
- if (This->grabber) ISampleGrabber_Release(This->grabber);
- This->grabber = NULL; if (This->graph) IGraphBuilder_Release(This->graph); This->graph = NULL; free(This->filename);
@@ -101,6 +104,38 @@ static HRESULT get_filter_info(IMoniker *moniker, GUID *clsid, VARIANT *var) return hr; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
- static HRESULT get_pin_media_type(IPin *pin, AM_MEDIA_TYPE *out) { IEnumMediaTypes *enummt;
@@ -224,6 +259,71 @@ next: return hr; }
+static HRESULT seek_source(MediaDetImpl *detector, double seek_time)
"seek_source" is a bit of a weird name for this, because you're really seeking the graph; it's just that seeking the graph implicitly means seeking the source.
+{
- FILTER_STATE state;
- LONGLONG pos, stop;
- IMediaControl *mc;
- IMediaSeeking *ms;
- IMediaFilter *mf;
- GUID format;
- HRESULT hr;
- if (FAILED(hr = IPin_QueryInterface(detector->cur_pin, &IID_IMediaSeeking, (void**)&ms)))
return hr;
- if (IMediaSeeking_IsUsingTimeFormat(ms, &TIME_FORMAT_MEDIA_TIME) != S_OK &&
(FAILED(IMediaSeeking_GetTimeFormat(ms, &format)) ||
!IsEqualGUID(&format, &TIME_FORMAT_MEDIA_TIME)))
- {
/* Windows doesn't implement it */
hr = E_NOTIMPL;
- }
- IMediaSeeking_Release(ms);
- if (FAILED(hr))
return hr;
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaControl, (void**)&mc)))
return hr;
- if (FAILED(hr = IMediaControl_Stop(mc)))
goto done;
Why do you need to stop the graph?
This is one of those bugs uncovered by the realistic custom filter :-)
If I don't stop it, there's a race condition with the Sample Grabber. The SG just gets the last Paused sample. When the filter seeks, it gets the sample that blocked first (because it now returned), instead of the one on the new seek, which is sent after it. It happens at the same time but it's slower than the test, so it always failed for me.
When I traced Windows with strmbase to see what it calls on my custom filter, I also got a Stop() when seeking.
This is one of the reasons I'd really prefer to keep the custom filter the way it is right now. This bug wouldn't even be tested for if we didn't ran a realistic loop and flush, for example.
- if (seek_time >= 0.0)
- {
if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaSeeking, (void**)&ms)))
goto done;
stop = pos = seek_time * 10000000.0;
hr = IMediaSeeking_SetPositions(ms, &pos, AM_SEEKING_AbsolutePositioning,
&stop, AM_SEEKING_AbsolutePositioning);
IMediaSeeking_Release(ms);
if (FAILED(hr))
goto done;
- }
- if (FAILED(hr = IGraphBuilder_QueryInterface(detector->graph, &IID_IMediaFilter, (void**)&mf)))
goto done;
- hr = IMediaFilter_SetSyncSource(mf, NULL);
- IMediaFilter_Release(mf);
- if (FAILED(hr)) goto done;
- if (FAILED(hr = IMediaControl_Pause(mc)))
goto done;
Or pause it?
Same as above: if we don't pause it, it just pumps out samples, so we need to freeze it (if we didn't send samples in a loop with the filter, it wouldn't test for this, so another reason for the loop).
I can probably add a test to check that the graph is indeed paused, because that's what Windows does (it doesn't use the OneShot capability of the Sample Grabber). This matters since the application can potentially grab the Sample Grabber and screw with it.
- /* Testing on Windows shows it waits up to 37500 ms */
- if (FAILED(hr = IMediaControl_GetState(mc, 37500, (OAFilterState*)&state)))
- {
if (hr == VFW_S_STATE_INTERMEDIATE) hr = VFW_E_TIME_EXPIRED;
goto done;
- }
- hr = (state == State_Paused) ? S_OK : E_FAIL;
Can this even happen?
Uh, I remember I hacked some stuff when testing on Windows and managed to get it return E_FAIL when I overrode the state in testfilter_init_stream. But it's probably not important so I'll leave it out.
If you return the wrong state from IBaseFilter::GetState(), the filter graph will return E_FAIL (we actually have tests for that, and implement it correctly even). But in that case we're probably just propagating the same return value.
+done:
I don't think that labels are generally worthwhile, not when there's only one line of cleanup.
But it's called from a lot of places and it simplifies the code, otherwise I'd have to open braces and add two lines on each of them. That would be almost 20 extra lines of code. Are you sure I should get rid of it?
I guess it's fine...
- IMediaControl_Release(mc);
- return hr;
+}
- /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) {
@@ -371,7 +471,7 @@ static HRESULT WINAPI MediaDet_get_OutputStreams(IMediaDet* iface, LONG *pVal)
TRACE("(%p)\n", This);
- if (!This->splitter)
if (This->grabber || !This->splitter) return E_INVALIDARG;
if (This->num_streams != -1)
@@ -466,6 +566,9 @@ static HRESULT WINAPI MediaDet_put_CurrentStream(IMediaDet* iface, LONG newVal)
TRACE("(%p)->(%d)\n", This, newVal);
- if (This->grabber)
return E_INVALIDARG;
if (This->num_streams == -1) { LONG n;
@@ -495,6 +598,8 @@ static HRESULT WINAPI MediaDet_get_StreamType(IMediaDet *iface, GUID *majortype)
if (!majortype) return E_POINTER;
if (detector->grabber)
return E_INVALIDARG; if (SUCCEEDED(hr = IMediaDet_get_StreamMediaType(iface, &mt))) {
@@ -533,7 +638,7 @@ static HRESULT WINAPI MediaDet_get_StreamLength(IMediaDet *iface, double *length if (!length) return E_POINTER;
- if (!detector->cur_pin)
if (detector->grabber || !detector->cur_pin) return E_INVALIDARG;
if (SUCCEEDED(hr = IPin_QueryInterface(detector->cur_pin,
@@ -647,7 +752,7 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, if (!pVal) return E_POINTER;
- if (!This->cur_pin)
if (This->grabber || !This->cur_pin) return E_INVALIDARG;
return get_pin_media_type(This->cur_pin, pVal);
@@ -672,6 +777,8 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
if (!pVal) return E_POINTER;
if (This->grabber)
return E_INVALIDARG; hr = MediaDet_get_StreamMediaType(iface, &mt); if (FAILED(hr))
@@ -693,9 +800,81 @@ static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) static HRESULT WINAPI MediaDet_EnterBitmapGrabMode(IMediaDet* iface, double SeekTime) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f): not implemented!\n", This, SeekTime);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- IPin *sg_inpin, *sg_outpin, *null_pin;
- IBaseFilter *sg_filter, *null_filter;
- ISampleGrabber *sg;
- AM_MEDIA_TYPE mt;
- GUID major_type;
- HRESULT hr;
- TRACE("(%p)->(%f)\n", detector, SeekTime);
Use "%.16e" for doubles, please; we'd like to keep full precision.
- if (detector->grabber)
return S_OK;
- if (!detector->cur_pin)
return E_INVALIDARG;
- if (FAILED(hr = get_pin_media_type(detector->cur_pin, &mt)))
return hr;
- major_type = mt.majortype;
- FreeMediaType(&mt);
- if (!IsEqualGUID(&major_type, &MEDIATYPE_Video))
return VFW_E_INVALIDMEDIATYPE;
Can this be tested?
I think so, I think I'll change just the major type to check that it's the only thing being tested.
- hr = CoCreateInstance(&CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&sg_filter);
I'd be more than a little tempted ot use sample_grabber_create() directly, bypassing COM overhead...
- if (FAILED(hr)) return hr;
- if (FAILED(hr = IBaseFilter_QueryInterface(sg_filter, &IID_ISampleGrabber, (void**)&sg)))
- {
IBaseFilter_Release(sg_filter);
return hr;
- }
- memset(&mt, 0, sizeof(mt));
- mt.majortype = MEDIATYPE_Video;
- mt.subtype = MEDIASUBTYPE_RGB24;
- mt.formattype = FORMAT_VideoInfo;
- if (FAILED(hr = ISampleGrabber_SetMediaType(sg, &mt)) ||
FAILED(hr = CoCreateInstance(&CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
&IID_IBaseFilter, (void**)&null_filter)))
And the same here...
- {
ISampleGrabber_Release(sg);
IBaseFilter_Release(sg_filter);
return hr;
- }
- ISampleGrabber_SetBufferSamples(sg, TRUE);
- sg_inpin = sg_outpin = null_pin = NULL;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_INPUT, &sg_inpin))) goto err;
- if (FAILED(hr = get_first_pin(sg_filter, PINDIR_OUTPUT, &sg_outpin))) goto err;
- if (FAILED(hr = get_first_pin(null_filter, PINDIR_INPUT, &null_pin))) goto err;
IBaseFilter::FindPin() would be simpler. Also, it's our code; the calls shouldn't fail, which means at best they deserve an assert().
- if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, sg_filter, L"BitBucket")))
- {
I would probably stick with FAILED() error handling here (i.e. reverse it); it's consistent with the above and it keeps the indentation level low.
if (SUCCEEDED(hr = IGraphBuilder_AddFilter(detector->graph, null_filter, L"NullRenderer")))
{
if (SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, detector->cur_pin, sg_inpin)) &&
SUCCEEDED(hr = IGraphBuilder_Connect(detector->graph, sg_outpin, null_pin)) &&
At least the latter can be ConnectDirect().
SUCCEEDED(hr = seek_source(detector, SeekTime)))
{
detector->grabber = sg;
goto done;
}
IGraphBuilder_RemoveFilter(detector->graph, null_filter);
}
IGraphBuilder_RemoveFilter(detector->graph, sg_filter);
- }
+err:
- ISampleGrabber_Release(sg);
+done:
if (null_pin) IPin_Release(null_pin);
if (sg_outpin) IPin_Release(sg_outpin);
if (sg_inpin) IPin_Release(sg_inpin);
IBaseFilter_Release(null_filter);
IBaseFilter_Release(sg_filter);
return hr; }
static const IMediaDetVtbl IMediaDet_VTable =
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 010b746..afad173 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1335,7 +1335,7 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1344,10 +1344,10 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(hr == S_OK, "Got hr %#x.\n", hr); ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ref = IMediaDet_Release(detector);
@@ -1371,11 +1371,11 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0); if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME) {
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); } else ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
@@ -1396,11 +1396,11 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos)); hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
/* These still work */ hr = IMediaDet_get_Filter(detector, &unk);
@@ -1415,21 +1415,19 @@ static void test_bitmap_grab_mode(void)
/* These don't work anymore */ hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/mediadet.c | 13 +++-- dlls/qedit/tests/mediadet.c | 101 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 11498bd..6afae37 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -761,9 +761,16 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, static HRESULT WINAPI MediaDet_GetSampleGrabber(IMediaDet* iface, ISampleGrabber **ppVal) { - MediaDetImpl *This = impl_from_IMediaDet(iface); - FIXME("(%p)->(%p): not implemented!\n", This, ppVal); - return E_NOTIMPL; + MediaDetImpl *detector = impl_from_IMediaDet(iface); + + TRACE("(%p)->(%p)\n", detector, ppVal); + + if (!detector->grabber) + return E_NOINTERFACE; + + *ppVal = detector->grabber; + ISampleGrabber_AddRef(*ppVal); + return S_OK; }
static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index afad173..412d574 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -627,6 +627,39 @@ static BOOL unpack_avi_file(int id, WCHAR name[MAX_PATH]) return ret && written == size; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{ + IEnumPins *pins; + HRESULT hr; + IPin *pin; + + *out = NULL; + if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins))) + return hr; + + while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK) + { + PIN_DIRECTION dir; + + hr = IPin_QueryDirection(pin, &dir); + if (FAILED(hr)) + { + IPin_Release(pin); + IEnumPins_Release(pins); + return hr; + } + if (dir == pin_dir) + { + *out = pin; + IEnumPins_Release(pins); + return S_OK; + } + IPin_Release(pin); + } + IEnumPins_Release(pins); + return E_NOTIMPL; +} + static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename) @@ -1318,14 +1351,23 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_BYTE, &TIME_FORMAT_MEDIA_TIME }; + char *buf = malloc(640 * 480 * 3); struct testfilter testfilter; + FILTER_INFO filter_info; + IReferenceClock *clock; + IBaseFilter *filter; IMediaDet *detector; + ISampleGrabber *sg; + FILTER_STATE state; + PIN_INFO pin_info; AM_MEDIA_TYPE mt; + IMediaFilter *mf; + LONG count, size; + IPin *pin, *pin2; double duration; IUnknown *unk; unsigned i; HRESULT hr; - LONG count; ULONG ref; GUID guid; BSTR str; @@ -1336,6 +1378,8 @@ static void test_bitmap_grab_mode(void)
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); @@ -1429,10 +1473,64 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_put_CurrentStream(detector, 0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
+ /* Check the SampleGrabber */ + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = ISampleGrabber_GetConnectedMediaType(sg, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Video), "Got major type %s.\n", debugstr_guid(&mt.majortype)); + ok(IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24), "Got sub type %s.\n", debugstr_guid(&mt.subtype)); + ok(IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo), "Got format type %s.\n", debugstr_guid(&mt.formattype)); + FreeMediaType(&mt); + + hr = ISampleGrabber_GetCurrentBuffer(sg, &size, NULL); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == 640 * 480 * 3, "Got size %d.\n", size); + hr = ISampleGrabber_GetCurrentBuffer(sg, &size, (LONG*)buf); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == 640 * 480 * 3, "Got size %d.\n", size); + hr = ISampleGrabber_QueryInterface(sg, &IID_IBaseFilter, (void**)&filter); + ok(hr == S_OK, "QueryInterface for IID_IBaseFilter failed: %08x\n", hr); + ISampleGrabber_Release(sg); + + hr = IBaseFilter_QueryFilterInfo(filter, &filter_info); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(!wcscmp(filter_info.achName, L"BitBucket"), "Got name %s.\n", debugstr_w(filter_info.achName)); + IFilterGraph_Release(filter_info.pGraph); + hr = get_first_pin(filter, PINDIR_OUTPUT, &pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IBaseFilter_Release(filter); + + hr = IPin_ConnectedTo(pin, &pin2); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IPin_QueryPinInfo(pin2, &pin_info); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(pin_info.pFilter != NULL, "Got NULL filter.\n"); + IPin_Release(pin2); + IPin_Release(pin); + + hr = IBaseFilter_QueryFilterInfo(pin_info.pFilter, &filter_info); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(!wcscmp(filter_info.achName, L"NullRenderer"), "Got name %s.\n", debugstr_w(filter_info.achName)); + hr = IFilterGraph_QueryInterface(filter_info.pGraph, &IID_IMediaFilter, (void**)&mf); + ok(hr == S_OK, "QueryInterface for IID_IMediaFilter failed: %08x\n", hr); + IFilterGraph_Release(filter_info.pGraph); + IBaseFilter_Release(pin_info.pFilter); + + hr = IMediaFilter_GetState(mf, INFINITE, &state); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(state == State_Paused, "Got state %d.\n", state); + hr = IMediaFilter_GetSyncSource(mf, &clock); + ok(SUCCEEDED(hr), "Got hr %#x.\n", hr); + ok(clock == NULL, "Got non-NULL clock.\n"); + IMediaFilter_Release(mf); + /* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); ok(hr == S_OK, "Got hr %#x.\n", hr); ok(count == 1, "Got %d streams.\n", count); @@ -1441,6 +1539,7 @@ static void test_bitmap_grab_mode(void) ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); ok(!ref, "Got outstanding refcount %d.\n", ref); + free(buf); }
START_TEST(mediadet)
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 13 +++-- dlls/qedit/tests/mediadet.c | 101 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 11498bd..6afae37 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -761,9 +761,16 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, static HRESULT WINAPI MediaDet_GetSampleGrabber(IMediaDet* iface, ISampleGrabber **ppVal) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%p): not implemented!\n", This, ppVal);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- TRACE("(%p)->(%p)\n", detector, ppVal);
- if (!detector->grabber)
return E_NOINTERFACE;
- *ppVal = detector->grabber;
- ISampleGrabber_AddRef(*ppVal);
- return S_OK;
}
static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal) diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index afad173..412d574 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -627,6 +627,39 @@ static BOOL unpack_avi_file(int id, WCHAR name[MAX_PATH]) return ret && written == size; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- *out = NULL;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename) @@ -1318,14 +1351,23 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_BYTE, &TIME_FORMAT_MEDIA_TIME };
- char *buf = malloc(640 * 480 * 3); struct testfilter testfilter;
- FILTER_INFO filter_info;
- IReferenceClock *clock;
- IBaseFilter *filter; IMediaDet *detector;
- ISampleGrabber *sg;
- FILTER_STATE state;
- PIN_INFO pin_info; AM_MEDIA_TYPE mt;
- IMediaFilter *mf;
- LONG count, size;
- IPin *pin, *pin2; double duration; IUnknown *unk; unsigned i; HRESULT hr;
- LONG count; ULONG ref; GUID guid; BSTR str;
@@ -1336,6 +1378,8 @@ static void test_bitmap_grab_mode(void)
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
hr = IMediaDet_GetSampleGrabber(detector, &sg);
ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1429,10 +1473,64 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_put_CurrentStream(detector, 0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Check the SampleGrabber */
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = ISampleGrabber_GetConnectedMediaType(sg, &mt);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Video), "Got major type %s.\n", debugstr_guid(&mt.majortype));
- ok(IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24), "Got sub type %s.\n", debugstr_guid(&mt.subtype));
- ok(IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo), "Got format type %s.\n", debugstr_guid(&mt.formattype));
- FreeMediaType(&mt);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, NULL);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, (LONG*)buf);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_QueryInterface(sg, &IID_IBaseFilter, (void**)&filter);
- ok(hr == S_OK, "QueryInterface for IID_IBaseFilter failed: %08x\n", hr);
- ISampleGrabber_Release(sg);
- hr = IBaseFilter_QueryFilterInfo(filter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"BitBucket"), "Got name %s.\n", debugstr_w(filter_info.achName));
- IFilterGraph_Release(filter_info.pGraph);
- hr = get_first_pin(filter, PINDIR_OUTPUT, &pin);
Same here; you can replace this with FindPin(L"Out").
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IBaseFilter_Release(filter);
- hr = IPin_ConnectedTo(pin, &pin2);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IPin_QueryPinInfo(pin2, &pin_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(pin_info.pFilter != NULL, "Got NULL filter.\n");
- IPin_Release(pin2);
- IPin_Release(pin);
- hr = IBaseFilter_QueryFilterInfo(pin_info.pFilter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"NullRenderer"), "Got name %s.\n", debugstr_w(filter_info.achName));
- hr = IFilterGraph_QueryInterface(filter_info.pGraph, &IID_IMediaFilter, (void**)&mf);
- ok(hr == S_OK, "QueryInterface for IID_IMediaFilter failed: %08x\n", hr);
- IFilterGraph_Release(filter_info.pGraph);
- IBaseFilter_Release(pin_info.pFilter);
- hr = IMediaFilter_GetState(mf, INFINITE, &state);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(state == State_Paused, "Got state %d.\n", state);
- hr = IMediaFilter_GetSyncSource(mf, &clock);
- ok(SUCCEEDED(hr), "Got hr %#x.\n", hr);
- ok(clock == NULL, "Got non-NULL clock.\n");
- IMediaFilter_Release(mf);
- /* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); ok(hr == S_OK, "Got hr %#x.\n", hr); ok(count == 1, "Got %d streams.\n", count);
@@ -1441,6 +1539,7 @@ static void test_bitmap_grab_mode(void) ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); ok(!ref, "Got outstanding refcount %d.\n", ref);
- free(buf);
I think it's clearer to free things closer to when they're returned, unless you end up using them later in the function.
}
START_TEST(mediadet)
Can these tests be moved to before patch 2/7?
On 20/10/2020 20:15, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 13 +++-- dlls/qedit/tests/mediadet.c | 101 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 11498bd..6afae37 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -761,9 +761,16 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, static HRESULT WINAPI MediaDet_GetSampleGrabber(IMediaDet* iface, ISampleGrabber **ppVal) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%p): not implemented!\n", This, ppVal);
- return E_NOTIMPL;
MediaDetImpl *detector = impl_from_IMediaDet(iface);
TRACE("(%p)->(%p)\n", detector, ppVal);
if (!detector->grabber)
return E_NOINTERFACE;
*ppVal = detector->grabber;
ISampleGrabber_AddRef(*ppVal);
return S_OK; }
static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index afad173..412d574 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -627,6 +627,39 @@ static BOOL unpack_avi_file(int id, WCHAR name[MAX_PATH]) return ret && written == size; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- *out = NULL;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
- static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename)
@@ -1318,14 +1351,23 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_BYTE, &TIME_FORMAT_MEDIA_TIME };
- char *buf = malloc(640 * 480 * 3); struct testfilter testfilter;
- FILTER_INFO filter_info;
- IReferenceClock *clock;
- IBaseFilter *filter; IMediaDet *detector;
- ISampleGrabber *sg;
- FILTER_STATE state;
- PIN_INFO pin_info; AM_MEDIA_TYPE mt;
- IMediaFilter *mf;
- LONG count, size;
- IPin *pin, *pin2; double duration; IUnknown *unk; unsigned i; HRESULT hr;
- LONG count; ULONG ref; GUID guid; BSTR str;
@@ -1336,6 +1378,8 @@ static void test_bitmap_grab_mode(void)
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
hr = IMediaDet_GetSampleGrabber(detector, &sg);
ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1429,10 +1473,64 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_put_CurrentStream(detector, 0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Check the SampleGrabber */
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = ISampleGrabber_GetConnectedMediaType(sg, &mt);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Video), "Got major type %s.\n", debugstr_guid(&mt.majortype));
- ok(IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24), "Got sub type %s.\n", debugstr_guid(&mt.subtype));
- ok(IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo), "Got format type %s.\n", debugstr_guid(&mt.formattype));
- FreeMediaType(&mt);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, NULL);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, (LONG*)buf);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_QueryInterface(sg, &IID_IBaseFilter, (void**)&filter);
- ok(hr == S_OK, "QueryInterface for IID_IBaseFilter failed: %08x\n", hr);
- ISampleGrabber_Release(sg);
- hr = IBaseFilter_QueryFilterInfo(filter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"BitBucket"), "Got name %s.\n", debugstr_w(filter_info.achName));
- IFilterGraph_Release(filter_info.pGraph);
- hr = get_first_pin(filter, PINDIR_OUTPUT, &pin);
Same here; you can replace this with FindPin(L"Out").
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IBaseFilter_Release(filter);
- hr = IPin_ConnectedTo(pin, &pin2);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IPin_QueryPinInfo(pin2, &pin_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(pin_info.pFilter != NULL, "Got NULL filter.\n");
- IPin_Release(pin2);
- IPin_Release(pin);
- hr = IBaseFilter_QueryFilterInfo(pin_info.pFilter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"NullRenderer"), "Got name %s.\n", debugstr_w(filter_info.achName));
- hr = IFilterGraph_QueryInterface(filter_info.pGraph, &IID_IMediaFilter, (void**)&mf);
- ok(hr == S_OK, "QueryInterface for IID_IMediaFilter failed: %08x\n", hr);
- IFilterGraph_Release(filter_info.pGraph);
- IBaseFilter_Release(pin_info.pFilter);
- hr = IMediaFilter_GetState(mf, INFINITE, &state);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(state == State_Paused, "Got state %d.\n", state);
- hr = IMediaFilter_GetSyncSource(mf, &clock);
- ok(SUCCEEDED(hr), "Got hr %#x.\n", hr);
- ok(clock == NULL, "Got non-NULL clock.\n");
- IMediaFilter_Release(mf);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); ok(hr == S_OK, "Got hr %#x.\n", hr); ok(count == 1, "Got %d streams.\n", count);
@@ -1441,6 +1539,7 @@ static void test_bitmap_grab_mode(void) ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); ok(!ref, "Got outstanding refcount %d.\n", ref);
- free(buf);
I think it's clearer to free things closer to when they're returned, unless you end up using them later in the function.
I'll use "buf" in later patches (GetBitmapBits/WriteBitmapBits). The only reason it's malloc'd is because it's quite large and I didn't want to allocate such a large array on the stack.
}
START_TEST(mediadet)
Can these tests be moved to before patch 2/7?
They can, but I'll probably have to use a goto if the Sample Grabber can't be retrieved (as it is in Wine before it's implemented) to avoid a pointless identation back-and-forth between patches. All the further tests depend on it, so it would just crash on Wine. Are you ok with the goto?
On 10/21/20 7:51 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 20:15, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/mediadet.c | 13 +++-- dlls/qedit/tests/mediadet.c | 101 +++++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 11498bd..6afae37 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -761,9 +761,16 @@ static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, static HRESULT WINAPI MediaDet_GetSampleGrabber(IMediaDet* iface, ISampleGrabber **ppVal) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%p): not implemented!\n", This, ppVal);
- return E_NOTIMPL;
MediaDetImpl *detector = impl_from_IMediaDet(iface);
TRACE("(%p)->(%p)\n", detector, ppVal);
if (!detector->grabber)
return E_NOINTERFACE;
*ppVal = detector->grabber;
ISampleGrabber_AddRef(*ppVal);
return S_OK; }
static HRESULT WINAPI MediaDet_get_FrameRate(IMediaDet* iface, double *pVal)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index afad173..412d574 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -627,6 +627,39 @@ static BOOL unpack_avi_file(int id, WCHAR name[MAX_PATH]) return ret && written == size; }
+static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin **out) +{
- IEnumPins *pins;
- HRESULT hr;
- IPin *pin;
- *out = NULL;
- if (FAILED(hr = IBaseFilter_EnumPins(filter, &pins)))
return hr;
- while (IEnumPins_Next(pins, 1, &pin, NULL) == S_OK)
- {
PIN_DIRECTION dir;
hr = IPin_QueryDirection(pin, &dir);
if (FAILED(hr))
{
IPin_Release(pin);
IEnumPins_Release(pins);
return hr;
}
if (dir == pin_dir)
{
*out = pin;
IEnumPins_Release(pins);
return S_OK;
}
IPin_Release(pin);
- }
- IEnumPins_Release(pins);
- return E_NOTIMPL;
+}
- static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename)
@@ -1318,14 +1351,23 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_BYTE, &TIME_FORMAT_MEDIA_TIME };
- char *buf = malloc(640 * 480 * 3); struct testfilter testfilter;
- FILTER_INFO filter_info;
- IReferenceClock *clock;
- IBaseFilter *filter; IMediaDet *detector;
- ISampleGrabber *sg;
- FILTER_STATE state;
- PIN_INFO pin_info; AM_MEDIA_TYPE mt;
- IMediaFilter *mf;
- LONG count, size;
- IPin *pin, *pin2; double duration; IUnknown *unk; unsigned i; HRESULT hr;
- LONG count; ULONG ref; GUID guid; BSTR str;
@@ -1336,6 +1378,8 @@ static void test_bitmap_grab_mode(void)
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
hr = IMediaDet_GetSampleGrabber(detector, &sg);
ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter);
@@ -1429,10 +1473,64 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_put_CurrentStream(detector, 0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Check the SampleGrabber */
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = ISampleGrabber_GetConnectedMediaType(sg, &mt);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(IsEqualGUID(&mt.majortype, &MEDIATYPE_Video), "Got major type %s.\n", debugstr_guid(&mt.majortype));
- ok(IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24), "Got sub type %s.\n", debugstr_guid(&mt.subtype));
- ok(IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo), "Got format type %s.\n", debugstr_guid(&mt.formattype));
- FreeMediaType(&mt);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, NULL);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_GetCurrentBuffer(sg, &size, (LONG*)buf);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(size == 640 * 480 * 3, "Got size %d.\n", size);
- hr = ISampleGrabber_QueryInterface(sg, &IID_IBaseFilter, (void**)&filter);
- ok(hr == S_OK, "QueryInterface for IID_IBaseFilter failed: %08x\n", hr);
- ISampleGrabber_Release(sg);
- hr = IBaseFilter_QueryFilterInfo(filter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"BitBucket"), "Got name %s.\n", debugstr_w(filter_info.achName));
- IFilterGraph_Release(filter_info.pGraph);
- hr = get_first_pin(filter, PINDIR_OUTPUT, &pin);
Same here; you can replace this with FindPin(L"Out").
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IBaseFilter_Release(filter);
- hr = IPin_ConnectedTo(pin, &pin2);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IPin_QueryPinInfo(pin2, &pin_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(pin_info.pFilter != NULL, "Got NULL filter.\n");
- IPin_Release(pin2);
- IPin_Release(pin);
- hr = IBaseFilter_QueryFilterInfo(pin_info.pFilter, &filter_info);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(!wcscmp(filter_info.achName, L"NullRenderer"), "Got name %s.\n", debugstr_w(filter_info.achName));
- hr = IFilterGraph_QueryInterface(filter_info.pGraph, &IID_IMediaFilter, (void**)&mf);
- ok(hr == S_OK, "QueryInterface for IID_IMediaFilter failed: %08x\n", hr);
- IFilterGraph_Release(filter_info.pGraph);
- IBaseFilter_Release(pin_info.pFilter);
- hr = IMediaFilter_GetState(mf, INFINITE, &state);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(state == State_Paused, "Got state %d.\n", state);
- hr = IMediaFilter_GetSyncSource(mf, &clock);
- ok(SUCCEEDED(hr), "Got hr %#x.\n", hr);
- ok(clock == NULL, "Got non-NULL clock.\n");
- IMediaFilter_Release(mf);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_GetSampleGrabber(detector, &sg);
- ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); ok(hr == S_OK, "Got hr %#x.\n", hr); ok(count == 1, "Got %d streams.\n", count);
@@ -1441,6 +1539,7 @@ static void test_bitmap_grab_mode(void) ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface); ok(!ref, "Got outstanding refcount %d.\n", ref);
- free(buf);
I think it's clearer to free things closer to when they're returned, unless you end up using them later in the function.
I'll use "buf" in later patches (GetBitmapBits/WriteBitmapBits). The only reason it's malloc'd is because it's quite large and I didn't want to allocate such a large array on the stack.
}
START_TEST(mediadet)
Can these tests be moved to before patch 2/7?
They can, but I'll probably have to use a goto if the Sample Grabber can't be retrieved (as it is in Wine before it's implemented) to avoid a pointless identation back-and-forth between patches. All the further tests depend on it, so it would just crash on Wine. Are you ok with the goto?
Sure, temporarily skipping tests is fine.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Note the stretching algorithm used here to verify that it's correct: please read the comments for the next patch in the series for more details. Short story is: Windows doesn't use gdi32 to do the scaling, as none of the APIs produced the correct scaling on Windows.
dlls/qedit/tests/mediadet.c | 210 +++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 1 deletion(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 412d574..98372b6 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -660,6 +660,157 @@ static HRESULT get_first_pin(IBaseFilter *filter, PIN_DIRECTION pin_dir, IPin ** return E_NOTIMPL; }
+static void stretch_line(BYTE *dst, ULONG dst_width, BYTE *src, ULONG src_width) +{ + ULONG ratio, rem, drift, i = dst_width; + + if (dst_width < src_width) + { + ratio = src_width / dst_width; + rem = src_width % dst_width; + drift = 0; + while (i--) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst += 3; + src += ratio * 3; + if (drift < rem) + { + src += 3; + drift += dst_width; + } + drift -= rem; + } + } + else if (dst_width > src_width) + { + drift = dst_width - 1; + while (i--) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst += 3; + if (drift < src_width) + { + drift += dst_width; + src += 3; + } + drift -= src_width; + } + } + else + { + memcpy(dst, src, dst_width * 3); + dst += dst_width * 3; + } + + /* Fill the stride padding with zeros */ + i = (dst_width * 3) % 4; + if (i) + for (i = 4 - i; i--;) + *dst++ = 0; +} + +static void stretch_to_bitmap(BYTE *dst, ULONG dst_width, ULONG dst_height, BYTE *src, ULONG src_width, ULONG src_height) +{ + ULONG dst_stride = (dst_width * 3 + 3) & ~3; + ULONG src_stride = (src_width * 3 + 3) & ~3; + ULONG ratio, rem, drift, i = dst_height; + BYTE *dup; + + if (dst_height < src_height) + { + ratio = src_height / dst_height; + rem = src_height % dst_height; + drift = 0; + while (i--) + { + stretch_line(dst, dst_width, src, src_width); + dst += dst_stride; + src += ratio * src_stride; + if (drift < rem) + { + src += src_stride; + drift += dst_height; + } + drift -= rem; + } + } + else + { + drift = dst_height - 1; + while (i--) + { + stretch_line(dst, dst_width, src, src_width); + dup = dst; + dst += dst_stride; + while (drift >= src_height && i--) + { + memcpy(dst, dup, dst_stride); + dst += dst_stride; + drift -= src_height; + } + drift += dst_height - src_height; + src += src_stride; + } + } +} + +#define check_bitmap(buffer, width, height, seek_time) check_bitmap_(__LINE__, buffer, width, height, seek_time) +static void check_bitmap_(unsigned line, void *buffer, LONG width, LONG height, double seek_time) +{ + DWORD fill = (DWORD)(seek_time * 1000.0) ^ 0xccaabb; + BYTE *p = (BYTE*)buffer + sizeof(BITMAPINFOHEADER); + DWORD line_width = (width * 3 + 3) & ~3; + BYTE *img, *src = malloc(640 * 480 * 3); + const BITMAPINFOHEADER *h = buffer; + unsigned i, j, v; + + ok_(__FILE__,line)(h->biSize == sizeof(BITMAPINFOHEADER), "Got biSize %u.\n", h->biSize); + ok_(__FILE__,line)(h->biWidth == width, "Got biWidth %d.\n", h->biWidth); + ok_(__FILE__,line)(h->biHeight == height, "Got biHeight %d.\n", h->biHeight); + ok_(__FILE__,line)(h->biPlanes == 1, "Got biPlanes %d.\n", h->biPlanes); + ok_(__FILE__,line)(h->biBitCount == 24, "Got biBitCount %d.\n", h->biBitCount); + ok_(__FILE__,line)(h->biCompression == BI_RGB, "Got biCompression %d.\n", h->biCompression); + ok_(__FILE__,line)(h->biXPelsPerMeter == 0, "Got biXPelsPerMeter %d.\n", h->biXPelsPerMeter); + ok_(__FILE__,line)(h->biYPelsPerMeter == 0, "Got biYPelsPerMeter %d.\n", h->biYPelsPerMeter); + ok_(__FILE__,line)(h->biClrUsed == 0, "Got biClrUsed %d.\n", h->biClrUsed); + ok_(__FILE__,line)(h->biClrImportant == 0, "Got biClrImportant %d.\n", h->biClrImportant); + + /* The lines are reversed since our source was top-down */ + src = malloc(640 * 480 * 3); + img = src; + for (j = 640 * 480 * 3; j != 0;) + { + j -= 640 * 3; + v = j; + for (i = 0; i < 640 * 3; i += 3, v += 3) + { + img[i] = fill ^ v; + img[i + 1] = fill >> 8 ^ v; + img[i + 2] = fill >> 16 ^ v; + } + img += 640 * 3; + } + + img = malloc(line_width * height); + stretch_to_bitmap(img, width, height, src, 640, 480); + free(src); + + for (i = 0; i < line_width * height; i += 3) + if (p[i] != img[i] || p[i + 1] != img[i + 1] || p[i + 2] != img[i + 2]) + { + ok_(__FILE__,line)(0, "Wrong bitmap data at offset %u (got 0x%06x, expected 0x%06x).\n", + p + i - (BYTE*)buffer, p[i] | p[i + 1] << 8 | p[i + 2] << 16, + img[i] | img[i + 1] << 8 | img[i + 2] << 16); + break; + } + free(img); +} + static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename) @@ -1351,7 +1502,7 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_BYTE, &TIME_FORMAT_MEDIA_TIME }; - char *buf = malloc(640 * 480 * 3); + char *buf = malloc(sizeof(BITMAPINFOHEADER) + 960 * 720 * 3); struct testfilter testfilter; FILTER_INFO filter_info; IReferenceClock *clock; @@ -1378,6 +1529,9 @@ static void test_bitmap_grab_mode(void)
hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
@@ -1525,6 +1679,42 @@ static void test_bitmap_grab_mode(void) ok(clock == NULL, "Got non-NULL clock.\n"); IMediaFilter_Release(mf);
+ /* despite what MSDN states, size must be properly supplied on newer Windows versions */ + hr = IMediaDet_GetBitmapBits(detector, 0.0, NULL, NULL, 640, 480); + todo_wine ok(hr == E_POINTER, "Got hr %#x.\n", hr); + size = -1; + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); + todo_wine ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); + size = 640 * 480 * 3; + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); + todo_wine ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); + ok(size == 640 * 480 * 3, "Got size %d.\n", size); + size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; + hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, buf, 640, 480); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_GetBitmapBits(detector, 2.5, &size, buf, 640, 480); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap(buf, 640, 480, 2.5); + + /* GetBitmapBits/WriteBitmapBits can scale the image */ + size = sizeof(BITMAPINFOHEADER) + 960 * 720 * 3; + hr = IMediaDet_GetBitmapBits(detector, 1.5, &size, buf, 131, 151); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == sizeof(BITMAPINFOHEADER) + 960 * 720 * 3, "Got size %d.\n", size); + todo_wine check_bitmap(buf, 131, 151, 1.5); + hr = IMediaDet_GetBitmapBits(detector, 4.0, NULL, buf, 503, 79); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap(buf, 503, 79, 4.0); + hr = IMediaDet_GetBitmapBits(detector, 1.52, NULL, buf, 139, 487); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap(buf, 139, 487, 1.52); + hr = IMediaDet_GetBitmapBits(detector, 2.12, NULL, buf, 640, 641); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap(buf, 640, 641, 2.12); + hr = IMediaDet_GetBitmapBits(detector, 3.25, NULL, buf, 960, 720); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap(buf, 960, 720, 3.25); + /* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); @@ -1535,6 +1725,24 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr); ok(count == 1, "Got %d streams.\n", count);
+ /* GetBitmapBits enables it only if it retrieves the image */ + testfilter.bitmap_grab_mode = TRUE; + hr = IMediaDet_GetBitmapBits(detector, 1.25, &size, NULL, 640, 480); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); + hr = IMediaDet_get_OutputStreams(detector, &count); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(count == 1, "Got %d streams.\n", count); + + hr = IMediaDet_GetBitmapBits(detector, 1.25, NULL, buf, 640, 480); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + if (SUCCEEDED(hr)) ISampleGrabber_Release(sg); + hr = IMediaDet_get_OutputStreams(detector, &count); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
The stretching algorithm that Windows uses does not match any mode on StretchBlt or StretchDIBits, which is why the actual algorithm used by these APIs was implemented.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
I've literally tested every mode (on SetStretchBltMode) and operation mode and none of them were correct on Windows, even if Wine's implementation is not perfect. So clearly Windows doesn't use gdi32 to do the scaling here. It was quite a pain to discover the exact algorithm used, but this passes all tests on all Windows versions on the testbot.
dlls/qedit/mediadet.c | 191 +++++++++++++++++++++++++++++++++++- dlls/qedit/tests/mediadet.c | 57 ++++++----- 2 files changed, 222 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 6afae37..f874095 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -324,6 +324,60 @@ done: return hr; }
+static void stretch_line(BYTE *dst, ULONG dst_width, BYTE *src, ULONG src_width) +{ + ULONG ratio, rem, drift, i = dst_width; + + if (dst_width < src_width) + { + ratio = src_width / dst_width; + rem = src_width % dst_width; + drift = 0; + while (i--) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst += 3; + src += ratio * 3; + if (drift < rem) + { + src += 3; + drift += dst_width; + } + drift -= rem; + } + } + else if (dst_width > src_width) + { + drift = dst_width - 1; + while (i--) + { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst += 3; + if (drift < src_width) + { + drift += dst_width; + src += 3; + } + drift -= src_width; + } + } + else + { + memcpy(dst, src, dst_width * 3); + dst += dst_width * 3; + } + + /* Fill the stride padding with zeros */ + i = (dst_width * 3) % 4; + if (i) + for (i = 4 - i; i--;) + *dst++ = 0; +} + /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) { @@ -727,10 +781,139 @@ static HRESULT WINAPI MediaDet_GetBitmapBits(IMediaDet* iface, LONG *pBufferSize, char *pBuffer, LONG Width, LONG Height) { - MediaDetImpl *This = impl_from_IMediaDet(iface); - FIXME("(%p)->(%f %p %p %d %d): not implemented!\n", This, StreamTime, pBufferSize, pBuffer, - Width, Height); - return E_NOTIMPL; + MediaDetImpl *detector = impl_from_IMediaDet(iface); + LONG ratio, rem, stride = (Width * 3 + 3) & ~3; + LONG src_x, src_y, src_stride, size, buf_size; + ULONG src_width, src_height, drift, i; + BITMAPINFOHEADER hdr = { 0 }; + BYTE *buf, *dup, *dst, *src; + VIDEOINFOHEADER *info; + AM_MEDIA_TYPE mt; + HRESULT hr; + + TRACE("(%p)->(%f %p %p %d %d)\n", detector, StreamTime, pBufferSize, pBuffer, Width, Height); + + if (!pBuffer && !pBufferSize) return E_POINTER; + if (Width < 0 || Height < 0) return E_INVALIDARG; + if (!pBuffer) + { + *pBufferSize = sizeof(BITMAPINFOHEADER) + stride * Height; + return S_OK; + } + if (StreamTime < 0.0) return E_INVALIDARG; + if (pBufferSize) + { + if (*pBufferSize < 0) + return E_INVALIDARG; + if (*pBufferSize < sizeof(BITMAPINFOHEADER) + stride * Height) + return E_OUTOFMEMORY; + } + + if (detector->grabber) + hr = seek_source(detector, StreamTime); + else + hr = IMediaDet_EnterBitmapGrabMode(&detector->IMediaDet_iface, StreamTime); + if (FAILED(hr)) + return hr; + + hr = ISampleGrabber_GetConnectedMediaType(detector->grabber, &mt); + info = (VIDEOINFOHEADER*)mt.pbFormat; + if (FAILED(hr) || + !IsEqualGUID(&mt.majortype, &MEDIATYPE_Video) || + !IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24) || + !IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo) || + mt.cbFormat != sizeof(VIDEOINFOHEADER) || + info->bmiHeader.biSize != sizeof(BITMAPINFOHEADER) || + info->bmiHeader.biWidth <= 0 || + info->bmiHeader.biHeight == 0 || + info->bmiHeader.biPlanes != 1 || + info->bmiHeader.biBitCount != 24 || + info->bmiHeader.biCompression != BI_RGB) + return VFW_E_INVALID_MEDIA_TYPE; + + src_x = src_y = 0; + src_width = info->bmiHeader.biWidth; + src_height = abs(info->bmiHeader.biHeight); + src_stride = (src_width * 3 + 3) & ~3; + buf_size = src_stride * src_height; + if (!IsRectEmpty(&info->rcTarget)) + { + src_x = max(info->rcTarget.left, 0); + src_y = max(info->rcTarget.top, 0); + src_width = min(info->rcTarget.right - src_x, src_width); + src_height = min(info->rcTarget.bottom - src_y, src_height); + } + if (info->bmiHeader.biHeight < 0) + { + src_y += info->bmiHeader.biHeight + 1; + src_stride = -src_stride; + } + FreeMediaType(&mt); + + if (!(buf = HeapAlloc(GetProcessHeap(), 0, buf_size))) + return E_OUTOFMEMORY; + size = buf_size; + hr = ISampleGrabber_GetCurrentBuffer(detector->grabber, &size, (LONG*)buf); + if (FAILED(hr)) goto err; + if (size != buf_size) + { + hr = E_UNEXPECTED; + goto err; + } + + hdr.biSize = sizeof(BITMAPINFOHEADER); + hdr.biWidth = Width; + hdr.biHeight = Height; + hdr.biPlanes = 1; + hdr.biBitCount = 24; + hdr.biCompression = BI_RGB; + memcpy(pBuffer, &hdr, sizeof(BITMAPINFOHEADER)); + + /* Copy and potentially stretch the image (differently than StretchBlt) */ + dst = (BYTE*)pBuffer + sizeof(BITMAPINFOHEADER); + src = buf + src_x * 3 + src_y * src_stride; + i = Height; + if (Height < src_height) + { + ratio = src_height / Height; + rem = src_height % Height; + drift = 0; + while (i--) + { + stretch_line(dst, Width, src, src_width); + dst += stride; + src += ratio * src_stride; + if (drift < rem) + { + src += src_stride; + drift += Height; + } + drift -= rem; + } + } + else + { + drift = Height - 1; + while (i--) + { + stretch_line(dst, Width, src, src_width); + dup = dst; + dst += stride; + while (drift >= src_height && i--) + { + memcpy(dst, dup, stride); + dst += stride; + drift -= src_height; + } + drift += Height - src_height; + src += src_stride; + } + } + + hr = S_OK; +err: + HeapFree(GetProcessHeap(), 0, buf); + return hr; }
static HRESULT WINAPI MediaDet_WriteBitmapBits(IMediaDet* iface, diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 98372b6..e00226a 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1531,10 +1531,23 @@ static void test_bitmap_grab_mode(void) ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
+ /* Lines are rounded up to the bitmap stride */ + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 480); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == sizeof(BITMAPINFOHEADER) + 640 * 480 * 3, "Got size %d.\n", size); + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == sizeof(BITMAPINFOHEADER), "Got size %d.\n", size); + hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, NULL, 151, 131); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(size == sizeof(BITMAPINFOHEADER) + ((151 * 3 + 3) & ~3) * 131, "Got size %d.\n", size); + hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, -59, -79); + ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); + /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); testfilter.bitmap_grab_mode = TRUE; @@ -1681,39 +1694,39 @@ static void test_bitmap_grab_mode(void)
/* despite what MSDN states, size must be properly supplied on newer Windows versions */ hr = IMediaDet_GetBitmapBits(detector, 0.0, NULL, NULL, 640, 480); - todo_wine ok(hr == E_POINTER, "Got hr %#x.\n", hr); + ok(hr == E_POINTER, "Got hr %#x.\n", hr); size = -1; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); - todo_wine ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); size = 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); - todo_wine ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); + ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); ok(size == 640 * 480 * 3, "Got size %d.\n", size); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, buf, 640, 480); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetBitmapBits(detector, 2.5, &size, buf, 640, 480); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap(buf, 640, 480, 2.5); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap(buf, 640, 480, 2.5);
/* GetBitmapBits/WriteBitmapBits can scale the image */ size = sizeof(BITMAPINFOHEADER) + 960 * 720 * 3; hr = IMediaDet_GetBitmapBits(detector, 1.5, &size, buf, 131, 151); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); ok(size == sizeof(BITMAPINFOHEADER) + 960 * 720 * 3, "Got size %d.\n", size); - todo_wine check_bitmap(buf, 131, 151, 1.5); + check_bitmap(buf, 131, 151, 1.5); hr = IMediaDet_GetBitmapBits(detector, 4.0, NULL, buf, 503, 79); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap(buf, 503, 79, 4.0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap(buf, 503, 79, 4.0); hr = IMediaDet_GetBitmapBits(detector, 1.52, NULL, buf, 139, 487); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap(buf, 139, 487, 1.52); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap(buf, 139, 487, 1.52); hr = IMediaDet_GetBitmapBits(detector, 2.12, NULL, buf, 640, 641); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap(buf, 640, 641, 2.12); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap(buf, 640, 641, 2.12); hr = IMediaDet_GetBitmapBits(detector, 3.25, NULL, buf, 960, 720); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap(buf, 960, 720, 3.25); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap(buf, 960, 720, 3.25);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; @@ -1728,7 +1741,7 @@ static void test_bitmap_grab_mode(void) /* GetBitmapBits enables it only if it retrieves the image */ testfilter.bitmap_grab_mode = TRUE; hr = IMediaDet_GetBitmapBits(detector, 1.25, &size, NULL, 640, 480); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); @@ -1736,12 +1749,12 @@ static void test_bitmap_grab_mode(void) ok(count == 1, "Got %d streams.\n", count);
hr = IMediaDet_GetBitmapBits(detector, 1.25, NULL, buf, 640, 480); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - if (SUCCEEDED(hr)) ISampleGrabber_Release(sg); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ISampleGrabber_Release(sg); hr = IMediaDet_get_OutputStreams(detector, &count); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref);
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
The stretching algorithm that Windows uses does not match any mode on StretchBlt or StretchDIBits, which is why the actual algorithm used by these APIs was implemented.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
I've literally tested every mode (on SetStretchBltMode) and operation mode and none of them were correct on Windows, even if Wine's implementation is not perfect. So clearly Windows doesn't use gdi32 to do the scaling here. It was quite a pain to discover the exact algorithm used, but this passes all tests on all Windows versions on the testbot.
I appreciate the attention to detail, and the effort put into reverse-engineering this algorithm. However, pixel-perfect accuracy is not really a goal of Wine, especially not when we're relying on libraries like GStreamer to do decoding and format conversions, and in this case I don't think that it's worth writing or maintaining a new stretching algorithm. I would instead just use StretchDIBits() with the closest stretch blit mode.
dlls/qedit/mediadet.c | 191 +++++++++++++++++++++++++++++++++++- dlls/qedit/tests/mediadet.c | 57 ++++++----- 2 files changed, 222 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 6afae37..f874095 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -324,6 +324,60 @@ done: return hr; }
+static void stretch_line(BYTE *dst, ULONG dst_width, BYTE *src, ULONG src_width) +{
- ULONG ratio, rem, drift, i = dst_width;
- if (dst_width < src_width)
- {
ratio = src_width / dst_width;
rem = src_width % dst_width;
drift = 0;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
src += ratio * 3;
if (drift < rem)
{
src += 3;
drift += dst_width;
}
drift -= rem;
}
- }
- else if (dst_width > src_width)
- {
drift = dst_width - 1;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
if (drift < src_width)
{
drift += dst_width;
src += 3;
}
drift -= src_width;
}
- }
- else
- {
memcpy(dst, src, dst_width * 3);
dst += dst_width * 3;
- }
- /* Fill the stride padding with zeros */
- i = (dst_width * 3) % 4;
- if (i)
for (i = 4 - i; i--;)
*dst++ = 0;
+}
/* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) { @@ -727,10 +781,139 @@ static HRESULT WINAPI MediaDet_GetBitmapBits(IMediaDet* iface, LONG *pBufferSize, char *pBuffer, LONG Width, LONG Height) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f %p %p %d %d): not implemented!\n", This, StreamTime, pBufferSize, pBuffer,
Width, Height);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- LONG ratio, rem, stride = (Width * 3 + 3) & ~3;
- LONG src_x, src_y, src_stride, size, buf_size;
- ULONG src_width, src_height, drift, i;
- BITMAPINFOHEADER hdr = { 0 };
- BYTE *buf, *dup, *dst, *src;
- VIDEOINFOHEADER *info;
- AM_MEDIA_TYPE mt;
- HRESULT hr;
- TRACE("(%p)->(%f %p %p %d %d)\n", detector, StreamTime, pBufferSize, pBuffer, Width, Height);
- if (!pBuffer && !pBufferSize) return E_POINTER;
- if (Width < 0 || Height < 0) return E_INVALIDARG;
- if (!pBuffer)
- {
*pBufferSize = sizeof(BITMAPINFOHEADER) + stride * Height;
return S_OK;
- }
- if (StreamTime < 0.0) return E_INVALIDARG;
- if (pBufferSize)
- {
if (*pBufferSize < 0)
return E_INVALIDARG;
if (*pBufferSize < sizeof(BITMAPINFOHEADER) + stride * Height)
return E_OUTOFMEMORY;
- }
- if (detector->grabber)
hr = seek_source(detector, StreamTime);
- else
hr = IMediaDet_EnterBitmapGrabMode(&detector->IMediaDet_iface, StreamTime);
- if (FAILED(hr))
return hr;
- hr = ISampleGrabber_GetConnectedMediaType(detector->grabber, &mt);
- info = (VIDEOINFOHEADER*)mt.pbFormat;
- if (FAILED(hr) ||
!IsEqualGUID(&mt.majortype, &MEDIATYPE_Video) ||
!IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24) ||
!IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo) ||
mt.cbFormat != sizeof(VIDEOINFOHEADER) ||
info->bmiHeader.biSize != sizeof(BITMAPINFOHEADER) ||
info->bmiHeader.biWidth <= 0 ||
info->bmiHeader.biHeight == 0 ||
info->bmiHeader.biPlanes != 1 ||
info->bmiHeader.biBitCount != 24 ||
info->bmiHeader.biCompression != BI_RGB)
return VFW_E_INVALID_MEDIA_TYPE;
This may leak "mt.pbFormat". Moreover, can some of these conditions even happen? And are the others worth checking for?
- src_x = src_y = 0;
- src_width = info->bmiHeader.biWidth;
- src_height = abs(info->bmiHeader.biHeight);
- src_stride = (src_width * 3 + 3) & ~3;
- buf_size = src_stride * src_height;
- if (!IsRectEmpty(&info->rcTarget))
- {
src_x = max(info->rcTarget.left, 0);
src_y = max(info->rcTarget.top, 0);
src_width = min(info->rcTarget.right - src_x, src_width);
src_height = min(info->rcTarget.bottom - src_y, src_height);
- }
- if (info->bmiHeader.biHeight < 0)
- {
src_y += info->bmiHeader.biHeight + 1;
src_stride = -src_stride;
- }
- FreeMediaType(&mt);
- if (!(buf = HeapAlloc(GetProcessHeap(), 0, buf_size)))
return E_OUTOFMEMORY;
- size = buf_size;
- hr = ISampleGrabber_GetCurrentBuffer(detector->grabber, &size, (LONG*)buf);
- if (FAILED(hr)) goto err;
- if (size != buf_size)
- {
hr = E_UNEXPECTED;
goto err;
- }
- hdr.biSize = sizeof(BITMAPINFOHEADER);
- hdr.biWidth = Width;
- hdr.biHeight = Height;
- hdr.biPlanes = 1;
- hdr.biBitCount = 24;
- hdr.biCompression = BI_RGB;
- memcpy(pBuffer, &hdr, sizeof(BITMAPINFOHEADER));
- /* Copy and potentially stretch the image (differently than StretchBlt) */
- dst = (BYTE*)pBuffer + sizeof(BITMAPINFOHEADER);
- src = buf + src_x * 3 + src_y * src_stride;
- i = Height;
- if (Height < src_height)
- {
ratio = src_height / Height;
rem = src_height % Height;
drift = 0;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dst += stride;
src += ratio * src_stride;
if (drift < rem)
{
src += src_stride;
drift += Height;
}
drift -= rem;
}
- }
- else
- {
drift = Height - 1;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dup = dst;
dst += stride;
while (drift >= src_height && i--)
{
memcpy(dst, dup, stride);
dst += stride;
drift -= src_height;
}
drift += Height - src_height;
src += src_stride;
}
- }
- hr = S_OK;
+err:
- HeapFree(GetProcessHeap(), 0, buf);
- return hr;
}
static HRESULT WINAPI MediaDet_WriteBitmapBits(IMediaDet* iface, diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 98372b6..e00226a 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1531,10 +1531,23 @@ static void test_bitmap_grab_mode(void) ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* Lines are rounded up to the bitmap stride */
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 480);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + 640 * 480 * 3, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 0);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER), "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, NULL, 151, 131);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + ((151 * 3 + 3) & ~3) * 131, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, -59, -79);
ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); testfilter.bitmap_grab_mode = TRUE;
@@ -1681,39 +1694,39 @@ static void test_bitmap_grab_mode(void)
/* despite what MSDN states, size must be properly supplied on newer Windows versions */ hr = IMediaDet_GetBitmapBits(detector, 0.0, NULL, NULL, 640, 480);
- todo_wine ok(hr == E_POINTER, "Got hr %#x.\n", hr);
- ok(hr == E_POINTER, "Got hr %#x.\n", hr); size = -1; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); size = 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); ok(size == 640 * 480 * 3, "Got size %d.\n", size); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetBitmapBits(detector, 2.5, &size, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 480, 2.5);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 640, 480, 2.5);
/* GetBitmapBits/WriteBitmapBits can scale the image */ size = sizeof(BITMAPINFOHEADER) + 960 * 720 * 3; hr = IMediaDet_GetBitmapBits(detector, 1.5, &size, buf, 131, 151);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(size == sizeof(BITMAPINFOHEADER) + 960 * 720 * 3, "Got size %d.\n", size);
- todo_wine check_bitmap(buf, 131, 151, 1.5);
- check_bitmap(buf, 131, 151, 1.5); hr = IMediaDet_GetBitmapBits(detector, 4.0, NULL, buf, 503, 79);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 503, 79, 4.0);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 503, 79, 4.0); hr = IMediaDet_GetBitmapBits(detector, 1.52, NULL, buf, 139, 487);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 139, 487, 1.52);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 139, 487, 1.52); hr = IMediaDet_GetBitmapBits(detector, 2.12, NULL, buf, 640, 641);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 641, 2.12);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 640, 641, 2.12); hr = IMediaDet_GetBitmapBits(detector, 3.25, NULL, buf, 960, 720);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 960, 720, 3.25);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 960, 720, 3.25);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
@@ -1728,7 +1741,7 @@ static void test_bitmap_grab_mode(void) /* GetBitmapBits enables it only if it retrieves the image */ testfilter.bitmap_grab_mode = TRUE; hr = IMediaDet_GetBitmapBits(detector, 1.25, &size, NULL, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count);
@@ -1736,12 +1749,12 @@ static void test_bitmap_grab_mode(void) ok(count == 1, "Got %d streams.\n", count);
hr = IMediaDet_GetBitmapBits(detector, 1.25, NULL, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) ISampleGrabber_Release(sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ISampleGrabber_Release(sg); hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref);
On 20/10/2020 20:27, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
The stretching algorithm that Windows uses does not match any mode on StretchBlt or StretchDIBits, which is why the actual algorithm used by these APIs was implemented.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
I've literally tested every mode (on SetStretchBltMode) and operation mode and none of them were correct on Windows, even if Wine's implementation is not perfect. So clearly Windows doesn't use gdi32 to do the scaling here. It was quite a pain to discover the exact algorithm used, but this passes all tests on all Windows versions on the testbot.
I appreciate the attention to detail, and the effort put into reverse-engineering this algorithm. However, pixel-perfect accuracy is not really a goal of Wine, especially not when we're relying on libraries like GStreamer to do decoding and format conversions, and in this case I don't think that it's worth writing or maintaining a new stretching algorithm. I would instead just use StretchDIBits() with the closest stretch blit mode.
Hmm, can we please keep it this time? I have several reasons of course, but I'd also like to avoid feel like I wasted all that effort for nothing. :-)
I think there might be a reason Microsoft used a different stretching than gdi32, and since I've already done it... it's not that complicated either, it's quite a simple algorithm.
More importantly, if we don't have an exact stretch, we won't be able to test it at all, not even on Windows. It's not a question of todo_wine, we won't be able to test it *at all*.
I'd have to remove all the stretching tests on the bitmap data. This was actually my main motivator to reverse engineer it, after all. Are you sure that's a good idea, seeing as I already uncovered it now? (so there's no extra effort involved, because it passes on *all* Windows versions, from XP to the latest Windows 10)
dlls/qedit/mediadet.c | 191 +++++++++++++++++++++++++++++++++++- dlls/qedit/tests/mediadet.c | 57 ++++++----- 2 files changed, 222 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 6afae37..f874095 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -324,6 +324,60 @@ done: return hr; }
+static void stretch_line(BYTE *dst, ULONG dst_width, BYTE *src, ULONG src_width) +{
- ULONG ratio, rem, drift, i = dst_width;
- if (dst_width < src_width)
- {
ratio = src_width / dst_width;
rem = src_width % dst_width;
drift = 0;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
src += ratio * 3;
if (drift < rem)
{
src += 3;
drift += dst_width;
}
drift -= rem;
}
- }
- else if (dst_width > src_width)
- {
drift = dst_width - 1;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
if (drift < src_width)
{
drift += dst_width;
src += 3;
}
drift -= src_width;
}
- }
- else
- {
memcpy(dst, src, dst_width * 3);
dst += dst_width * 3;
- }
- /* Fill the stride padding with zeros */
- i = (dst_width * 3) % 4;
- if (i)
for (i = 4 - i; i--;)
*dst++ = 0;
+}
- /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) {
@@ -727,10 +781,139 @@ static HRESULT WINAPI MediaDet_GetBitmapBits(IMediaDet* iface, LONG *pBufferSize, char *pBuffer, LONG Width, LONG Height) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f %p %p %d %d): not implemented!\n", This, StreamTime, pBufferSize, pBuffer,
Width, Height);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- LONG ratio, rem, stride = (Width * 3 + 3) & ~3;
- LONG src_x, src_y, src_stride, size, buf_size;
- ULONG src_width, src_height, drift, i;
- BITMAPINFOHEADER hdr = { 0 };
- BYTE *buf, *dup, *dst, *src;
- VIDEOINFOHEADER *info;
- AM_MEDIA_TYPE mt;
- HRESULT hr;
- TRACE("(%p)->(%f %p %p %d %d)\n", detector, StreamTime, pBufferSize, pBuffer, Width, Height);
- if (!pBuffer && !pBufferSize) return E_POINTER;
- if (Width < 0 || Height < 0) return E_INVALIDARG;
- if (!pBuffer)
- {
*pBufferSize = sizeof(BITMAPINFOHEADER) + stride * Height;
return S_OK;
- }
- if (StreamTime < 0.0) return E_INVALIDARG;
- if (pBufferSize)
- {
if (*pBufferSize < 0)
return E_INVALIDARG;
if (*pBufferSize < sizeof(BITMAPINFOHEADER) + stride * Height)
return E_OUTOFMEMORY;
- }
- if (detector->grabber)
hr = seek_source(detector, StreamTime);
- else
hr = IMediaDet_EnterBitmapGrabMode(&detector->IMediaDet_iface, StreamTime);
- if (FAILED(hr))
return hr;
- hr = ISampleGrabber_GetConnectedMediaType(detector->grabber, &mt);
- info = (VIDEOINFOHEADER*)mt.pbFormat;
- if (FAILED(hr) ||
!IsEqualGUID(&mt.majortype, &MEDIATYPE_Video) ||
!IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24) ||
!IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo) ||
mt.cbFormat != sizeof(VIDEOINFOHEADER) ||
info->bmiHeader.biSize != sizeof(BITMAPINFOHEADER) ||
info->bmiHeader.biWidth <= 0 ||
info->bmiHeader.biHeight == 0 ||
info->bmiHeader.biPlanes != 1 ||
info->bmiHeader.biBitCount != 24 ||
info->bmiHeader.biCompression != BI_RGB)
return VFW_E_INVALID_MEDIA_TYPE;
This may leak "mt.pbFormat". Moreover, can some of these conditions even happen? And are the others worth checking for?
Oops you're right. And I check them rigorously just to be sure, because the Sample Grabber can be messed with, I believe.
- src_x = src_y = 0;
- src_width = info->bmiHeader.biWidth;
- src_height = abs(info->bmiHeader.biHeight);
- src_stride = (src_width * 3 + 3) & ~3;
- buf_size = src_stride * src_height;
- if (!IsRectEmpty(&info->rcTarget))
- {
src_x = max(info->rcTarget.left, 0);
src_y = max(info->rcTarget.top, 0);
src_width = min(info->rcTarget.right - src_x, src_width);
src_height = min(info->rcTarget.bottom - src_y, src_height);
- }
- if (info->bmiHeader.biHeight < 0)
- {
src_y += info->bmiHeader.biHeight + 1;
src_stride = -src_stride;
- }
- FreeMediaType(&mt);
- if (!(buf = HeapAlloc(GetProcessHeap(), 0, buf_size)))
return E_OUTOFMEMORY;
- size = buf_size;
- hr = ISampleGrabber_GetCurrentBuffer(detector->grabber, &size, (LONG*)buf);
- if (FAILED(hr)) goto err;
- if (size != buf_size)
- {
hr = E_UNEXPECTED;
goto err;
- }
- hdr.biSize = sizeof(BITMAPINFOHEADER);
- hdr.biWidth = Width;
- hdr.biHeight = Height;
- hdr.biPlanes = 1;
- hdr.biBitCount = 24;
- hdr.biCompression = BI_RGB;
- memcpy(pBuffer, &hdr, sizeof(BITMAPINFOHEADER));
- /* Copy and potentially stretch the image (differently than StretchBlt) */
- dst = (BYTE*)pBuffer + sizeof(BITMAPINFOHEADER);
- src = buf + src_x * 3 + src_y * src_stride;
- i = Height;
- if (Height < src_height)
- {
ratio = src_height / Height;
rem = src_height % Height;
drift = 0;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dst += stride;
src += ratio * src_stride;
if (drift < rem)
{
src += src_stride;
drift += Height;
}
drift -= rem;
}
- }
- else
- {
drift = Height - 1;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dup = dst;
dst += stride;
while (drift >= src_height && i--)
{
memcpy(dst, dup, stride);
dst += stride;
drift -= src_height;
}
drift += Height - src_height;
src += src_stride;
}
- }
- hr = S_OK;
+err:
HeapFree(GetProcessHeap(), 0, buf);
return hr; }
static HRESULT WINAPI MediaDet_WriteBitmapBits(IMediaDet* iface,
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 98372b6..e00226a 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1531,10 +1531,23 @@ static void test_bitmap_grab_mode(void) ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* Lines are rounded up to the bitmap stride */
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 480);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + 640 * 480 * 3, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 0);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER), "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, NULL, 151, 131);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + ((151 * 3 + 3) & ~3) * 131, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, -59, -79);
ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); testfilter.bitmap_grab_mode = TRUE;
@@ -1681,39 +1694,39 @@ static void test_bitmap_grab_mode(void)
/* despite what MSDN states, size must be properly supplied on newer Windows versions */ hr = IMediaDet_GetBitmapBits(detector, 0.0, NULL, NULL, 640, 480);
- todo_wine ok(hr == E_POINTER, "Got hr %#x.\n", hr);
- ok(hr == E_POINTER, "Got hr %#x.\n", hr); size = -1; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); size = 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); ok(size == 640 * 480 * 3, "Got size %d.\n", size); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetBitmapBits(detector, 2.5, &size, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 480, 2.5);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 640, 480, 2.5);
/* GetBitmapBits/WriteBitmapBits can scale the image */ size = sizeof(BITMAPINFOHEADER) + 960 * 720 * 3; hr = IMediaDet_GetBitmapBits(detector, 1.5, &size, buf, 131, 151);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(size == sizeof(BITMAPINFOHEADER) + 960 * 720 * 3, "Got size %d.\n", size);
- todo_wine check_bitmap(buf, 131, 151, 1.5);
- check_bitmap(buf, 131, 151, 1.5); hr = IMediaDet_GetBitmapBits(detector, 4.0, NULL, buf, 503, 79);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 503, 79, 4.0);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 503, 79, 4.0); hr = IMediaDet_GetBitmapBits(detector, 1.52, NULL, buf, 139, 487);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 139, 487, 1.52);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 139, 487, 1.52); hr = IMediaDet_GetBitmapBits(detector, 2.12, NULL, buf, 640, 641);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 641, 2.12);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 640, 641, 2.12); hr = IMediaDet_GetBitmapBits(detector, 3.25, NULL, buf, 960, 720);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 960, 720, 3.25);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 960, 720, 3.25);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
@@ -1728,7 +1741,7 @@ static void test_bitmap_grab_mode(void) /* GetBitmapBits enables it only if it retrieves the image */ testfilter.bitmap_grab_mode = TRUE; hr = IMediaDet_GetBitmapBits(detector, 1.25, &size, NULL, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count);
@@ -1736,12 +1749,12 @@ static void test_bitmap_grab_mode(void) ok(count == 1, "Got %d streams.\n", count);
hr = IMediaDet_GetBitmapBits(detector, 1.25, NULL, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) ISampleGrabber_Release(sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ISampleGrabber_Release(sg); hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref);
On 10/21/20 7:57 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 20:27, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
The stretching algorithm that Windows uses does not match any mode on StretchBlt or StretchDIBits, which is why the actual algorithm used by these APIs was implemented.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
I've literally tested every mode (on SetStretchBltMode) and operation mode and none of them were correct on Windows, even if Wine's implementation is not perfect. So clearly Windows doesn't use gdi32 to do the scaling here. It was quite a pain to discover the exact algorithm used, but this passes all tests on all Windows versions on the testbot.
I appreciate the attention to detail, and the effort put into reverse-engineering this algorithm. However, pixel-perfect accuracy is not really a goal of Wine, especially not when we're relying on libraries like GStreamer to do decoding and format conversions, and in this case I don't think that it's worth writing or maintaining a new stretching algorithm. I would instead just use StretchDIBits() with the closest stretch blit mode.
Hmm, can we please keep it this time? I have several reasons of course, but I'd also like to avoid feel like I wasted all that effort for nothing. :-)
I understand, and sympathize, but at the same time, it's not a good argument for accepting a patch...
I think there might be a reason Microsoft used a different stretching than gdi32, and since I've already done it... it's not that complicated either, it's quite a simple algorithm.
More importantly, if we don't have an exact stretch, we won't be able to test it at all, not even on Windows. It's not a question of todo_wine, we won't be able to test it *at all*.
I'd have to remove all the stretching tests on the bitmap data. This was actually my main motivator to reverse engineer it, after all. Are you sure that's a good idea, seeing as I already uncovered it now? (so there's no extra effort involved, because it passes on *all* Windows versions, from XP to the latest Windows 10)
It can probably be tested in a similar way to the d3d tests [e.g. compare_color() in a loop.] Failing that, though, just using a solid color isn't awful.
dlls/qedit/mediadet.c | 191 +++++++++++++++++++++++++++++++++++- dlls/qedit/tests/mediadet.c | 57 ++++++----- 2 files changed, 222 insertions(+), 26 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index 6afae37..f874095 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -324,6 +324,60 @@ done: return hr; }
+static void stretch_line(BYTE *dst, ULONG dst_width, BYTE *src, ULONG src_width) +{
- ULONG ratio, rem, drift, i = dst_width;
- if (dst_width < src_width)
- {
ratio = src_width / dst_width;
rem = src_width % dst_width;
drift = 0;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
src += ratio * 3;
if (drift < rem)
{
src += 3;
drift += dst_width;
}
drift -= rem;
}
- }
- else if (dst_width > src_width)
- {
drift = dst_width - 1;
while (i--)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst += 3;
if (drift < src_width)
{
drift += dst_width;
src += 3;
}
drift -= src_width;
}
- }
- else
- {
memcpy(dst, src, dst_width * 3);
dst += dst_width * 3;
- }
- /* Fill the stride padding with zeros */
- i = (dst_width * 3) % 4;
- if (i)
for (i = 4 - i; i--;)
*dst++ = 0;
+}
- /* MediaDet inner IUnknown */ static HRESULT WINAPI MediaDet_inner_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) {
@@ -727,10 +781,139 @@ static HRESULT WINAPI MediaDet_GetBitmapBits(IMediaDet* iface, LONG *pBufferSize, char *pBuffer, LONG Width, LONG Height) {
- MediaDetImpl *This = impl_from_IMediaDet(iface);
- FIXME("(%p)->(%f %p %p %d %d): not implemented!\n", This, StreamTime, pBufferSize, pBuffer,
Width, Height);
- return E_NOTIMPL;
- MediaDetImpl *detector = impl_from_IMediaDet(iface);
- LONG ratio, rem, stride = (Width * 3 + 3) & ~3;
- LONG src_x, src_y, src_stride, size, buf_size;
- ULONG src_width, src_height, drift, i;
- BITMAPINFOHEADER hdr = { 0 };
- BYTE *buf, *dup, *dst, *src;
- VIDEOINFOHEADER *info;
- AM_MEDIA_TYPE mt;
- HRESULT hr;
- TRACE("(%p)->(%f %p %p %d %d)\n", detector, StreamTime, pBufferSize, pBuffer, Width, Height);
- if (!pBuffer && !pBufferSize) return E_POINTER;
- if (Width < 0 || Height < 0) return E_INVALIDARG;
- if (!pBuffer)
- {
*pBufferSize = sizeof(BITMAPINFOHEADER) + stride * Height;
return S_OK;
- }
- if (StreamTime < 0.0) return E_INVALIDARG;
- if (pBufferSize)
- {
if (*pBufferSize < 0)
return E_INVALIDARG;
if (*pBufferSize < sizeof(BITMAPINFOHEADER) + stride * Height)
return E_OUTOFMEMORY;
- }
- if (detector->grabber)
hr = seek_source(detector, StreamTime);
- else
hr = IMediaDet_EnterBitmapGrabMode(&detector->IMediaDet_iface, StreamTime);
- if (FAILED(hr))
return hr;
- hr = ISampleGrabber_GetConnectedMediaType(detector->grabber, &mt);
- info = (VIDEOINFOHEADER*)mt.pbFormat;
- if (FAILED(hr) ||
!IsEqualGUID(&mt.majortype, &MEDIATYPE_Video) ||
!IsEqualGUID(&mt.subtype, &MEDIASUBTYPE_RGB24) ||
!IsEqualGUID(&mt.formattype, &FORMAT_VideoInfo) ||
mt.cbFormat != sizeof(VIDEOINFOHEADER) ||
info->bmiHeader.biSize != sizeof(BITMAPINFOHEADER) ||
info->bmiHeader.biWidth <= 0 ||
info->bmiHeader.biHeight == 0 ||
info->bmiHeader.biPlanes != 1 ||
info->bmiHeader.biBitCount != 24 ||
info->bmiHeader.biCompression != BI_RGB)
return VFW_E_INVALID_MEDIA_TYPE;
This may leak "mt.pbFormat". Moreover, can some of these conditions even happen? And are the others worth checking for?
Oops you're right. And I check them rigorously just to be sure, because the Sample Grabber can be messed with, I believe.
A lot of components in the graph *can* be "messed with", yes. I'm not sure that it's actually worth trying to verify that they're not all the time, though, because as a rule, applications get to keep the pieces if they break things.
- src_x = src_y = 0;
- src_width = info->bmiHeader.biWidth;
- src_height = abs(info->bmiHeader.biHeight);
- src_stride = (src_width * 3 + 3) & ~3;
- buf_size = src_stride * src_height;
- if (!IsRectEmpty(&info->rcTarget))
- {
src_x = max(info->rcTarget.left, 0);
src_y = max(info->rcTarget.top, 0);
src_width = min(info->rcTarget.right - src_x, src_width);
src_height = min(info->rcTarget.bottom - src_y, src_height);
- }
- if (info->bmiHeader.biHeight < 0)
- {
src_y += info->bmiHeader.biHeight + 1;
src_stride = -src_stride;
- }
- FreeMediaType(&mt);
- if (!(buf = HeapAlloc(GetProcessHeap(), 0, buf_size)))
return E_OUTOFMEMORY;
- size = buf_size;
- hr = ISampleGrabber_GetCurrentBuffer(detector->grabber, &size, (LONG*)buf);
- if (FAILED(hr)) goto err;
- if (size != buf_size)
- {
hr = E_UNEXPECTED;
goto err;
- }
- hdr.biSize = sizeof(BITMAPINFOHEADER);
- hdr.biWidth = Width;
- hdr.biHeight = Height;
- hdr.biPlanes = 1;
- hdr.biBitCount = 24;
- hdr.biCompression = BI_RGB;
- memcpy(pBuffer, &hdr, sizeof(BITMAPINFOHEADER));
- /* Copy and potentially stretch the image (differently than StretchBlt) */
- dst = (BYTE*)pBuffer + sizeof(BITMAPINFOHEADER);
- src = buf + src_x * 3 + src_y * src_stride;
- i = Height;
- if (Height < src_height)
- {
ratio = src_height / Height;
rem = src_height % Height;
drift = 0;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dst += stride;
src += ratio * src_stride;
if (drift < rem)
{
src += src_stride;
drift += Height;
}
drift -= rem;
}
- }
- else
- {
drift = Height - 1;
while (i--)
{
stretch_line(dst, Width, src, src_width);
dup = dst;
dst += stride;
while (drift >= src_height && i--)
{
memcpy(dst, dup, stride);
dst += stride;
drift -= src_height;
}
drift += Height - src_height;
src += src_stride;
}
- }
- hr = S_OK;
+err:
HeapFree(GetProcessHeap(), 0, buf);
return hr; }
static HRESULT WINAPI MediaDet_WriteBitmapBits(IMediaDet* iface,
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 98372b6..e00226a 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1531,10 +1531,23 @@ static void test_bitmap_grab_mode(void) ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
/* Lines are rounded up to the bitmap stride */
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 480);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + 640 * 480 * 3, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, 640, 0);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER), "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, NULL, 151, 131);
ok(hr == S_OK, "Got hr %#x.\n", hr);
ok(size == sizeof(BITMAPINFOHEADER) + ((151 * 3 + 3) & ~3) * 131, "Got size %d.\n", size);
hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, NULL, -59, -79);
ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
/* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */ testfilter_init(&testfilter); testfilter.bitmap_grab_mode = TRUE;
@@ -1681,39 +1694,39 @@ static void test_bitmap_grab_mode(void)
/* despite what MSDN states, size must be properly supplied on newer Windows versions */ hr = IMediaDet_GetBitmapBits(detector, 0.0, NULL, NULL, 640, 480);
- todo_wine ok(hr == E_POINTER, "Got hr %#x.\n", hr);
- ok(hr == E_POINTER, "Got hr %#x.\n", hr); size = -1; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); size = 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr);
- ok(hr == E_OUTOFMEMORY || broken(hr == S_OK) /* WinXP */, "Got hr %#x.\n", hr); ok(size == 640 * 480 * 3, "Got size %d.\n", size); size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, -1.0, &size, buf, 640, 480);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetBitmapBits(detector, 2.5, &size, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 480, 2.5);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 640, 480, 2.5);
/* GetBitmapBits/WriteBitmapBits can scale the image */ size = sizeof(BITMAPINFOHEADER) + 960 * 720 * 3; hr = IMediaDet_GetBitmapBits(detector, 1.5, &size, buf, 131, 151);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); ok(size == sizeof(BITMAPINFOHEADER) + 960 * 720 * 3, "Got size %d.\n", size);
- todo_wine check_bitmap(buf, 131, 151, 1.5);
- check_bitmap(buf, 131, 151, 1.5); hr = IMediaDet_GetBitmapBits(detector, 4.0, NULL, buf, 503, 79);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 503, 79, 4.0);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 503, 79, 4.0); hr = IMediaDet_GetBitmapBits(detector, 1.52, NULL, buf, 139, 487);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 139, 487, 1.52);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 139, 487, 1.52); hr = IMediaDet_GetBitmapBits(detector, 2.12, NULL, buf, 640, 641);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 640, 641, 2.12);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- check_bitmap(buf, 640, 641, 2.12); hr = IMediaDet_GetBitmapBits(detector, 3.25, NULL, buf, 960, 720);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine check_bitmap(buf, 960, 720, 3.25);
ok(hr == S_OK, "Got hr %#x.\n", hr);
check_bitmap(buf, 960, 720, 3.25);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE;
@@ -1728,7 +1741,7 @@ static void test_bitmap_grab_mode(void) /* GetBitmapBits enables it only if it retrieves the image */ testfilter.bitmap_grab_mode = TRUE; hr = IMediaDet_GetBitmapBits(detector, 1.25, &size, NULL, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count);
@@ -1736,12 +1749,12 @@ static void test_bitmap_grab_mode(void) ok(count == 1, "Got %d streams.\n", count);
hr = IMediaDet_GetBitmapBits(detector, 1.25, NULL, buf, 640, 480);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) ISampleGrabber_Release(sg);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ISampleGrabber_Release(sg); hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref);
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/tests/mediadet.c | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index e00226a..951bf04 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -811,6 +811,45 @@ static void check_bitmap_(unsigned line, void *buffer, LONG width, LONG height, free(img); }
+#define check_bitmap_file(filename, buffer, width, height, seek_time) check_bitmap_file_(__LINE__, filename, buffer, width, height, seek_time) +static void check_bitmap_file_(unsigned line, const WCHAR *filename, void *buffer, LONG width, LONG height, double seek_time) +{ + DWORD size_high, read, expected; + BITMAPFILEHEADER *hdr; + DWORD64 size; + HANDLE file; + + /* WriteBitmapBits rounds the width up to a multiple of 4 */ + width = (width + 3) & ~3; + expected = sizeof(BITMAPINFOHEADER) + width * 3 * height; + + file = CreateFileW(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + ok_(__FILE__,line)(file != INVALID_HANDLE_VALUE, "CreateFile failed, error: 0x%08x.\n", GetLastError()); + if (file == INVALID_HANDLE_VALUE) return; + + size = GetFileSize(file, &size_high); + size |= (DWORD64)size_high << 32; + ok_(__FILE__,line)(GetLastError() == NO_ERROR, "GetFileSize failed, error: 0x%08x.\n", GetLastError()); + ok_(__FILE__,line)(size == expected + sizeof(BITMAPFILEHEADER), "Wrong size 0x%s (expected 0x%x).\n", + wine_dbgstr_longlong(size), expected); + + ok_(__FILE__,line)(ReadFile(file, buffer, sizeof(BITMAPFILEHEADER), &read, NULL), + "ReadFile failed, error: 0x%08x.\n", GetLastError()); + ok_(__FILE__,line)(read == sizeof(BITMAPFILEHEADER), "ReadFile read %u bytes (expected %u).\n", + read, sizeof(BITMAPFILEHEADER)); + hdr = (BITMAPFILEHEADER*)buffer; + ok_(__FILE__,line)(hdr->bfType == 0x4d42, "Got bfType %04x.\n", hdr->bfType); + ok_(__FILE__,line)(hdr->bfSize == expected + sizeof(BITMAPFILEHEADER), "Got bfSize %u.\n", hdr->bfSize); + ok_(__FILE__,line)(hdr->bfOffBits == sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), + "Got bfOffBits %u.\n", hdr->bfOffBits); + + ok_(__FILE__,line)(ReadFile(file, buffer, expected, &read, NULL), "ReadFile failed, error: 0x%08x.\n", GetLastError()); + ok_(__FILE__,line)(read == expected, "ReadFile read %u bytes (expected %u).\n", read, expected); + + CloseHandle(file); + check_bitmap_(line, buffer, width, height, seek_time); +} + static BOOL init_tests(void) { return unpack_avi_file(TEST_AVI_RES, test_avi_filename) @@ -1503,6 +1542,7 @@ static void test_bitmap_grab_mode(void) &TIME_FORMAT_MEDIA_TIME }; char *buf = malloc(sizeof(BITMAPINFOHEADER) + 960 * 720 * 3); + WCHAR temp_path[MAX_PATH], filename[MAX_PATH]; struct testfilter testfilter; FILTER_INFO filter_info; IReferenceClock *clock; @@ -1523,6 +1563,10 @@ static void test_bitmap_grab_mode(void) GUID guid; BSTR str;
+ ok(GetTempPathW(MAX_PATH, temp_path), "GetTempPath failed, error: 0x%08x.\n", GetLastError()); + ok(GetTempFileNameW(temp_path, L"DES", 0, filename), "GetTempFileName failed, error: 0x%08x.\n", GetLastError()); + DeleteFileW(filename); + hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, &IID_IMediaDet, (void **)&detector); ok(hr == S_OK, "Got hr %#x.\n", hr); @@ -1532,6 +1576,8 @@ static void test_bitmap_grab_mode(void) size = sizeof(BITMAPINFOHEADER) + 640 * 480 * 3; hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + hr = IMediaDet_WriteBitmapBits(detector, 0.0, 640, 480, filename); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
@@ -1728,6 +1774,24 @@ static void test_bitmap_grab_mode(void) ok(hr == S_OK, "Got hr %#x.\n", hr); check_bitmap(buf, 960, 720, 3.25);
+ hr = IMediaDet_WriteBitmapBits(detector, 0.0, 640, 480, NULL); + todo_wine ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); + hr = IMediaDet_WriteBitmapBits(detector, 1.6, 640, 480, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap_file(filename, buf, 640, 480, 1.6); + hr = IMediaDet_WriteBitmapBits(detector, 3.08, 487, 337, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap_file(filename, buf, 487, 337, 3.08); + hr = IMediaDet_WriteBitmapBits(detector, 2.68, 11, 250, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap_file(filename, buf, 11, 250, 2.68); + hr = IMediaDet_WriteBitmapBits(detector, 0.44, 441, 363, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap_file(filename, buf, 441, 363, 0.44); + hr = IMediaDet_WriteBitmapBits(detector, 4.04, 809, 701, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + todo_wine check_bitmap_file(filename, buf, 809, 701, 4.04); + /* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); @@ -1756,6 +1820,34 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_get_OutputStreams(detector, &count); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
+ testfilter.bitmap_grab_mode = FALSE; + hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); + hr = IMediaDet_get_OutputStreams(detector, &count); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(count == 1, "Got %d streams.\n", count); + + /* As does WriteBitmapBits */ + testfilter.bitmap_grab_mode = TRUE; + hr = IMediaDet_WriteBitmapBits(detector, 1.75, 640, 480, NULL); + todo_wine ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); + hr = IMediaDet_get_OutputStreams(detector, &count); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(count == 1, "Got %d streams.\n", count); + + hr = IMediaDet_WriteBitmapBits(detector, 1.75, 640, 480, filename); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaDet_GetSampleGrabber(detector, &sg); + todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + if (SUCCEEDED(hr)) ISampleGrabber_Release(sg); + hr = IMediaDet_get_OutputStreams(detector, &count); + todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + + DeleteFileW(filename); ref = IMediaDet_Release(detector); ok(!ref, "Got outstanding refcount %d.\n", ref); ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/qedit/mediadet.c | 62 +++++++++++++++++++++++++++++++++++-- dlls/qedit/tests/mediadet.c | 35 ++++++++++----------- 2 files changed, 76 insertions(+), 21 deletions(-)
diff --git a/dlls/qedit/mediadet.c b/dlls/qedit/mediadet.c index f874095..306fdc3 100644 --- a/dlls/qedit/mediadet.c +++ b/dlls/qedit/mediadet.c @@ -920,9 +920,65 @@ static HRESULT WINAPI MediaDet_WriteBitmapBits(IMediaDet* iface, double StreamTime, LONG Width, LONG Height, BSTR Filename) { - MediaDetImpl *This = impl_from_IMediaDet(iface); - FIXME("(%p)->(%f %d %d %p): not implemented!\n", This, StreamTime, Width, Height, Filename); - return E_NOTIMPL; + MediaDetImpl *detector = impl_from_IMediaDet(iface); + BITMAPFILEHEADER *hdr; + HANDLE file, mapping; + LONG size, size_hi; + HRESULT hr; + char *buf; + + TRACE("(%p)->(%f %d %d %s)\n", detector, StreamTime, Width, Height, debugstr_w(Filename)); + + if (!Filename) + return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); + + /* WriteBitmapBits rounds the width up to a multiple of 4 */ + Width = (Width + 3) & ~3; + size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + Width * 3 * Height; + + file = CreateFileW(Filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + return HRESULT_FROM_WIN32(GetLastError()); + + size_hi = 0; + if (SetFilePointer(file, size, &size_hi, FILE_BEGIN) == INVALID_SET_FILE_POINTER || + !SetEndOfFile(file) || + !(mapping = CreateFileMappingW(file, NULL, PAGE_READWRITE, 0, 0, NULL))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + CloseHandle(file); + DeleteFileW(Filename); + return hr; + } + + if (!(buf = MapViewOfFile(mapping, FILE_MAP_WRITE, 0, 0, 0))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto err; + } + + hdr = (BITMAPFILEHEADER*)buf; + hdr->bfType = 0x4d42; + hdr->bfSize = size; + hdr->bfReserved1 = 0; + hdr->bfReserved2 = 0; + hdr->bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); + hr = IMediaDet_GetBitmapBits(&detector->IMediaDet_iface, StreamTime, NULL, + buf + sizeof(BITMAPFILEHEADER), Width, Height); + UnmapViewOfFile(buf); + if (FAILED(hr)) + goto err; + + CloseHandle(mapping); + CloseHandle(file); + return S_OK; + +err: + CloseHandle(mapping); + CloseHandle(file); + DeleteFileW(Filename); + return hr; }
static HRESULT WINAPI MediaDet_get_StreamMediaType(IMediaDet* iface, diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index 951bf04..adb0d36 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -1565,7 +1565,6 @@ static void test_bitmap_grab_mode(void)
ok(GetTempPathW(MAX_PATH, temp_path), "GetTempPath failed, error: 0x%08x.\n", GetLastError()); ok(GetTempFileNameW(temp_path, L"DES", 0, filename), "GetTempFileName failed, error: 0x%08x.\n", GetLastError()); - DeleteFileW(filename);
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER, &IID_IMediaDet, (void **)&detector); @@ -1577,7 +1576,7 @@ static void test_bitmap_grab_mode(void) hr = IMediaDet_GetBitmapBits(detector, 0.0, &size, buf, 640, 480); ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_WriteBitmapBits(detector, 0.0, 640, 480, filename); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr);
@@ -1775,22 +1774,22 @@ static void test_bitmap_grab_mode(void) check_bitmap(buf, 960, 720, 3.25);
hr = IMediaDet_WriteBitmapBits(detector, 0.0, 640, 480, NULL); - todo_wine ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); + ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); hr = IMediaDet_WriteBitmapBits(detector, 1.6, 640, 480, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap_file(filename, buf, 640, 480, 1.6); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap_file(filename, buf, 640, 480, 1.6); hr = IMediaDet_WriteBitmapBits(detector, 3.08, 487, 337, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap_file(filename, buf, 487, 337, 3.08); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap_file(filename, buf, 487, 337, 3.08); hr = IMediaDet_WriteBitmapBits(detector, 2.68, 11, 250, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap_file(filename, buf, 11, 250, 2.68); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap_file(filename, buf, 11, 250, 2.68); hr = IMediaDet_WriteBitmapBits(detector, 0.44, 441, 363, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap_file(filename, buf, 441, 363, 0.44); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap_file(filename, buf, 441, 363, 0.44); hr = IMediaDet_WriteBitmapBits(detector, 4.04, 809, 701, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - todo_wine check_bitmap_file(filename, buf, 809, 701, 4.04); + ok(hr == S_OK, "Got hr %#x.\n", hr); + check_bitmap_file(filename, buf, 809, 701, 4.04);
/* Changing filter resets bitmap grab mode */ testfilter.bitmap_grab_mode = FALSE; @@ -1832,7 +1831,7 @@ static void test_bitmap_grab_mode(void) /* As does WriteBitmapBits */ testfilter.bitmap_grab_mode = TRUE; hr = IMediaDet_WriteBitmapBits(detector, 1.75, 640, 480, NULL); - todo_wine ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); + ok(hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); ok(hr == E_NOINTERFACE, "Got hr %#x.\n", hr); hr = IMediaDet_get_OutputStreams(detector, &count); @@ -1840,12 +1839,12 @@ static void test_bitmap_grab_mode(void) ok(count == 1, "Got %d streams.\n", count);
hr = IMediaDet_WriteBitmapBits(detector, 1.75, 640, 480, filename); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(hr == S_OK, "Got hr %#x.\n", hr); hr = IMediaDet_GetSampleGrabber(detector, &sg); - todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr); - if (SUCCEEDED(hr)) ISampleGrabber_Release(sg); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ISampleGrabber_Release(sg); hr = IMediaDet_get_OutputStreams(detector, &count); - todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
DeleteFileW(filename); ref = IMediaDet_Release(detector);
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
- BOOL bitmap_grab_mode;
- const GUID *time_format;
- LONGLONG cur_pos;
- HANDLE thread;
};
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) @@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
+static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (!filter->bitmap_grab_mode) return S_OK;
- hr = BaseOutputPinImpl_Active(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
- ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
- return S_OK;
+}
+static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (filter->thread)
- {
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->thread = NULL;
- }
- if (!filter->bitmap_grab_mode)
return S_OK;
- hr = BaseOutputPinImpl_Inactive(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return S_OK;
+}
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy,
- .filter_init_stream = testfilter_init_stream,
- .filter_cleanup_stream = testfilter_cleanup_stream
};
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface) @@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640,
.bmiHeader.biHeight = 480,
.bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB,
@@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface,
IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested)
+{
- ALLOCATOR_PROPERTIES actual;
- if (!requested->cbAlign)
requested->cbAlign = 1;
- if (requested->cbBuffer < 640 * 480 * 3)
requested->cbBuffer = 640 * 480 * 3;
- if (!requested->cBuffers)
requested->cBuffers = 1;
- return IMemAllocator_SetProperties(allocator, requested, &actual);
+}
static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) {
- return S_OK;
- ALLOCATOR_PROPERTIES props = {0};
- HRESULT hr;
- hr = BaseOutputPinImpl_InitAllocator(iface, allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IMemInputPin_GetAllocatorRequirements(peer, &props);
- hr = testsource_DecideBufferSize(iface, *allocator, &props);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE);
}
static const struct strmbase_source_ops testsource_ops = @@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
- .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator,
};
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n");
*caps = 0; /* Doesn't seem to have any effect, despite being called */
I don't know that this comment is especially interesting; I can kind of draw that conclusion from the 0 return.
return S_OK;
- }
- ok(0, "Unexpected call.\n");
You could, I think, simplify this (and other functions) by getting rid of the condition and using "ok(filter->bitmap_grab_mode, ...)".
return E_NOTIMPL;
} @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format));
ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format));
return S_OK;
- }
- ok(0, "Unexpected call.\n"); return E_NOTIMPL;
} @@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n");
*format = *filter->time_format;
return S_OK;
- }
- ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format));
return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE;
- }
- ok(0, "Unexpected call.\n"); return E_NOTIMPL;
} @@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) {
- ok(0, "Unexpected call.\n");
- return E_NOTIMPL;
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (winetest_debug > 1)
trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n",
wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags);
- if (filter->bitmap_grab_mode)
- {
ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop));
ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime),
"Unexpected current_flags 0x%08x.\n", current_flags);
ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags);
When are "*stop" and "stop_flags" nonzero?
Also, please trace (32-bit) hexadecimal numbers with "%#x".
if (filter->thread)
{
IPin_BeginFlush(filter->source.pin.peer);
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->cur_pos = *current;
IPin_EndFlush(filter->source.pin.peer);
filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
}
else
filter->cur_pos = *current;
- }
- return S_OK;
}
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop) @@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
- filter->cur_pos = 0xdeadbeef;
- filter->time_format = &TIME_FORMAT_MEDIA_TIME;
}
static WCHAR test_avi_filename[MAX_PATH]; @@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{
- static const GUID *time_formats[] =
"static const GUID *const time_formats[]"
- {
&TIME_FORMAT_NONE,
&TIME_FORMAT_FRAME,
&TIME_FORMAT_SAMPLE,
&TIME_FORMAT_FIELD,
&TIME_FORMAT_BYTE,
&TIME_FORMAT_MEDIA_TIME
- };
- struct testfilter testfilter;
- IMediaDet *detector;
- AM_MEDIA_TYPE mt;
- double duration;
- IUnknown *unk;
- unsigned i;
- HRESULT hr;
- LONG count;
- ULONG ref;
- GUID guid;
- BSTR str;
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */
- for (i = 0; i < ARRAY_SIZE(time_formats); i++)
- {
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
ok(hr == S_OK, "Got hr %#x.\n", hr);
testfilter_init(&testfilter);
testfilter.bitmap_grab_mode = TRUE;
testfilter.time_format = time_formats[i];
hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0);
if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME)
{
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
}
else
ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector);
ok(!ref, "Got outstanding refcount %d.\n", ref);
ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
ok(!ref, "Got outstanding refcount %d.\n", ref);
- }
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- /* These still work */
- hr = IMediaDet_get_Filter(detector, &unk);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IUnknown_Release(unk);
- hr = IMediaDet_get_Filename(detector, &str);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- SysFreeString(str);
- hr = IMediaDet_get_CurrentStream(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 0, "Got stream %d.\n", count);
- /* These don't work anymore */
- hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Changing filter resets bitmap grab mode */
- testfilter.bitmap_grab_mode = FALSE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_OutputStreams(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 1, "Got %d streams.\n", count);
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
+}
START_TEST(mediadet) { IMediaDet *detector; @@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins();
test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
BOOL bitmap_grab_mode;
const GUID *time_format;
LONGLONG cur_pos;
HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
@@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
+static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (!filter->bitmap_grab_mode) return S_OK;
- hr = BaseOutputPinImpl_Active(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
- ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
- return S_OK;
+}
+static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (filter->thread)
- {
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->thread = NULL;
- }
- if (!filter->bitmap_grab_mode)
return S_OK;
- hr = BaseOutputPinImpl_Inactive(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return S_OK;
+}
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy,
.filter_init_stream = testfilter_init_stream,
.filter_cleanup_stream = testfilter_cleanup_stream };
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface)
@@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640,
.bmiHeader.biHeight = 480,
.bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB,
@@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface,
IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested)
+{
- ALLOCATOR_PROPERTIES actual;
- if (!requested->cbAlign)
requested->cbAlign = 1;
- if (requested->cbBuffer < 640 * 480 * 3)
requested->cbBuffer = 640 * 480 * 3;
- if (!requested->cBuffers)
requested->cBuffers = 1;
- return IMemAllocator_SetProperties(allocator, requested, &actual);
+}
- static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) {
- return S_OK;
ALLOCATOR_PROPERTIES props = {0};
HRESULT hr;
hr = BaseOutputPinImpl_InitAllocator(iface, allocator);
ok(hr == S_OK, "Got hr %#x.\n", hr);
IMemInputPin_GetAllocatorRequirements(peer, &props);
hr = testsource_DecideBufferSize(iface, *allocator, &props);
ok(hr == S_OK, "Got hr %#x.\n", hr);
return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); }
static const struct strmbase_source_ops testsource_ops =
@@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
- .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator, };
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n");
*caps = 0; /* Doesn't seem to have any effect, despite being called */
I don't know that this comment is especially interesting; I can kind of draw that conclusion from the 0 return.
return S_OK;
- }
ok(0, "Unexpected call.\n");
You could, I think, simplify this (and other functions) by getting rid of the condition and using "ok(filter->bitmap_grab_mode, ...)".
return E_NOTIMPL;
} @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format));
ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format));
return S_OK;
- }
}ok(0, "Unexpected call.\n"); return E_NOTIMPL;
@@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n");
*format = *filter->time_format;
return S_OK;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format));
return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
@@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) {
- ok(0, "Unexpected call.\n");
- return E_NOTIMPL;
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (winetest_debug > 1)
trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n",
wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags);
- if (filter->bitmap_grab_mode)
- {
ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop));
ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime),
"Unexpected current_flags 0x%08x.\n", current_flags);
ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags);
When are "*stop" and "stop_flags" nonzero?
*stop is the same as *current when we seek with the bitmap grab mode. It's zero (same with stop_flags) when the filter is destroyed / rewound.
Also, please trace (32-bit) hexadecimal numbers with "%#x".
if (filter->thread)
{
IPin_BeginFlush(filter->source.pin.peer);
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->cur_pos = *current;
IPin_EndFlush(filter->source.pin.peer);
filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
}
else
filter->cur_pos = *current;
}
return S_OK; }
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop)
@@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
filter->cur_pos = 0xdeadbeef;
filter->time_format = &TIME_FORMAT_MEDIA_TIME; }
static WCHAR test_avi_filename[MAX_PATH];
@@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{
- static const GUID *time_formats[] =
"static const GUID *const time_formats[]"
- {
&TIME_FORMAT_NONE,
&TIME_FORMAT_FRAME,
&TIME_FORMAT_SAMPLE,
&TIME_FORMAT_FIELD,
&TIME_FORMAT_BYTE,
&TIME_FORMAT_MEDIA_TIME
- };
- struct testfilter testfilter;
- IMediaDet *detector;
- AM_MEDIA_TYPE mt;
- double duration;
- IUnknown *unk;
- unsigned i;
- HRESULT hr;
- LONG count;
- ULONG ref;
- GUID guid;
- BSTR str;
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */
- for (i = 0; i < ARRAY_SIZE(time_formats); i++)
- {
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
ok(hr == S_OK, "Got hr %#x.\n", hr);
testfilter_init(&testfilter);
testfilter.bitmap_grab_mode = TRUE;
testfilter.time_format = time_formats[i];
hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0);
if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME)
{
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
}
else
ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector);
ok(!ref, "Got outstanding refcount %d.\n", ref);
ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
ok(!ref, "Got outstanding refcount %d.\n", ref);
- }
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- /* These still work */
- hr = IMediaDet_get_Filter(detector, &unk);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IUnknown_Release(unk);
- hr = IMediaDet_get_Filename(detector, &str);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- SysFreeString(str);
- hr = IMediaDet_get_CurrentStream(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 0, "Got stream %d.\n", count);
- /* These don't work anymore */
- hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Changing filter resets bitmap grab mode */
- testfilter.bitmap_grab_mode = FALSE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_OutputStreams(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 1, "Got %d streams.\n", count);
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
+}
- START_TEST(mediadet) { IMediaDet *detector;
@@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins();
test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
I'll do all the other changes, but I believe the custom filter is still very useful (also to test the stretching) unless I misunderstood something with "rendering to AVI file".
Thanks, Gabriel
On 10/20/20 12:05 PM, Gabriel Ivăncescu wrote:
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
BOOL bitmap_grab_mode;
const GUID *time_format;
LONGLONG cur_pos;
HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
@@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
That seems suspicious; I'd expect that a flush should be sufficient to achieve that.
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
quartz is complicated, not just because it uses a complicated system of callbacks, but also because it has a *lot* of moving parts, most of which can be system components but also can be application components. Hence one of the things I try to do, when writing quartz tests, is to try to test every single moving piece, one at a time. I try to validate every *non-obvious* implementation detail that I reasonably can, mostly with the exception of exhaustively testing error handling (e.g. in general I don't think it's worth testing whether the code checks for S_OK or SUCCEEDED(), especially if a potential failure can be flagged by an ERR/WARN message anyway) where functions are "not supposed to" fail.
When doing this kind of testing, I want to isolate the causes of a return value—sometimes if only for clarity—and often this means using our own custom filters instead of system ones. For example, it makes it clearer that the video renderer is the one responsible for holding up the graph during preroll if we don't have any *other* system filters in the graph at the time.
These tests are focused mostly on proving the implementation correct, though of course they help to catch regressions as well. At the same time, using custom filters also often means testing real-world behaviour. It's not rare for applications to insert their own filters at any point in the pipeline (source, sink, transform, parser).
On the other hand, there's also some value in testing very high-level usage of the quartz API. That's not just because we need to test high-level functions (like IGraphBuilder::RenderFile()) but also because, as you've discovered, it's easy to miss important details about how parts work together. These tests are more regression tests than conformance tests. Most of these are in rungraph() [in quartz:filtergraph] and its callers.
Back to the more concrete case: Certainly I'm not trying to advocate that we remove any of the testfilter infrastructure this patch adds. I am suggesting that we instead send frames one at a time instead of using such a loop. Although frankly, after looking at the later patches in the series I'm not even necessarily sure that's better. [I will note, though, that it would be nice to test the frames returned from GetBitmapBits() a little closer to where the test loop is introduced, if not in the same patch, so that I have that context immediately.] It may also make sense to send just one frame instead of looping, just for a little extra simplicity.
Separately, though, I think it would be a good idea to also add some tests that exercise the media detector's bitmap grab mode as an application would actually use it—e.g. call put_Filename() with a test AVI file (or whatever), and then GetBitmapBits(). Not actually checking the contents of the buffer, but making sure it succeeds, and doesn't crash or hang.
+static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (!filter->bitmap_grab_mode) return S_OK;
- hr = BaseOutputPinImpl_Active(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
- ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
- return S_OK;
+}
+static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (filter->thread)
- {
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->thread = NULL;
- }
- if (!filter->bitmap_grab_mode)
return S_OK;
- hr = BaseOutputPinImpl_Inactive(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return S_OK;
+}
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy,
.filter_init_stream = testfilter_init_stream,
.filter_cleanup_stream = testfilter_cleanup_stream };
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface)
@@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640,
.bmiHeader.biHeight = 480,
.bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB,
@@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface,
IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested)
+{
- ALLOCATOR_PROPERTIES actual;
- if (!requested->cbAlign)
requested->cbAlign = 1;
- if (requested->cbBuffer < 640 * 480 * 3)
requested->cbBuffer = 640 * 480 * 3;
- if (!requested->cBuffers)
requested->cBuffers = 1;
- return IMemAllocator_SetProperties(allocator, requested, &actual);
+}
- static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) {
- return S_OK;
ALLOCATOR_PROPERTIES props = {0};
HRESULT hr;
hr = BaseOutputPinImpl_InitAllocator(iface, allocator);
ok(hr == S_OK, "Got hr %#x.\n", hr);
IMemInputPin_GetAllocatorRequirements(peer, &props);
hr = testsource_DecideBufferSize(iface, *allocator, &props);
ok(hr == S_OK, "Got hr %#x.\n", hr);
return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); }
static const struct strmbase_source_ops testsource_ops =
@@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
- .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator, };
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n");
*caps = 0; /* Doesn't seem to have any effect, despite being called */
I don't know that this comment is especially interesting; I can kind of draw that conclusion from the 0 return.
return S_OK;
- }
ok(0, "Unexpected call.\n");
You could, I think, simplify this (and other functions) by getting rid of the condition and using "ok(filter->bitmap_grab_mode, ...)".
return E_NOTIMPL;
} @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format));
ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format));
return S_OK;
- }
}ok(0, "Unexpected call.\n"); return E_NOTIMPL;
@@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n");
*format = *filter->time_format;
return S_OK;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format));
return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
@@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) {
- ok(0, "Unexpected call.\n");
- return E_NOTIMPL;
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (winetest_debug > 1)
trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n",
wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags);
- if (filter->bitmap_grab_mode)
- {
ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop));
ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime),
"Unexpected current_flags 0x%08x.\n", current_flags);
ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags);
When are "*stop" and "stop_flags" nonzero?
*stop is the same as *current when we seek with the bitmap grab mode. It's zero (same with stop_flags) when the filter is destroyed / rewound.
Also, please trace (32-bit) hexadecimal numbers with "%#x".
if (filter->thread)
{
IPin_BeginFlush(filter->source.pin.peer);
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->cur_pos = *current;
IPin_EndFlush(filter->source.pin.peer);
filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
}
else
filter->cur_pos = *current;
}
return S_OK; }
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop)
@@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
filter->cur_pos = 0xdeadbeef;
filter->time_format = &TIME_FORMAT_MEDIA_TIME; }
static WCHAR test_avi_filename[MAX_PATH];
@@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{
- static const GUID *time_formats[] =
"static const GUID *const time_formats[]"
- {
&TIME_FORMAT_NONE,
&TIME_FORMAT_FRAME,
&TIME_FORMAT_SAMPLE,
&TIME_FORMAT_FIELD,
&TIME_FORMAT_BYTE,
&TIME_FORMAT_MEDIA_TIME
- };
- struct testfilter testfilter;
- IMediaDet *detector;
- AM_MEDIA_TYPE mt;
- double duration;
- IUnknown *unk;
- unsigned i;
- HRESULT hr;
- LONG count;
- ULONG ref;
- GUID guid;
- BSTR str;
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */
- for (i = 0; i < ARRAY_SIZE(time_formats); i++)
- {
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
ok(hr == S_OK, "Got hr %#x.\n", hr);
testfilter_init(&testfilter);
testfilter.bitmap_grab_mode = TRUE;
testfilter.time_format = time_formats[i];
hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0);
if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME)
{
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
}
else
ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector);
ok(!ref, "Got outstanding refcount %d.\n", ref);
ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
ok(!ref, "Got outstanding refcount %d.\n", ref);
- }
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- /* These still work */
- hr = IMediaDet_get_Filter(detector, &unk);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IUnknown_Release(unk);
- hr = IMediaDet_get_Filename(detector, &str);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- SysFreeString(str);
- hr = IMediaDet_get_CurrentStream(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 0, "Got stream %d.\n", count);
- /* These don't work anymore */
- hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Changing filter resets bitmap grab mode */
- testfilter.bitmap_grab_mode = FALSE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_OutputStreams(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 1, "Got %d streams.\n", count);
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
+}
- START_TEST(mediadet) { IMediaDet *detector;
@@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins();
test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
I'll do all the other changes, but I believe the custom filter is still very useful (also to test the stretching) unless I misunderstood something with "rendering to AVI file".
Thanks, Gabriel
On 20/10/2020 22:56, Zebediah Figura wrote:
On 10/20/20 12:05 PM, Gabriel Ivăncescu wrote:
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
BOOL bitmap_grab_mode;
const GUID *time_format;
LONGLONG cur_pos;
HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
@@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
That seems suspicious; I'd expect that a flush should be sufficient to achieve that.
It's possible there's a bug in the Sample Grabber, because it doesn't handle flushing at all, and I don't know if it should. But as I mentioned in the other reply to the EnterBitmapGrabMode implementation, this uncovers the bug and Windows does seem to put a Stop() when seeking (if I trace +strmbase on the test).
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
quartz is complicated, not just because it uses a complicated system of callbacks, but also because it has a *lot* of moving parts, most of which can be system components but also can be application components. Hence one of the things I try to do, when writing quartz tests, is to try to test every single moving piece, one at a time. I try to validate every *non-obvious* implementation detail that I reasonably can, mostly with the exception of exhaustively testing error handling (e.g. in general I don't think it's worth testing whether the code checks for S_OK or SUCCEEDED(), especially if a potential failure can be flagged by an ERR/WARN message anyway) where functions are "not supposed to" fail.
When doing this kind of testing, I want to isolate the causes of a return value—sometimes if only for clarity—and often this means using our own custom filters instead of system ones. For example, it makes it clearer that the video renderer is the one responsible for holding up the graph during preroll if we don't have any *other* system filters in the graph at the time.
These tests are focused mostly on proving the implementation correct, though of course they help to catch regressions as well. At the same time, using custom filters also often means testing real-world behaviour. It's not rare for applications to insert their own filters at any point in the pipeline (source, sink, transform, parser).
On the other hand, there's also some value in testing very high-level usage of the quartz API. That's not just because we need to test high-level functions (like IGraphBuilder::RenderFile()) but also because, as you've discovered, it's easy to miss important details about how parts work together. These tests are more regression tests than conformance tests. Most of these are in rungraph() [in quartz:filtergraph] and its callers.
Back to the more concrete case: Certainly I'm not trying to advocate that we remove any of the testfilter infrastructure this patch adds. I am suggesting that we instead send frames one at a time instead of using such a loop. Although frankly, after looking at the later patches in the series I'm not even necessarily sure that's better. [I will note, though, that it would be nice to test the frames returned from GetBitmapBits() a little closer to where the test loop is introduced, if not in the same patch, so that I have that context immediately.] It may also make sense to send just one frame instead of looping, just for a little extra simplicity.
Thanks for the information :-)
I think the loop is needed to catch regressions as noticed. I should probably keep the loop in the first patch, but only send black pixels all the time, until the GetBitmapData tests, where I will convert it to what we have now with the varying pattern, so it matches the verification.
Would that be acceptable as a compromise?
Separately, though, I think it would be a good idea to also add some tests that exercise the media detector's bitmap grab mode as an application would actually use it—e.g. call put_Filename() with a test AVI file (or whatever), and then GetBitmapBits(). Not actually checking the contents of the buffer, but making sure it succeeds, and doesn't crash or hang.
Noted.
BTW I also made a few changes to the SetPositions (with stop and stop_flags) to test them better under the specific scenarios they are supposed to be (or not be) zero.
+static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (!filter->bitmap_grab_mode) return S_OK;
- hr = BaseOutputPinImpl_Active(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
- ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
- return S_OK;
+}
+static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (filter->thread)
- {
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->thread = NULL;
- }
- if (!filter->bitmap_grab_mode)
return S_OK;
- hr = BaseOutputPinImpl_Inactive(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return S_OK;
+}
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy,
.filter_init_stream = testfilter_init_stream,
.filter_cleanup_stream = testfilter_cleanup_stream };
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface)
@@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640,
.bmiHeader.biHeight = 480,
.bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB,
@@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface,
IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested)
+{
- ALLOCATOR_PROPERTIES actual;
- if (!requested->cbAlign)
requested->cbAlign = 1;
- if (requested->cbBuffer < 640 * 480 * 3)
requested->cbBuffer = 640 * 480 * 3;
- if (!requested->cBuffers)
requested->cBuffers = 1;
- return IMemAllocator_SetProperties(allocator, requested, &actual);
+}
- static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) {
- return S_OK;
ALLOCATOR_PROPERTIES props = {0};
HRESULT hr;
hr = BaseOutputPinImpl_InitAllocator(iface, allocator);
ok(hr == S_OK, "Got hr %#x.\n", hr);
IMemInputPin_GetAllocatorRequirements(peer, &props);
hr = testsource_DecideBufferSize(iface, *allocator, &props);
ok(hr == S_OK, "Got hr %#x.\n", hr);
return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); }
static const struct strmbase_source_ops testsource_ops =
@@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
- .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator, };
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n");
*caps = 0; /* Doesn't seem to have any effect, despite being called */
I don't know that this comment is especially interesting; I can kind of draw that conclusion from the 0 return.
return S_OK;
- }
ok(0, "Unexpected call.\n");
You could, I think, simplify this (and other functions) by getting rid of the condition and using "ok(filter->bitmap_grab_mode, ...)".
return E_NOTIMPL;
} @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format));
ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format));
return S_OK;
- }
}ok(0, "Unexpected call.\n"); return E_NOTIMPL;
@@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n");
*format = *filter->time_format;
return S_OK;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format));
return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
@@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) {
- ok(0, "Unexpected call.\n");
- return E_NOTIMPL;
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (winetest_debug > 1)
trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n",
wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags);
- if (filter->bitmap_grab_mode)
- {
ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop));
ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime),
"Unexpected current_flags 0x%08x.\n", current_flags);
ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags);
When are "*stop" and "stop_flags" nonzero?
*stop is the same as *current when we seek with the bitmap grab mode. It's zero (same with stop_flags) when the filter is destroyed / rewound.
Also, please trace (32-bit) hexadecimal numbers with "%#x".
if (filter->thread)
{
IPin_BeginFlush(filter->source.pin.peer);
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->cur_pos = *current;
IPin_EndFlush(filter->source.pin.peer);
filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
}
else
filter->cur_pos = *current;
}
return S_OK; }
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop)
@@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
filter->cur_pos = 0xdeadbeef;
filter->time_format = &TIME_FORMAT_MEDIA_TIME; }
static WCHAR test_avi_filename[MAX_PATH];
@@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{
- static const GUID *time_formats[] =
"static const GUID *const time_formats[]"
- {
&TIME_FORMAT_NONE,
&TIME_FORMAT_FRAME,
&TIME_FORMAT_SAMPLE,
&TIME_FORMAT_FIELD,
&TIME_FORMAT_BYTE,
&TIME_FORMAT_MEDIA_TIME
- };
- struct testfilter testfilter;
- IMediaDet *detector;
- AM_MEDIA_TYPE mt;
- double duration;
- IUnknown *unk;
- unsigned i;
- HRESULT hr;
- LONG count;
- ULONG ref;
- GUID guid;
- BSTR str;
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */
- for (i = 0; i < ARRAY_SIZE(time_formats); i++)
- {
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
ok(hr == S_OK, "Got hr %#x.\n", hr);
testfilter_init(&testfilter);
testfilter.bitmap_grab_mode = TRUE;
testfilter.time_format = time_formats[i];
hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0);
if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME)
{
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
}
else
ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector);
ok(!ref, "Got outstanding refcount %d.\n", ref);
ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
ok(!ref, "Got outstanding refcount %d.\n", ref);
- }
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- /* These still work */
- hr = IMediaDet_get_Filter(detector, &unk);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IUnknown_Release(unk);
- hr = IMediaDet_get_Filename(detector, &str);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- SysFreeString(str);
- hr = IMediaDet_get_CurrentStream(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 0, "Got stream %d.\n", count);
- /* These don't work anymore */
- hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Changing filter resets bitmap grab mode */
- testfilter.bitmap_grab_mode = FALSE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_OutputStreams(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 1, "Got %d streams.\n", count);
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
+}
- START_TEST(mediadet) { IMediaDet *detector;
@@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins();
test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
I'll do all the other changes, but I believe the custom filter is still very useful (also to test the stretching) unless I misunderstood something with "rendering to AVI file".
Thanks, Gabriel
On 10/21/20 8:07 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 22:56, Zebediah Figura wrote:
On 10/20/20 12:05 PM, Gabriel Ivăncescu wrote:
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
BOOL bitmap_grab_mode;
const GUID *time_format;
LONGLONG cur_pos;
HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
@@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
That seems suspicious; I'd expect that a flush should be sufficient to achieve that.
It's possible there's a bug in the Sample Grabber, because it doesn't handle flushing at all, and I don't know if it should. But as I mentioned in the other reply to the EnterBitmapGrabMode implementation, this uncovers the bug and Windows does seem to put a Stop() when seeking (if I trace +strmbase on the test).
If Windows does fully stop the filter, then that's fine, but that should also be tested if possible, probably by checking "filter->state" when seeking.
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
quartz is complicated, not just because it uses a complicated system of callbacks, but also because it has a *lot* of moving parts, most of which can be system components but also can be application components. Hence one of the things I try to do, when writing quartz tests, is to try to test every single moving piece, one at a time. I try to validate every *non-obvious* implementation detail that I reasonably can, mostly with the exception of exhaustively testing error handling (e.g. in general I don't think it's worth testing whether the code checks for S_OK or SUCCEEDED(), especially if a potential failure can be flagged by an ERR/WARN message anyway) where functions are "not supposed to" fail.
When doing this kind of testing, I want to isolate the causes of a return value—sometimes if only for clarity—and often this means using our own custom filters instead of system ones. For example, it makes it clearer that the video renderer is the one responsible for holding up the graph during preroll if we don't have any *other* system filters in the graph at the time.
These tests are focused mostly on proving the implementation correct, though of course they help to catch regressions as well. At the same time, using custom filters also often means testing real-world behaviour. It's not rare for applications to insert their own filters at any point in the pipeline (source, sink, transform, parser).
On the other hand, there's also some value in testing very high-level usage of the quartz API. That's not just because we need to test high-level functions (like IGraphBuilder::RenderFile()) but also because, as you've discovered, it's easy to miss important details about how parts work together. These tests are more regression tests than conformance tests. Most of these are in rungraph() [in quartz:filtergraph] and its callers.
Back to the more concrete case: Certainly I'm not trying to advocate that we remove any of the testfilter infrastructure this patch adds. I am suggesting that we instead send frames one at a time instead of using such a loop. Although frankly, after looking at the later patches in the series I'm not even necessarily sure that's better. [I will note, though, that it would be nice to test the frames returned from GetBitmapBits() a little closer to where the test loop is introduced, if not in the same patch, so that I have that context immediately.] It may also make sense to send just one frame instead of looping, just for a little extra simplicity.
Thanks for the information :-)
I think the loop is needed to catch regressions as noticed. I should probably keep the loop in the first patch, but only send black pixels all the time, until the GetBitmapData tests, where I will convert it to what we have now with the varying pattern, so it matches the verification.
Would that be acceptable as a compromise?
My point is largely that the kind of regressions you're talking about would probably have been detectable with the second, separate test I was recommending.
Note that one problem with keeping the tests as they are now is that there's not a good way to check what happens if you call GetBitmapBits() before a sample has reached the sample grabber. This is more than a little important, as it's quite possible that either the media detector or the sample grabber is supposed to wait.
Separately, though, I think it would be a good idea to also add some tests that exercise the media detector's bitmap grab mode as an application would actually use it—e.g. call put_Filename() with a test AVI file (or whatever), and then GetBitmapBits(). Not actually checking the contents of the buffer, but making sure it succeeds, and doesn't crash or hang.
Noted.
BTW I also made a few changes to the SetPositions (with stop and stop_flags) to test them better under the specific scenarios they are supposed to be (or not be) zero.
+static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (!filter->bitmap_grab_mode) return S_OK;
- hr = BaseOutputPinImpl_Active(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
- ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
- return S_OK;
+}
+static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{
- struct testfilter *filter = impl_from_strmbase_filter(iface);
- HRESULT hr;
- if (filter->thread)
- {
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->thread = NULL;
- }
- if (!filter->bitmap_grab_mode)
return S_OK;
- hr = BaseOutputPinImpl_Inactive(&filter->source);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- return S_OK;
+}
static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy,
.filter_init_stream = testfilter_init_stream,
.filter_cleanup_stream = testfilter_cleanup_stream };
static inline struct testfilter *impl_from_strmbase_pin(struct strmbase_pin *iface)
@@ -175,7 +273,7 @@ static HRESULT testsource_get_media_type(struct strmbase_pin *iface, unsigned in { .bmiHeader.biSize = sizeof(BITMAPINFOHEADER), .bmiHeader.biWidth = 640,
.bmiHeader.biHeight = 480,
.bmiHeader.biHeight = -480, .bmiHeader.biPlanes = 1, .bmiHeader.biBitCount = 24, .bmiHeader.biCompression = BI_RGB,
@@ -211,10 +309,37 @@ static HRESULT testsource_query_interface(struct strmbase_pin *iface, REFIID iid return S_OK; }
+static HRESULT WINAPI testsource_DecideBufferSize(struct strmbase_source *iface,
IMemAllocator *allocator, ALLOCATOR_PROPERTIES *requested)
+{
- ALLOCATOR_PROPERTIES actual;
- if (!requested->cbAlign)
requested->cbAlign = 1;
- if (requested->cbBuffer < 640 * 480 * 3)
requested->cbBuffer = 640 * 480 * 3;
- if (!requested->cBuffers)
requested->cBuffers = 1;
- return IMemAllocator_SetProperties(allocator, requested, &actual);
+}
- static HRESULT WINAPI testsource_DecideAllocator(struct strmbase_source *iface, IMemInputPin *peer, IMemAllocator **allocator) {
- return S_OK;
ALLOCATOR_PROPERTIES props = {0};
HRESULT hr;
hr = BaseOutputPinImpl_InitAllocator(iface, allocator);
ok(hr == S_OK, "Got hr %#x.\n", hr);
IMemInputPin_GetAllocatorRequirements(peer, &props);
hr = testsource_DecideBufferSize(iface, *allocator, &props);
ok(hr == S_OK, "Got hr %#x.\n", hr);
return IMemInputPin_NotifyAllocator(peer, *allocator, FALSE); }
static const struct strmbase_source_ops testsource_ops =
@@ -222,6 +347,7 @@ static const struct strmbase_source_ops testsource_ops = .base.pin_get_media_type = testsource_get_media_type, .base.pin_query_interface = testsource_query_interface, .pfnAttemptConnection = BaseOutputPinImpl_AttemptConnection,
- .pfnDecideBufferSize = testsource_DecideBufferSize, .pfnDecideAllocator = testsource_DecideAllocator, };
@@ -250,6 +376,15 @@ static ULONG WINAPI testseek_Release(IMediaSeeking *iface)
static HRESULT WINAPI testseek_GetCapabilities(IMediaSeeking *iface, DWORD *caps) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_GetCapabilities()\n");
*caps = 0; /* Doesn't seem to have any effect, despite being called */
I don't know that this comment is especially interesting; I can kind of draw that conclusion from the 0 return.
return S_OK;
- }
ok(0, "Unexpected call.\n");
You could, I think, simplify this (and other functions) by getting rid of the condition and using "ok(filter->bitmap_grab_mode, ...)".
return E_NOTIMPL;
} @@ -262,6 +397,15 @@ static HRESULT WINAPI testseek_CheckCapabilities(IMediaSeeking *iface, DWORD *ca
static HRESULT WINAPI testseek_IsFormatSupported(IMediaSeeking *iface, const GUID *format) {
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (filter->bitmap_grab_mode)
- {
if (winetest_debug > 1) trace("IMediaSeeking_IsFormatSupported(%s)\n", wine_dbgstr_guid(format));
ok(IsEqualGUID(format, &TIME_FORMAT_MEDIA_TIME), "Unexpected format %s.\n", wine_dbgstr_guid(format));
return S_OK;
- }
}ok(0, "Unexpected call.\n"); return E_NOTIMPL;
@@ -274,12 +418,29 @@ static HRESULT WINAPI testseek_QueryPreferredFormat(IMediaSeeking *iface, GUID *
static HRESULT WINAPI testseek_GetTimeFormat(IMediaSeeking *iface, GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_GetTimeFormat()\n");
*format = *filter->time_format;
return S_OK;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
static HRESULT WINAPI testseek_IsUsingTimeFormat(IMediaSeeking *iface, const GUID *format) {
struct testfilter *filter = impl_from_IMediaSeeking(iface);
if (filter->bitmap_grab_mode)
{
if (winetest_debug > 1) trace("IMediaSeeking_IsUsingTimeFormat(%s)\n", wine_dbgstr_guid(format));
return IsEqualGUID(format, filter->time_format) ? S_OK : S_FALSE;
}
ok(0, "Unexpected call.\n"); return E_NOTIMPL;
}
@@ -320,8 +481,35 @@ static HRESULT WINAPI testseek_ConvertTimeFormat(IMediaSeeking *iface, LONGLONG static HRESULT WINAPI testseek_SetPositions(IMediaSeeking *iface, LONGLONG *current, DWORD current_flags, LONGLONG *stop, DWORD stop_flags) {
- ok(0, "Unexpected call.\n");
- return E_NOTIMPL;
- struct testfilter *filter = impl_from_IMediaSeeking(iface);
- if (winetest_debug > 1)
trace("IMediaSeeking_SetPositions(0x%s, 0x%08x, 0x%s, 0x%08x)\n",
wine_dbgstr_longlong(*current), current_flags, wine_dbgstr_longlong(*stop), stop_flags);
- if (filter->bitmap_grab_mode)
- {
ok(*stop == *current || !*stop, "Unexpected stop position: 0x%s.\n", wine_dbgstr_longlong(*stop));
ok(current_flags == (AM_SEEKING_AbsolutePositioning | AM_SEEKING_ReturnTime),
"Unexpected current_flags 0x%08x.\n", current_flags);
ok(stop_flags == AM_SEEKING_AbsolutePositioning || !stop_flags, "Unexpected stop_flags 0x%08x.\n", stop_flags);
When are "*stop" and "stop_flags" nonzero?
*stop is the same as *current when we seek with the bitmap grab mode. It's zero (same with stop_flags) when the filter is destroyed / rewound.
Also, please trace (32-bit) hexadecimal numbers with "%#x".
if (filter->thread)
{
IPin_BeginFlush(filter->source.pin.peer);
WaitForSingleObject(filter->thread, INFINITE);
CloseHandle(filter->thread);
filter->cur_pos = *current;
IPin_EndFlush(filter->source.pin.peer);
filter->thread = CreateThread(NULL, 0, testfilter_frame_thread, filter, 0, NULL);
ok(filter->thread != NULL, "Failed to create thread: %#x.\n", GetLastError());
}
else
filter->cur_pos = *current;
}
return S_OK; }
static HRESULT WINAPI testseek_GetPositions(IMediaSeeking *iface, LONGLONG *current, LONGLONG *stop)
@@ -386,6 +574,8 @@ static void testfilter_init(struct testfilter *filter) strmbase_filter_init(&filter->filter, NULL, &clsid, &testfilter_ops); strmbase_source_init(&filter->source, &filter->filter, L"", &testsource_ops); filter->IMediaSeeking_iface.lpVtbl = &testseek_vtbl;
filter->cur_pos = 0xdeadbeef;
filter->time_format = &TIME_FORMAT_MEDIA_TIME; }
static WCHAR test_avi_filename[MAX_PATH];
@@ -1117,6 +1307,144 @@ static void test_COM_sg_enumpins(void) IBaseFilter_Release(bf); }
+static void test_bitmap_grab_mode(void) +{
- static const GUID *time_formats[] =
"static const GUID *const time_formats[]"
- {
&TIME_FORMAT_NONE,
&TIME_FORMAT_FRAME,
&TIME_FORMAT_SAMPLE,
&TIME_FORMAT_FIELD,
&TIME_FORMAT_BYTE,
&TIME_FORMAT_MEDIA_TIME
- };
- struct testfilter testfilter;
- IMediaDet *detector;
- AM_MEDIA_TYPE mt;
- double duration;
- IUnknown *unk;
- unsigned i;
- HRESULT hr;
- LONG count;
- ULONG ref;
- GUID guid;
- BSTR str;
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* EnterBitmapGrabMode only seeks once, and if SeekTime is non-negative */
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, -1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(testfilter.cur_pos == 0xdeadbeef, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- /* Time formats other than TIME_FORMAT_MEDIA_TIME return E_NOTIMPL */
- for (i = 0; i < ARRAY_SIZE(time_formats); i++)
- {
hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
ok(hr == S_OK, "Got hr %#x.\n", hr);
testfilter_init(&testfilter);
testfilter.bitmap_grab_mode = TRUE;
testfilter.time_format = time_formats[i];
hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaDet_EnterBitmapGrabMode(detector, 1337.0);
if (time_formats[i] == &TIME_FORMAT_MEDIA_TIME)
{
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
todo_wine ok(testfilter.cur_pos == 13370000000LL, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
}
else
ok(hr == E_NOTIMPL, "Got hr %#x.\n", hr);
ref = IMediaDet_Release(detector);
ok(!ref, "Got outstanding refcount %d.\n", ref);
ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
ok(!ref, "Got outstanding refcount %d.\n", ref);
- }
- hr = CoCreateInstance(&CLSID_MediaDet, NULL, CLSCTX_INPROC_SERVER,
&IID_IMediaDet, (void **)&detector);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- testfilter_init(&testfilter);
- testfilter.bitmap_grab_mode = TRUE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_EnterBitmapGrabMode(detector, 0.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- hr = IMediaDet_EnterBitmapGrabMode(detector, 1.0);
- todo_wine ok(hr == S_OK, "Got hr %#x.\n", hr);
- todo_wine ok(testfilter.cur_pos == 0, "Current position was set to 0x%s.\n", wine_dbgstr_longlong(testfilter.cur_pos));
- /* These still work */
- hr = IMediaDet_get_Filter(detector, &unk);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- IUnknown_Release(unk);
- hr = IMediaDet_get_Filename(detector, &str);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- SysFreeString(str);
- hr = IMediaDet_get_CurrentStream(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 0, "Got stream %d.\n", count);
- /* These don't work anymore */
- hr = IMediaDet_get_OutputStreams(detector, &count);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_FrameRate(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamLength(detector, &duration);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamMediaType(detector, &mt);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) FreeMediaType(&mt);
- hr = IMediaDet_get_StreamType(detector, &guid);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_StreamTypeB(detector, &str);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- if (SUCCEEDED(hr)) SysFreeString(str);
- hr = IMediaDet_put_CurrentStream(detector, 0);
- todo_wine ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr);
- /* Changing filter resets bitmap grab mode */
- testfilter.bitmap_grab_mode = FALSE;
- hr = IMediaDet_put_Filter(detector, &testfilter.filter.IUnknown_inner);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- hr = IMediaDet_get_OutputStreams(detector, &count);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- ok(count == 1, "Got %d streams.\n", count);
- ref = IMediaDet_Release(detector);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
- ref = IBaseFilter_Release(&testfilter.filter.IBaseFilter_iface);
- ok(!ref, "Got outstanding refcount %d.\n", ref);
+}
- START_TEST(mediadet) { IMediaDet *detector;
@@ -1145,6 +1473,7 @@ START_TEST(mediadet) test_put_filter(); test_samplegrabber(); test_COM_sg_enumpins();
test_bitmap_grab_mode();
ret = DeleteFileW(test_avi_filename); ok(ret, "Failed to delete file, error %u.\n", GetLastError());
I'll do all the other changes, but I believe the custom filter is still very useful (also to test the stretching) unless I misunderstood something with "rendering to AVI file".
Thanks, Gabriel
On 23/10/2020 18:51, Zebediah Figura wrote:
On 10/21/20 8:07 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 22:56, Zebediah Figura wrote:
On 10/20/20 12:05 PM, Gabriel Ivăncescu wrote:
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote:
We fill the video pattern with something that varies between lines and columns, to test it properly later (including the scaling algorithm).
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- 1 file changed, 333 insertions(+), 4 deletions(-)
diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c index dc83bb9..010b746 100644 --- a/dlls/qedit/tests/mediadet.c +++ b/dlls/qedit/tests/mediadet.c @@ -136,6 +136,11 @@ struct testfilter struct strmbase_filter filter; struct strmbase_source source; IMediaSeeking IMediaSeeking_iface;
BOOL bitmap_grab_mode;
const GUID *time_format;
LONGLONG cur_pos;
HANDLE thread; };
static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface)
@@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); }
+static DWORD WINAPI testfilter_frame_thread(void *arg) +{
- REFERENCE_TIME start_time, end_time;
- struct testfilter *filter = arg;
- IMemAllocator *allocator;
- IMediaSample *sample;
- unsigned i;
- HRESULT hr;
- DWORD fill;
- BYTE *data;
- hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator);
- ok(hr == S_OK, "Got hr %#x.\n", hr);
- start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos;
- while (hr == S_OK)
- {
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
if (hr == VFW_E_NOT_COMMITTED)
{
IMemAllocator_Commit(allocator);
hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0);
}
ok(hr == S_OK, "Got hr %#x.\n", hr);
hr = IMediaSample_GetPointer(sample, &data);
ok(hr == S_OK, "Got hr %#x.\n", hr);
fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb;
for (i = 0; i < 640 * 480 * 3; i += 3)
{
data[i] = fill ^ i;
data[i + 1] = fill >> 8 ^ i;
data[i + 2] = fill >> 16 ^ i;
}
hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3);
ok(hr == S_OK, "Got hr %#x.\n", hr);
end_time = start_time + 400000;
hr = IMediaSample_SetTime(sample, &start_time, &end_time);
ok(hr == S_OK, "Got hr %#x.\n", hr);
start_time = end_time;
if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId());
hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample);
if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr);
IMediaSample_Release(sample);
- }
- IMemAllocator_Release(allocator);
- return hr;
+}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
That seems suspicious; I'd expect that a flush should be sufficient to achieve that.
It's possible there's a bug in the Sample Grabber, because it doesn't handle flushing at all, and I don't know if it should. But as I mentioned in the other reply to the EnterBitmapGrabMode implementation, this uncovers the bug and Windows does seem to put a Stop() when seeking (if I trace +strmbase on the test).
If Windows does fully stop the filter, then that's fine, but that should also be tested if possible, probably by checking "filter->state" when seeking.
Ok on more investigation Windows seems to pause the filter *before* the seek operation, for some reason. But that's not so important now because I think I found a bug in the sample grabber anyway...
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
quartz is complicated, not just because it uses a complicated system of callbacks, but also because it has a *lot* of moving parts, most of which can be system components but also can be application components. Hence one of the things I try to do, when writing quartz tests, is to try to test every single moving piece, one at a time. I try to validate every *non-obvious* implementation detail that I reasonably can, mostly with the exception of exhaustively testing error handling (e.g. in general I don't think it's worth testing whether the code checks for S_OK or SUCCEEDED(), especially if a potential failure can be flagged by an ERR/WARN message anyway) where functions are "not supposed to" fail.
When doing this kind of testing, I want to isolate the causes of a return value—sometimes if only for clarity—and often this means using our own custom filters instead of system ones. For example, it makes it clearer that the video renderer is the one responsible for holding up the graph during preroll if we don't have any *other* system filters in the graph at the time.
These tests are focused mostly on proving the implementation correct, though of course they help to catch regressions as well. At the same time, using custom filters also often means testing real-world behaviour. It's not rare for applications to insert their own filters at any point in the pipeline (source, sink, transform, parser).
On the other hand, there's also some value in testing very high-level usage of the quartz API. That's not just because we need to test high-level functions (like IGraphBuilder::RenderFile()) but also because, as you've discovered, it's easy to miss important details about how parts work together. These tests are more regression tests than conformance tests. Most of these are in rungraph() [in quartz:filtergraph] and its callers.
Back to the more concrete case: Certainly I'm not trying to advocate that we remove any of the testfilter infrastructure this patch adds. I am suggesting that we instead send frames one at a time instead of using such a loop. Although frankly, after looking at the later patches in the series I'm not even necessarily sure that's better. [I will note, though, that it would be nice to test the frames returned from GetBitmapBits() a little closer to where the test loop is introduced, if not in the same patch, so that I have that context immediately.] It may also make sense to send just one frame instead of looping, just for a little extra simplicity.
Thanks for the information :-)
I think the loop is needed to catch regressions as noticed. I should probably keep the loop in the first patch, but only send black pixels all the time, until the GetBitmapData tests, where I will convert it to what we have now with the varying pattern, so it matches the verification.
Would that be acceptable as a compromise?
My point is largely that the kind of regressions you're talking about would probably have been detectable with the second, separate test I was recommending.
Note that one problem with keeping the tests as they are now is that there's not a good way to check what happens if you call GetBitmapBits() before a sample has reached the sample grabber. This is more than a little important, as it's quite possible that either the media detector or the sample grabber is supposed to wait.
Well I rewrote the tests and I believe I found bugs with the sample grabber. It should definitely wait for a new sample when flushing, I think, but I'm investigating it.
Note that EnterBitmapGrabMode / GetBitmapBits will stall until a sample is first received. Originally, this was due to Pause only actually pausing after receiving a sample (which is correct behavior) since the +strmbase log on Windows was filled with GetState polling for it. But this doesn't work when flushing.
On 10/26/20 8:35 AM, Gabriel Ivăncescu wrote:
On 23/10/2020 18:51, Zebediah Figura wrote:
On 10/21/20 8:07 AM, Gabriel Ivăncescu wrote:
On 20/10/2020 22:56, Zebediah Figura wrote:
On 10/20/20 12:05 PM, Gabriel Ivăncescu wrote:
Thanks for the review.
On 20/10/2020 19:46, Zebediah Figura wrote:
On 10/19/20 11:48 AM, Gabriel Ivăncescu wrote: > We fill the video pattern with something that varies between lines and > columns, to test it properly later (including the scaling algorithm). > > Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com > --- > dlls/qedit/tests/mediadet.c | 337 +++++++++++++++++++++++++++++++++++- > 1 file changed, 333 insertions(+), 4 deletions(-) > > diff --git a/dlls/qedit/tests/mediadet.c b/dlls/qedit/tests/mediadet.c > index dc83bb9..010b746 100644 > --- a/dlls/qedit/tests/mediadet.c > +++ b/dlls/qedit/tests/mediadet.c > @@ -136,6 +136,11 @@ struct testfilter > struct strmbase_filter filter; > struct strmbase_source source; > IMediaSeeking IMediaSeeking_iface; > + > + BOOL bitmap_grab_mode; > + const GUID *time_format; > + LONGLONG cur_pos; > + HANDLE thread; > }; > > static inline struct testfilter *impl_from_strmbase_filter(struct strmbase_filter *iface) > @@ -158,10 +163,103 @@ static void testfilter_destroy(struct strmbase_filter *iface) > strmbase_filter_cleanup(&filter->filter); > } > > +static DWORD WINAPI testfilter_frame_thread(void *arg) > +{ > + REFERENCE_TIME start_time, end_time; > + struct testfilter *filter = arg; > + IMemAllocator *allocator; > + IMediaSample *sample; > + unsigned i; > + HRESULT hr; > + DWORD fill; > + BYTE *data; > + > + hr = IMemInputPin_GetAllocator(filter->source.pMemInputPin, &allocator); > + ok(hr == S_OK, "Got hr %#x.\n", hr); > + > + start_time = (filter->cur_pos == 0xdeadbeef) ? 0 : filter->cur_pos; > + while (hr == S_OK) > + { > + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); > + if (hr == VFW_E_NOT_COMMITTED) > + { > + IMemAllocator_Commit(allocator); > + hr = IMemAllocator_GetBuffer(allocator, &sample, NULL, NULL, 0); > + } > + ok(hr == S_OK, "Got hr %#x.\n", hr); > + > + hr = IMediaSample_GetPointer(sample, &data); > + ok(hr == S_OK, "Got hr %#x.\n", hr); > + > + fill = (start_time / 10000 & 0xffffff) ^ 0xccaabb; > + for (i = 0; i < 640 * 480 * 3; i += 3) > + { > + data[i] = fill ^ i; > + data[i + 1] = fill >> 8 ^ i; > + data[i + 2] = fill >> 16 ^ i; > + } > + > + hr = IMediaSample_SetActualDataLength(sample, 640 * 480 * 3); > + ok(hr == S_OK, "Got hr %#x.\n", hr); > + > + end_time = start_time + 400000; > + hr = IMediaSample_SetTime(sample, &start_time, &end_time); > + ok(hr == S_OK, "Got hr %#x.\n", hr); > + start_time = end_time; > + > + if (winetest_debug > 1) trace("%04x: Sending frame.\n", GetCurrentThreadId()); > + hr = IMemInputPin_Receive(filter->source.pMemInputPin, sample); > + if (winetest_debug > 1) trace("%04x: Returned %#x.\n", GetCurrentThreadId(), hr); > + > + IMediaSample_Release(sample); > + } > + > + IMemAllocator_Release(allocator); > + return hr; > +}
Why spawn a thread that loops like this, instead of sending individual frames? In particular, the latter obviates the need to manually flush the stream when seeking.
I can see value in trying to emulate a realistic filter, but it seems easier to me to just render a test AVI file and let the media detector insert a built-in one. A separate test that exercises the functions as a program would actually use them seems quite welcome, in fact.
Actually I uncovered several self-made bugs while implementing it (as I was not very familiar with quartz) which were apparent mostly because of implementing this like a realistic filter. So I think it's useful.
For example this implicitly tests the condition that the media detector stops the stream before seeking, else the sample grabber would still hold the previous sample and have a race condition.
That seems suspicious; I'd expect that a flush should be sufficient to achieve that.
It's possible there's a bug in the Sample Grabber, because it doesn't handle flushing at all, and I don't know if it should. But as I mentioned in the other reply to the EnterBitmapGrabMode implementation, this uncovers the bug and Windows does seem to put a Stop() when seeking (if I trace +strmbase on the test).
If Windows does fully stop the filter, then that's fine, but that should also be tested if possible, probably by checking "filter->state" when seeking.
Ok on more investigation Windows seems to pause the filter *before* the seek operation, for some reason. But that's not so important now because I think I found a bug in the sample grabber anyway...
That's something that the filter graph does, actually. I don't know the reason for it, but we do implement it; see MediaSeeking_SetPositions().
Rendering to a test AVI file doesn't seem that ideal, unless you mean rendering it within the test itself? But I thought, from last patches I sent about qedit, that the focus was to use custom filters as much as possible to be able to test them better, and AVI files were just needed for put_Filename only.
quartz is complicated, not just because it uses a complicated system of callbacks, but also because it has a *lot* of moving parts, most of which can be system components but also can be application components. Hence one of the things I try to do, when writing quartz tests, is to try to test every single moving piece, one at a time. I try to validate every *non-obvious* implementation detail that I reasonably can, mostly with the exception of exhaustively testing error handling (e.g. in general I don't think it's worth testing whether the code checks for S_OK or SUCCEEDED(), especially if a potential failure can be flagged by an ERR/WARN message anyway) where functions are "not supposed to" fail.
When doing this kind of testing, I want to isolate the causes of a return value—sometimes if only for clarity—and often this means using our own custom filters instead of system ones. For example, it makes it clearer that the video renderer is the one responsible for holding up the graph during preroll if we don't have any *other* system filters in the graph at the time.
These tests are focused mostly on proving the implementation correct, though of course they help to catch regressions as well. At the same time, using custom filters also often means testing real-world behaviour. It's not rare for applications to insert their own filters at any point in the pipeline (source, sink, transform, parser).
On the other hand, there's also some value in testing very high-level usage of the quartz API. That's not just because we need to test high-level functions (like IGraphBuilder::RenderFile()) but also because, as you've discovered, it's easy to miss important details about how parts work together. These tests are more regression tests than conformance tests. Most of these are in rungraph() [in quartz:filtergraph] and its callers.
Back to the more concrete case: Certainly I'm not trying to advocate that we remove any of the testfilter infrastructure this patch adds. I am suggesting that we instead send frames one at a time instead of using such a loop. Although frankly, after looking at the later patches in the series I'm not even necessarily sure that's better. [I will note, though, that it would be nice to test the frames returned from GetBitmapBits() a little closer to where the test loop is introduced, if not in the same patch, so that I have that context immediately.] It may also make sense to send just one frame instead of looping, just for a little extra simplicity.
Thanks for the information :-)
I think the loop is needed to catch regressions as noticed. I should probably keep the loop in the first patch, but only send black pixels all the time, until the GetBitmapData tests, where I will convert it to what we have now with the varying pattern, so it matches the verification.
Would that be acceptable as a compromise?
My point is largely that the kind of regressions you're talking about would probably have been detectable with the second, separate test I was recommending.
Note that one problem with keeping the tests as they are now is that there's not a good way to check what happens if you call GetBitmapBits() before a sample has reached the sample grabber. This is more than a little important, as it's quite possible that either the media detector or the sample grabber is supposed to wait.
Well I rewrote the tests and I believe I found bugs with the sample grabber. It should definitely wait for a new sample when flushing, I think, but I'm investigating it.
Note that EnterBitmapGrabMode / GetBitmapBits will stall until a sample is first received. Originally, this was due to Pause only actually pausing after receiving a sample (which is correct behavior) since the +strmbase log on Windows was filled with GetState polling for it. But this doesn't work when flushing.
FWIW it wasn't a bug in the sample grabber, but in all renderers (and thus, strmbase), including the null renderer. I sent tests for the sample grabber, and a patch that fixes that behavior for all renderers.