From: Derek Lesho dlesho@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com ---
- split into several helpers to improve readability; - removed PLAYING state check, since we don't support flushing anyway; - fixed some misplaced media type calls; - removed incorrect IsMediaTypeSupported() usage, last argument is not guaranteed to be set. Default implementation does not set it;
Derek, please take a look if my changes break anything.
dlls/mfreadwrite/main.c | 203 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 7 deletions(-)
diff --git a/dlls/mfreadwrite/main.c b/dlls/mfreadwrite/main.c index 56ec036089..d3c10a4d11 100644 --- a/dlls/mfreadwrite/main.c +++ b/dlls/mfreadwrite/main.c @@ -94,6 +94,7 @@ struct media_stream { IMFMediaStream *stream; IMFMediaType *current; + IMFTransform *decoder; DWORD id; CRITICAL_SECTION cs; CONDITION_VARIABLE sample_event; @@ -576,6 +577,8 @@ static ULONG WINAPI src_reader_Release(IMFSourceReader *iface) IMFMediaStream_Release(stream->stream); if (stream->current) IMFMediaType_Release(stream->current); + if (stream->decoder) + IMFTransform_Release(stream->decoder); DeleteCriticalSection(&stream->cs);
LIST_FOR_EACH_ENTRY_SAFE(ptr, next, &stream->samples, struct sample, entry) @@ -669,18 +672,15 @@ static HRESULT WINAPI src_reader_SetStreamSelection(IMFSourceReader *iface, DWOR return S_OK; }
-static HRESULT WINAPI src_reader_GetNativeMediaType(IMFSourceReader *iface, DWORD index, DWORD type_index, - IMFMediaType **type) +static HRESULT source_reader_get_native_media_type(struct source_reader *reader, DWORD index, DWORD type_index, + IMFMediaType **type) { - struct source_reader *reader = impl_from_IMFSourceReader(iface); IMFMediaTypeHandler *handler; IMFStreamDescriptor *sd; IMFMediaType *src_type; BOOL selected; HRESULT hr;
- TRACE("%p, %#x, %#x, %p.\n", iface, index, type_index, type); - switch (index) { case MF_SOURCE_READER_FIRST_VIDEO_STREAM: @@ -717,6 +717,16 @@ static HRESULT WINAPI src_reader_GetNativeMediaType(IMFSourceReader *iface, DWOR return hr; }
+static HRESULT WINAPI src_reader_GetNativeMediaType(IMFSourceReader *iface, DWORD index, DWORD type_index, + IMFMediaType **type) +{ + struct source_reader *reader = impl_from_IMFSourceReader(iface); + + TRACE("%p, %#x, %#x, %p.\n", iface, index, type_index, type); + + return source_reader_get_native_media_type(reader, index, type_index, type); +} + static HRESULT WINAPI src_reader_GetCurrentMediaType(IMFSourceReader *iface, DWORD index, IMFMediaType **type) { struct source_reader *reader = impl_from_IMFSourceReader(iface); @@ -751,6 +761,184 @@ static HRESULT WINAPI src_reader_GetCurrentMediaType(IMFSourceReader *iface, DWO return hr; }
+static HRESULT source_reader_get_source_type_handler(struct source_reader *reader, DWORD index, + IMFMediaTypeHandler **handler) +{ + IMFStreamDescriptor *sd; + BOOL selected; + HRESULT hr; + + if (FAILED(hr = IMFPresentationDescriptor_GetStreamDescriptorByIndex(reader->descriptor, index, &selected, &sd))) + return hr; + + hr = IMFStreamDescriptor_GetMediaTypeHandler(sd, handler); + IMFStreamDescriptor_Release(sd); + + return hr; +} + +static HRESULT source_reader_set_compatible_media_type(struct source_reader *reader, DWORD index, IMFMediaType *type) +{ + IMFMediaTypeHandler *type_handler; + IMFMediaType *native_type; + BOOL type_set = FALSE; + unsigned int i = 0; + DWORD flags; + HRESULT hr; + + if (FAILED(hr = IMFMediaType_IsEqual(type, reader->streams[index].current, &flags))) + return hr; + + if (!(flags & MF_MEDIATYPE_EQUAL_MAJOR_TYPES)) + return MF_E_INVALIDMEDIATYPE; + + /* No need for a decoder or type change. */ + if (flags & MF_MEDIATYPE_EQUAL_FORMAT_TYPES) + return S_OK; + + if (FAILED(hr = source_reader_get_source_type_handler(reader, index, &type_handler))) + return hr; + + while (!type_set && IMFMediaTypeHandler_GetMediaTypeByIndex(type_handler, i++, &native_type) == S_OK) + { + static const DWORD compare_flags = MF_MEDIATYPE_EQUAL_MAJOR_TYPES | MF_MEDIATYPE_EQUAL_FORMAT_TYPES; + + if (SUCCEEDED(IMFMediaType_IsEqual(native_type, type, &flags)) && (flags & compare_flags) == compare_flags) + { + if ((type_set = SUCCEEDED(IMFMediaTypeHandler_SetCurrentMediaType(type_handler, native_type)))) + IMFMediaType_CopyAllItems(native_type, (IMFAttributes *)reader->streams[index].current); + } + + IMFMediaType_Release(native_type); + } + + IMFMediaTypeHandler_Release(type_handler); + + return type_set ? S_OK : S_FALSE; +} + +static HRESULT source_reader_configure_decoder(struct source_reader *reader, DWORD index, const CLSID *clsid, + IMFMediaType *input_type, IMFMediaType *output_type) +{ + IMFMediaTypeHandler *type_handler; + IMFTransform *transform = NULL; + IMFMediaType *type = NULL; + DWORD flags; + HRESULT hr; + int i = 0; + + if (FAILED(hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, &IID_IMFTransform, (void **)&transform))) + { + WARN("Failed to create transform object, hr %#x.\n", hr); + return hr; + } + + if (FAILED(hr = IMFTransform_SetInputType(transform, 0, input_type, 0))) + { + WARN("Failed to set decoder input type, hr %#x.\n", hr); + IMFTransform_Release(transform); + return hr; + } + + /* Find the relevant output type. */ + while (IMFTransform_GetOutputAvailableType(transform, 0, i++, &type) == S_OK) + { + flags = 0; + + if (SUCCEEDED(IMFMediaType_IsEqual(type, output_type, &flags))) + { + if (flags & MF_MEDIATYPE_EQUAL_FORMAT_TYPES) + { + if (SUCCEEDED(IMFTransform_SetOutputType(transform, 0, type, 0))) + { + if (SUCCEEDED(source_reader_get_source_type_handler(reader, index, &type_handler))) + { + IMFMediaTypeHandler_SetCurrentMediaType(type_handler, input_type); + IMFMediaTypeHandler_Release(type_handler); + } + + if (FAILED(hr = IMFMediaType_CopyAllItems(type, (IMFAttributes *)reader->streams[index].current))) + WARN("Failed to copy attributes, hr %#x.\n", hr); + IMFMediaType_Release(type); + + if (reader->streams[index].decoder) + IMFTransform_Release(reader->streams[index].decoder); + + reader->streams[index].decoder = transform; + + return S_OK; + } + } + } + + IMFMediaType_Release(type); + } + + WARN("Failed to find suitable decoder output type.\n"); + + IMFTransform_Release(transform); + + return MF_E_TOPO_CODEC_NOT_FOUND; +} + +static HRESULT source_reader_create_decoder_for_stream(struct source_reader *reader, DWORD index, IMFMediaType *output_type) +{ + MFT_REGISTER_TYPE_INFO in_type, out_type; + CLSID *clsids, mft_clsid, category; + unsigned int i = 0, count; + IMFMediaType *input_type; + HRESULT hr; + + /* TODO: should we check if the source type is compressed? */ + + if (FAILED(hr = IMFMediaType_GetMajorType(output_type, &out_type.guidMajorType))) + return hr; + + if (IsEqualGUID(&out_type.guidMajorType, &MFMediaType_Video)) + { + category = MFT_CATEGORY_VIDEO_DECODER; + } + else if (IsEqualGUID(&out_type.guidMajorType, &MFMediaType_Audio)) + { + category = MFT_CATEGORY_AUDIO_DECODER; + } + else + { + WARN("Unhandled major type %s.\n", debugstr_guid(&out_type.guidMajorType)); + return MF_E_TOPO_CODEC_NOT_FOUND; + } + + if (FAILED(hr = IMFMediaType_GetGUID(output_type, &MF_MT_SUBTYPE, &out_type.guidSubtype))) + return hr; + + in_type.guidMajorType = out_type.guidMajorType; + + while (source_reader_get_native_media_type(reader, index, i++, &input_type) == S_OK) + { + if (SUCCEEDED(IMFMediaType_GetGUID(input_type, &MF_MT_SUBTYPE, &in_type.guidSubtype))) + { + count = 0; + if (SUCCEEDED(hr = MFTEnum(category, 0, &in_type, &out_type, NULL, &clsids, &count)) && count) + { + mft_clsid = clsids[0]; + CoTaskMemFree(clsids); + + /* TODO: Should we iterate over all of them? */ + if (SUCCEEDED(source_reader_configure_decoder(reader, index, &mft_clsid, input_type, output_type))) + { + IMFMediaType_Release(input_type); + return S_OK; + } + + } + } + + IMFMediaType_Release(input_type); + } + + return MF_E_TOPO_CODEC_NOT_FOUND; +} + static HRESULT WINAPI src_reader_SetCurrentMediaType(IMFSourceReader *iface, DWORD index, DWORD *reserved, IMFMediaType *type) { @@ -774,11 +962,12 @@ static HRESULT WINAPI src_reader_SetCurrentMediaType(IMFSourceReader *iface, DWO if (index >= reader->stream_count) return MF_E_INVALIDSTREAMNUMBER;
- /* FIXME: validate passed type and current presentation state. */ + /* FIXME: setting the output type while streaming should trigger a flush */
EnterCriticalSection(&reader->cs);
- hr = IMFMediaType_CopyAllItems(type, (IMFAttributes *)reader->streams[index].current); + if ((hr = source_reader_set_compatible_media_type(reader, index, type)) == S_FALSE) + hr = source_reader_create_decoder_for_stream(reader, index, type);
LeaveCriticalSection(&reader->cs);
From: Derek Lesho dlesho@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com ---
- moved decoding calls to event handler, queue now always contains processed samples; - removed draining logic for now, I believe we can do that explicitly on ReadSample() or on EndOfStream instead. Same helper could be used for sync/async read and EOS event; - added a check for output transform flags;
dlls/mfreadwrite/main.c | 81 +++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-)
diff --git a/dlls/mfreadwrite/main.c b/dlls/mfreadwrite/main.c index d3c10a4d11..052575c100 100644 --- a/dlls/mfreadwrite/main.c +++ b/dlls/mfreadwrite/main.c @@ -366,6 +366,71 @@ static ULONG WINAPI source_reader_stream_events_callback_Release(IMFAsyncCallbac return IMFSourceReader_Release(&reader->IMFSourceReader_iface); }
+static void source_reader_queue_sample(struct media_stream *stream, IMFSample *sample) +{ + struct sample *pending_sample; + + if (!sample) + return; + + pending_sample = heap_alloc(sizeof(*pending_sample)); + pending_sample->sample = sample; + IMFSample_AddRef(pending_sample->sample); + + list_add_tail(&stream->samples, &pending_sample->entry); +} + +static HRESULT source_reader_process_sample(struct media_stream *stream, IMFSample *sample) +{ + MFT_OUTPUT_STREAM_INFO stream_info = { 0 }; + MFT_OUTPUT_DATA_BUFFER out_buffer; + DWORD status; + HRESULT hr; + + if (!stream->decoder) + { + source_reader_queue_sample(stream, sample); + return S_OK; + } + + /* It's assumed that decoder has 1 input and 1 output, both id's are 0. */ + + if (FAILED(hr = IMFTransform_GetOutputStreamInfo(stream->decoder, 0, &stream_info))) + { + WARN("Failed to get output stream info, hr %#x.\n", hr); + return hr; + } + + if (!(stream_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES)) + { + FIXME("Transform does not provide samples.\n"); + return E_NOTIMPL; + } + + while (hr == S_OK) + { + memset(&out_buffer, 0, sizeof(out_buffer)); + if (SUCCEEDED(hr = IMFTransform_ProcessOutput(stream->decoder, 0, 1, &out_buffer, &status))) + { + source_reader_queue_sample(stream, out_buffer.pSample); + if (out_buffer.pSample) + IMFSample_Release(out_buffer.pSample); + if (out_buffer.pEvents) + IMFCollection_Release(out_buffer.pEvents); + } + } + + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) + { + if (FAILED(hr = IMFTransform_ProcessInput(stream->decoder, 0, sample, 0))) + WARN("Transform failed to process input, hr %#x.\n", hr); + } + else + WARN("Transform failed to process output, hr %#x.\n", hr); + + return hr; +} + static HRESULT source_reader_media_sample_handler(struct source_reader *reader, IMFMediaStream *stream, IMFMediaEvent *event) { @@ -393,21 +458,14 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, { if (id == reader->streams[i].id) { - struct sample *pending_sample; - - if (!(pending_sample = heap_alloc(sizeof(*pending_sample)))) - { - hr = E_OUTOFMEMORY; - goto failed; - } + EnterCriticalSection(&reader->streams[i].cs);
- pending_sample->sample = sample; - IMFSample_AddRef(pending_sample->sample); + hr = source_reader_process_sample(&reader->streams[i], sample);
- EnterCriticalSection(&reader->streams[i].cs); - list_add_tail(&reader->streams[i].samples, &pending_sample->entry); LeaveCriticalSection(&reader->streams[i].cs);
+ /* FIXME: propagate processing errors? */ + WakeAllConditionVariable(&reader->streams[i].sample_event);
break; @@ -417,7 +475,6 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, if (i == reader->stream_count) WARN("Stream with id %#x was not present in presentation descriptor.\n", id);
-failed: IMFSample_Release(sample);
return hr;
On 2020-03-20 07:16, Nikolay Sivov wrote:
From: Derek Lesho dlesho@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
- moved decoding calls to event handler, queue now always contains processed samples;
- removed draining logic for now, I believe we can do that explicitly on ReadSample() or on EndOfStream instead. Same helper could be used for sync/async read and EOS event;
FWIW, the draining logic is crucial, as every h.264 decoder I'm aware of won't output the most recently sent samples until a drain is activated. In the case of the Microsoft H.264 decoder, 20 samples are buffered until drain is called, and with the openh264 decoder, it's ~3 samples. With the current logic, the application will get the EOS flag well before all the samples have run out, and the decoder will have a good amount of uncompressed samples just sitting there.
added a check for output transform flags;
dlls/mfreadwrite/main.c | 81 +++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-)
diff --git a/dlls/mfreadwrite/main.c b/dlls/mfreadwrite/main.c index d3c10a4d11..052575c100 100644 --- a/dlls/mfreadwrite/main.c +++ b/dlls/mfreadwrite/main.c @@ -366,6 +366,71 @@ static ULONG WINAPI source_reader_stream_events_callback_Release(IMFAsyncCallbac return IMFSourceReader_Release(&reader->IMFSourceReader_iface); }
+static void source_reader_queue_sample(struct media_stream *stream, IMFSample *sample) +{
- struct sample *pending_sample;
- if (!sample)
return;
- pending_sample = heap_alloc(sizeof(*pending_sample));
- pending_sample->sample = sample;
- IMFSample_AddRef(pending_sample->sample);
- list_add_tail(&stream->samples, &pending_sample->entry);
+}
+static HRESULT source_reader_process_sample(struct media_stream *stream, IMFSample *sample) +{
- MFT_OUTPUT_STREAM_INFO stream_info = { 0 };
- MFT_OUTPUT_DATA_BUFFER out_buffer;
- DWORD status;
- HRESULT hr;
- if (!stream->decoder)
- {
source_reader_queue_sample(stream, sample);
return S_OK;
- }
- /* It's assumed that decoder has 1 input and 1 output, both id's are 0. */
- if (FAILED(hr = IMFTransform_GetOutputStreamInfo(stream->decoder, 0, &stream_info)))
- {
WARN("Failed to get output stream info, hr %#x.\n", hr);
return hr;
- }
- if (!(stream_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES))
This can probably be changed to MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES, not too important though.
- {
FIXME("Transform does not provide samples.\n");
return E_NOTIMPL;
- }
- while (hr == S_OK)
- {
memset(&out_buffer, 0, sizeof(out_buffer));
if (SUCCEEDED(hr = IMFTransform_ProcessOutput(stream->decoder, 0, 1, &out_buffer, &status)))
{
source_reader_queue_sample(stream, out_buffer.pSample);
if (out_buffer.pSample)
IMFSample_Release(out_buffer.pSample);
if (out_buffer.pEvents)
IMFCollection_Release(out_buffer.pEvents);
}
- }
- if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
- {
if (FAILED(hr = IMFTransform_ProcessInput(stream->decoder, 0, sample, 0)))
WARN("Transform failed to process input, hr %#x.\n", hr);
- }
- else
WARN("Transform failed to process output, hr %#x.\n", hr);
This logic is wrong, there will always be a leftover sample, since you don't call ProcessOutput after giving the transform the incoming sample from the source. The way this is supposed to work is that you first empty any pending outputs, to ensure that the MFT has space for more input, then input the sample, then redo the ProcessOutput step. Additionally, since we're buffering all the output samples, we could simplify this and just call ProcessInput first, since it should never return MF_E_NOTACCEPTING.
- return hr;
+}
- static HRESULT source_reader_media_sample_handler(struct source_reader *reader, IMFMediaStream *stream, IMFMediaEvent *event) {
@@ -393,21 +458,14 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, { if (id == reader->streams[i].id) {
struct sample *pending_sample;
if (!(pending_sample = heap_alloc(sizeof(*pending_sample))))
{
hr = E_OUTOFMEMORY;
goto failed;
}
EnterCriticalSection(&reader->streams[i].cs);
pending_sample->sample = sample;
IMFSample_AddRef(pending_sample->sample);
hr = source_reader_process_sample(&reader->streams[i], sample);
EnterCriticalSection(&reader->streams[i].cs);
list_add_tail(&reader->streams[i].samples, &pending_sample->entry); LeaveCriticalSection(&reader->streams[i].cs);
/* FIXME: propagate processing errors? */
WakeAllConditionVariable(&reader->streams[i].sample_event); break;
@@ -417,7 +475,6 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, if (i == reader->stream_count) WARN("Stream with id %#x was not present in presentation descriptor.\n", id);
-failed: IMFSample_Release(sample);
return hr;
On 3/20/20 6:00 PM, Derek Lesho wrote:
On 2020-03-20 07:16, Nikolay Sivov wrote:
From: Derek Lesho dlesho@codeweavers.com
Signed-off-by: Nikolay Sivov nsivov@codeweavers.com
- moved decoding calls to event handler, queue now always contains
processed samples;
- removed draining logic for now, I believe we can do that explicitly
on ReadSample() or on EndOfStream instead. Same helper could be used for sync/async read and EOS event;
FWIW, the draining logic is crucial, as every h.264 decoder I'm aware of won't output the most recently sent samples until a drain is activated. In the case of the Microsoft H.264 decoder, 20 samples are buffered until drain is called, and with the openh264 decoder, it's ~3 samples. With the current logic, the application will get the EOS flag well before all the samples have run out, and the decoder will have a good amount of uncompressed samples just sitting there.
Makes sense, I'll do that then.
- added a check for output transform flags;
dlls/mfreadwrite/main.c | 81 +++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 12 deletions(-)
diff --git a/dlls/mfreadwrite/main.c b/dlls/mfreadwrite/main.c index d3c10a4d11..052575c100 100644 --- a/dlls/mfreadwrite/main.c +++ b/dlls/mfreadwrite/main.c @@ -366,6 +366,71 @@ static ULONG WINAPI source_reader_stream_events_callback_Release(IMFAsyncCallbac return IMFSourceReader_Release(&reader->IMFSourceReader_iface); } +static void source_reader_queue_sample(struct media_stream *stream, IMFSample *sample) +{ + struct sample *pending_sample;
+ if (!sample) + return;
+ pending_sample = heap_alloc(sizeof(*pending_sample)); + pending_sample->sample = sample; + IMFSample_AddRef(pending_sample->sample);
+ list_add_tail(&stream->samples, &pending_sample->entry); +}
+static HRESULT source_reader_process_sample(struct media_stream *stream, IMFSample *sample) +{ + MFT_OUTPUT_STREAM_INFO stream_info = { 0 }; + MFT_OUTPUT_DATA_BUFFER out_buffer; + DWORD status; + HRESULT hr;
+ if (!stream->decoder) + { + source_reader_queue_sample(stream, sample); + return S_OK; + }
+ /* It's assumed that decoder has 1 input and 1 output, both id's are 0. */
+ if (FAILED(hr = IMFTransform_GetOutputStreamInfo(stream->decoder, 0, &stream_info))) + { + WARN("Failed to get output stream info, hr %#x.\n", hr); + return hr; + }
+ if (!(stream_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES))
This can probably be changed to MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES, not too important though.
I remember testing this specifically for session logic, and as I remember it responded only on PROVIDES, I'll take another look.
+ { + FIXME("Transform does not provide samples.\n"); + return E_NOTIMPL; + }
+ while (hr == S_OK) + { + memset(&out_buffer, 0, sizeof(out_buffer)); + if (SUCCEEDED(hr = IMFTransform_ProcessOutput(stream->decoder, 0, 1, &out_buffer, &status))) + { + source_reader_queue_sample(stream, out_buffer.pSample); + if (out_buffer.pSample) + IMFSample_Release(out_buffer.pSample); + if (out_buffer.pEvents) + IMFCollection_Release(out_buffer.pEvents); + } + }
+ if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) + { + if (FAILED(hr = IMFTransform_ProcessInput(stream->decoder, 0, sample, 0))) + WARN("Transform failed to process input, hr %#x.\n", hr); + } + else + WARN("Transform failed to process output, hr %#x.\n", hr);
This logic is wrong, there will always be a leftover sample, since you don't call ProcessOutput after giving the transform the incoming sample from the source. The way this is supposed to work is that you first empty any pending outputs, to ensure that the MFT has space for more input, then input the sample, then redo the ProcessOutput step. Additionally, since we're buffering all the output samples, we could simplify this and just call ProcessInput first, since it should never return MF_E_NOTACCEPTING.
I see, so failed ProcessInput() would mean early return.
+ return hr; +}
static HRESULT source_reader_media_sample_handler(struct source_reader *reader, IMFMediaStream *stream, IMFMediaEvent *event) { @@ -393,21 +458,14 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, { if (id == reader->streams[i].id) { - struct sample *pending_sample;
- if (!(pending_sample = heap_alloc(sizeof(*pending_sample)))) - { - hr = E_OUTOFMEMORY; - goto failed; - }
- EnterCriticalSection(&reader->streams[i].cs);
- pending_sample->sample = sample; - IMFSample_AddRef(pending_sample->sample); + hr = source_reader_process_sample(&reader->streams[i], sample); - EnterCriticalSection(&reader->streams[i].cs); - list_add_tail(&reader->streams[i].samples, &pending_sample->entry); LeaveCriticalSection(&reader->streams[i].cs); + /* FIXME: propagate processing errors? */
WakeAllConditionVariable(&reader->streams[i].sample_event); break; @@ -417,7 +475,6 @@ static HRESULT source_reader_media_sample_handler(struct source_reader *reader, if (i == reader->stream_count) WARN("Stream with id %#x was not present in presentation descriptor.\n", id); -failed: IMFSample_Release(sample); return hr;