From: Brendan McGrath <bmcgrath@codeweavers.com> --- dlls/quartz/Makefile.in | 2 +- dlls/quartz/colorconv.c | 361 +++++++++++++++++++++++++++++++++- dlls/quartz/tests/colorconv.c | 23 +-- 3 files changed, 359 insertions(+), 27 deletions(-) diff --git a/dlls/quartz/Makefile.in b/dlls/quartz/Makefile.in index 98aac7c5553..54d559a12d8 100644 --- a/dlls/quartz/Makefile.in +++ b/dlls/quartz/Makefile.in @@ -1,6 +1,6 @@ MODULE = quartz.dll IMPORTLIB = quartz -IMPORTS = strmiids dxguid strmbase uuid dsound msacm32 msvfw32 ole32 oleaut32 rpcrt4 user32 gdi32 advapi32 winmm msvcrt dmoguids wmcodecdspuuid +IMPORTS = mfplat strmiids dxguid strmbase uuid dsound msacm32 msvfw32 ole32 oleaut32 rpcrt4 user32 gdi32 advapi32 winmm msvcrt dmoguids wmcodecdspuuid DELAYIMPORTS = ddraw VER_PRODUCTVERSION = 6,5,1,902 diff --git a/dlls/quartz/colorconv.c b/dlls/quartz/colorconv.c index c918417e7b1..252bca50acc 100644 --- a/dlls/quartz/colorconv.c +++ b/dlls/quartz/colorconv.c @@ -21,6 +21,7 @@ #include "quartz_private.h" #include "mediaobj.h" +#include "mfapi.h" #include "vfw.h" #include "wmcodecdsp.h" @@ -38,8 +39,129 @@ struct color_converter struct strmbase_sink sink; IMediaObject *dmo; + LONG sample_output_stride; + LONG dmo_output_stride; + ULONG dmo_output_sample_size; }; +struct buffer_for_sample +{ + IMediaBuffer IMediaBuffer_iface; + LONG refcount; + + IMediaSample *sample; +}; + +struct buffer +{ + IMediaBuffer IMediaBuffer_iface; + LONG refcount; + + BYTE *data; + DWORD length; + DWORD max_length; +}; + +static struct buffer *buffer_from_IMediaBuffer(IMediaBuffer *iface) +{ + return CONTAINING_RECORD(iface, struct buffer, IMediaBuffer_iface); +} + +static HRESULT WINAPI buffer_QueryInterface(IMediaBuffer *iface, REFIID iid, void **out) +{ + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IMediaBuffer)) + { + *out = iface; + } + else + { + *out = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)(*out)); + return S_OK; +} + +static ULONG WINAPI buffer_AddRef(IMediaBuffer *iface) +{ + struct buffer *buffer = buffer_from_IMediaBuffer(iface); + ULONG refcount; + + refcount = InterlockedIncrement(&buffer->refcount); + + return refcount; +} + +static ULONG WINAPI buffer_Release(IMediaBuffer *iface) +{ + struct buffer *buffer = buffer_from_IMediaBuffer(iface); + ULONG refcount; + + refcount = InterlockedDecrement(&buffer->refcount); + + if (!refcount) + { + free(buffer->data); + free(buffer); + } + + return refcount; +} + +static HRESULT WINAPI buffer_SetLength(IMediaBuffer *iface, DWORD length) +{ + struct buffer *buffer = buffer_from_IMediaBuffer(iface); + + buffer->length = length; + + return S_OK; +} + +static HRESULT WINAPI buffer_GetMaxLength(IMediaBuffer *iface, DWORD *max_length) +{ + struct buffer *buffer = buffer_from_IMediaBuffer(iface); + + *max_length = buffer->max_length; + + return S_OK; +} + +static HRESULT WINAPI buffer_GetBufferAndLength(IMediaBuffer *iface, BYTE **data, DWORD *length) +{ + struct buffer *buffer = buffer_from_IMediaBuffer(iface); + + *length = buffer->length; + if (data) + *data = buffer->data; + + return S_OK; +} + +static IMediaBufferVtbl buffer_vtbl = +{ + buffer_QueryInterface, + buffer_AddRef, + buffer_Release, + buffer_SetLength, + buffer_GetMaxLength, + buffer_GetBufferAndLength, +}; + +static struct buffer *create_buffer(DWORD max_length) +{ + struct buffer *buffer; + + buffer = calloc(1, sizeof(*buffer)); + buffer->IMediaBuffer_iface.lpVtbl = &buffer_vtbl; + buffer->refcount = 1; + + buffer->data = malloc(max_length); + buffer->max_length = max_length; + + return buffer; +} + struct subtype { const GUID *guid; @@ -111,6 +233,110 @@ static void populate_output_dmo_mt( dmo_mt->lSampleSize = video_info->bmiHeader.biSizeImage; } +static struct buffer_for_sample *buffer_for_sample_from_IMediaBuffer(IMediaBuffer *iface) +{ + return CONTAINING_RECORD(iface, struct buffer_for_sample, IMediaBuffer_iface); +} + +static HRESULT WINAPI buffer_for_sample_QueryInterface(IMediaBuffer *iface, REFIID iid, void **out) +{ + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IMediaBuffer)) + { + *out = iface; + } + else + { + *out = NULL; + WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown *)(*out)); + return S_OK; +} + +static ULONG WINAPI buffer_for_sample_AddRef(IMediaBuffer *iface) +{ + struct buffer_for_sample *buffer = buffer_for_sample_from_IMediaBuffer(iface); + ULONG refcount; + + refcount = InterlockedIncrement(&buffer->refcount); + + return refcount; +} + +static ULONG WINAPI buffer_for_sample_Release(IMediaBuffer *iface) +{ + struct buffer_for_sample *buffer = buffer_for_sample_from_IMediaBuffer(iface); + ULONG refcount; + + refcount = InterlockedDecrement(&buffer->refcount); + + if (!refcount) + { + IMediaSample_Release(buffer->sample); + free(buffer); + } + + return refcount; +} + +static HRESULT WINAPI buffer_for_sample_SetLength(IMediaBuffer *iface, DWORD len) +{ + struct buffer_for_sample *buffer = buffer_for_sample_from_IMediaBuffer(iface); + + TRACE("iface %p, len %lu.\n", iface, len); + + return IMediaSample_SetActualDataLength(buffer->sample, len); +} + +static HRESULT WINAPI buffer_for_sample_GetMaxLength(IMediaBuffer *iface, DWORD *len) +{ + struct buffer_for_sample *buffer = buffer_for_sample_from_IMediaBuffer(iface); + + TRACE("iface %p, len %p.\n", iface, len); + + *len = IMediaSample_GetSize(buffer->sample); + return S_OK; +} + +static HRESULT WINAPI buffer_for_sample_GetBufferAndLength(IMediaBuffer *iface, BYTE **data, DWORD *len) +{ + struct buffer_for_sample *buffer = buffer_for_sample_from_IMediaBuffer(iface); + + TRACE("iface %p, data %p, len %p.\n", iface, data, len); + + *len = IMediaSample_GetActualDataLength(buffer->sample); + if (data) + return IMediaSample_GetPointer(buffer->sample, data); + return S_OK; +} + +static const IMediaBufferVtbl buffer_for_sample_vtbl = +{ + buffer_for_sample_QueryInterface, + buffer_for_sample_AddRef, + buffer_for_sample_Release, + buffer_for_sample_SetLength, + buffer_for_sample_GetMaxLength, + buffer_for_sample_GetBufferAndLength, +}; + +static struct buffer_for_sample *create_buffer_for_sample(IMediaSample *sample) +{ + struct buffer_for_sample *buffer; + + buffer = calloc(1, sizeof(*buffer)); + buffer->IMediaBuffer_iface.lpVtbl = &buffer_for_sample_vtbl; + buffer->refcount = 1; + + IMediaSample_AddRef(buffer->sample = sample); + + return buffer; +} + static struct color_converter *impl_from_strmbase_filter(struct strmbase_filter *iface) { return CONTAINING_RECORD(iface, struct color_converter, filter); @@ -149,13 +375,6 @@ static HRESULT color_sink_connect(struct strmbase_sink *iface, IPin *peer, const return hr; } -static const struct strmbase_sink_ops sink_ops = -{ - .base.pin_query_interface = color_sink_query_interface, - .base.pin_query_accept = color_sink_query_accept, - .sink_connect = color_sink_connect, -}; - static HRESULT WINAPI color_source_DecideBufferSize( struct strmbase_source *iface, IMemAllocator *alloc, ALLOCATOR_PROPERTIES *props) { @@ -171,6 +390,10 @@ static HRESULT WINAPI color_source_DecideBufferSize( if (FAILED(hr = IMediaObject_SetOutputType(filter->dmo, 0, &dmo_mt, 0))) return hr; + filter->sample_output_stride = calculate_stride(&((VIDEOINFOHEADER *)iface->pin.mt.pbFormat)->bmiHeader); + filter->dmo_output_stride = calculate_stride(&((VIDEOINFOHEADER *)dmo_mt.pbFormat)->bmiHeader); + filter->dmo_output_sample_size = dmo_mt.lSampleSize; + if (!props->cbAlign) props->cbAlign = 1; @@ -188,6 +411,130 @@ static HRESULT WINAPI color_source_DecideBufferSize( return IMemAllocator_SetProperties(alloc, props, &actual); } +static HRESULT WINAPI color_sink_Receive(struct strmbase_sink *iface, IMediaSample *src_sample) +{ + struct color_converter *filter = impl_from_strmbase_filter(iface->pin.filter); + BITMAPINFOHEADER *input_bmi_header, *output_bmi_header; + struct buffer_for_sample *src_buffer; + DMO_OUTPUT_DATA_BUFFER output; + struct buffer *dst_buffer; + BITMAPINFOHEADER *header; + IMediaSample *dst_sample; + long output_image_size; + BYTE *src_buff, *dest; + LONGLONG start, stop; + DWORD flags, status; + LONG dst_size; + UINT32 *data; + HRESULT hr; + int i; + + /* We do not expect pin connection state to change while the filter is + * running. This guarantee is necessary, since otherwise we would have to + * take the filter lock, and we can't take the filter lock from a streaming + * thread. */ + if (!filter->source.pMemInputPin) + { + WARN("Source is not connected, returning VFW_E_NOT_CONNECTED.\n"); + return VFW_E_NOT_CONNECTED; + } + + if (filter->filter.state == State_Stopped) + return VFW_E_WRONG_STATE; + + if (filter->sink.flushing) + return S_FALSE; + + hr = IMediaSample_GetPointer(src_sample, &src_buff); + if (FAILED(hr)) + { + ERR("Failed to get input buffer pointer, hr %#lx.\n", hr); + return hr; + } + + if (FAILED(hr = IMemAllocator_GetBuffer(filter->source.pAllocator, &dst_sample, NULL, NULL, 0))) + { + ERR("Failed to get sample, hr %#lx.\n", hr); + return hr; + } + + header = &((VIDEOINFOHEADER *)filter->source.pin.mt.pbFormat)->bmiHeader; + output_image_size = calculate_stride(header) * header->biHeight; + dst_size = IMediaSample_GetSize(dst_sample); + if (dst_size < output_image_size) + { + ERR("Sample size is too small (%ld < %lu).\n", dst_size, output_image_size); + IMediaSample_Release(dst_sample); + return E_FAIL; + } + + hr = IMediaSample_GetTime(src_sample, &start, &stop); + + if (hr == S_OK) + { + IMediaSample_SetTime(dst_sample, &start, &stop); + flags = DMO_INPUT_DATA_BUFFERF_TIME | DMO_INPUT_DATA_BUFFERF_TIMELENGTH; + } + else if (hr == VFW_S_NO_STOP_TIME) + { + IMediaSample_SetTime(dst_sample, &start, NULL); + flags = DMO_INPUT_DATA_BUFFERF_TIME; + } + else + { + IMediaSample_SetTime(dst_sample, NULL, NULL); + flags = 0; + } + + /* perform color conversion */ + src_buffer = create_buffer_for_sample(src_sample); + hr = IMediaObject_ProcessInput(filter->dmo, 0, &src_buffer->IMediaBuffer_iface, flags, start, stop - start); + IMediaBuffer_Release(&src_buffer->IMediaBuffer_iface); + + input_bmi_header = &((VIDEOINFOHEADER *)filter->sink.pin.mt.pbFormat)->bmiHeader; + output_bmi_header = &((VIDEOINFOHEADER *)filter->source.pin.mt.pbFormat)->bmiHeader; + dst_buffer = create_buffer(filter->dmo_output_sample_size); + memset(&output, 0, sizeof(output)); + output.pBuffer = &dst_buffer->IMediaBuffer_iface; + hr = IMediaObject_ProcessOutput(filter->dmo, 0, 1, &output, &status); + if (input_bmi_header->biBitCount < output_bmi_header->biBitCount && output_bmi_header->biBitCount == 32) + { + /* Fix the value of the alpha channel. DMO uses 0xff, whilst quartz uses 0x00. */ + data = (UINT32 *)dst_buffer->data; + + for (i = 0; i < input_bmi_header->biHeight * input_bmi_header->biWidth; i++) + *data++ &= 0xffffff; + } + IMediaSample_GetPointer(dst_sample, &dest); + if (filter->sample_output_stride < 0) + dest += -filter->sample_output_stride * (input_bmi_header->biHeight - 1); + + MFCopyImage(dest, filter->sample_output_stride, dst_buffer->data, filter->dmo_output_stride, + input_bmi_header->biWidth * (output_bmi_header->biBitCount / 8), input_bmi_header->biHeight); + IMediaBuffer_Release(output.pBuffer); + + IMediaSample_SetActualDataLength(dst_sample, output_image_size); + + IMediaSample_SetPreroll(dst_sample, (IMediaSample_IsPreroll(src_sample) == S_OK)); + IMediaSample_SetDiscontinuity(dst_sample, (IMediaSample_IsDiscontinuity(src_sample) == S_OK)); + IMediaSample_SetSyncPoint(dst_sample, TRUE); + + hr = IMemInputPin_Receive(filter->source.pMemInputPin, dst_sample); + if (hr != S_OK && hr != VFW_E_NOT_CONNECTED) + ERR("Failed to send sample, hr %#lx.\n", hr); + + IMediaSample_Release(dst_sample); + return hr; +} + +static const struct strmbase_sink_ops sink_ops = +{ + .base.pin_query_interface = color_sink_query_interface, + .base.pin_query_accept = color_sink_query_accept, + .pfnReceive = color_sink_Receive, + .sink_connect = color_sink_connect, +}; + static HRESULT color_source_query_interface(struct strmbase_pin *iface, REFIID iid, void **out) { struct color_converter *filter = impl_from_strmbase_filter(iface->filter); diff --git a/dlls/quartz/tests/colorconv.c b/dlls/quartz/tests/colorconv.c index 4807c7601f8..c63fafb5422 100644 --- a/dlls/quartz/tests/colorconv.c +++ b/dlls/quartz/tests/colorconv.c @@ -1721,7 +1721,7 @@ static void test_filter_state(IMediaControl *control, IBaseFilter *filter) #define EXP_DISCONTINUITY (1 << 4) #define EXP_UNDEFINED_TIME_END (1 << 5) -#define TODO_TIME (1 << 0) +#define TODO_TIME_END (1 << 0) #define TODO_MEDIA_TIME (1 << 1) #define TODO_SYNC_POINT (1 << 2) #define TODO_PREROLL (1 << 3) @@ -1740,7 +1740,7 @@ static void test_sample_processing( tests[] = { { - .todo_flags = TODO_SYNC_POINT | TODO_TIME + .todo_flags = TODO_SYNC_POINT }, { .flags = SET_TIME_START | SET_MEDIA_TIME | SET_SYNC_POINT, @@ -1749,7 +1749,7 @@ static void test_sample_processing( .media_time_end = 20000, .sync_point = TRUE, .expected_flags = EXP_TIME | EXP_UNDEFINED_TIME_END | EXP_MEDIA_TIME | EXP_SYNC_POINT, - .todo_flags = TODO_MEDIA_TIME + .todo_flags = TODO_MEDIA_TIME | TODO_TIME_END }, { .flags = SET_TIME, @@ -1915,13 +1915,7 @@ static void test_sample_processing( sink_allocator->expect_get_media_type = TRUE; sink_allocator->media_type_checked = FALSE; hr = IMemInputPin_Receive(input, sample); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); - if (hr != S_OK) - { - winetest_pop_context(); - continue; - } ok(sink_allocator->sample_refcount == 1, "Got sample refcount %ld.\n", sink_allocator->sample_refcount); todo_wine @@ -1931,10 +1925,10 @@ static void test_sample_processing( sink_allocator->expect_set_time = FALSE; sink_allocator->expect_get_media_type = FALSE; - todo_wine_if(tests[i].todo_flags & TODO_TIME) if (tests[i].expected_flags & EXP_TIME) { ok(sink_allocator->ts_set, "Time start should be set.\n"); + todo_wine_if(tests[i].todo_flags & TODO_TIME_END) ok(sink_allocator->te_set, "Time end should be set.\n"); ok(sink_allocator->time_start == tests[i].time_start, "Got start time %I64d.\n", sink_allocator->time_start); @@ -2020,7 +2014,6 @@ static void test_sample_processing( sink_allocator->expect_get_media_type = TRUE; sink_allocator->media_type_checked = FALSE; hr = IMemInputPin_Receive(input, sample); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); todo_wine ok(sink_allocator->media_type_checked, "Expected media type to have been checked.\n"); @@ -2032,10 +2025,7 @@ static void test_sample_processing( sample = testsink->sample; testsink->sample = NULL; - todo_wine ok(sample != NULL, "Expected out peer sample.\n"); - if (sample == NULL) - goto skip_test; hr = IMediaSample_GetPointer(sample, &buff); ok(hr == S_OK, "Get hr %#lx.\n", hr); @@ -2126,7 +2116,6 @@ static void test_sample_processing( IMediaSample_Release(sample); ok(sink_allocator->sample_refcount == 0, "Got sample refcount %ld.\n", sink_allocator->sample_refcount); -skip_test: hr = IMemAllocator_Decommit(allocator); ok(hr == S_OK, "Got hr %#lx.\n", hr); @@ -2185,9 +2174,7 @@ static void test_streaming_events(IMediaControl *control, IPin *sink, IMemInputP sink_allocator->media_type_checked = FALSE; sink_allocator->expect_get_media_type = TRUE; hr = IMemInputPin_Receive(input, sample); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); - todo_wine ok(testsink->sample != NULL, "Expected to receive sample.\n"); todo_wine ok(sink_allocator->media_type_checked, "Expected media type to have been checked.\n"); @@ -2216,9 +2203,7 @@ static void test_streaming_events(IMediaControl *control, IPin *sink, IMemInputP sink_allocator->expect_get_media_type = TRUE; sink_allocator->expect_get_buffer = TRUE; hr = IMemInputPin_Receive(input, sample); - todo_wine ok(hr == S_OK, "Got hr %#lx.\n", hr); - todo_wine ok(testsink->sample != NULL, "Expected to receive sample.\n"); todo_wine ok(sink_allocator->media_type_checked, "Expected media type to have been checked.\n"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11679