Signed-off-by: Derek Lesho dlesho@codeweavers.com --- v9: - Fixed sometimes issuing MEEndOfStream while responding to the last sample request. - Add test for the previous fix. - Add workaround in mfreadwrite tests to deal with gstreamer audio sample sizes from .wav files differing from those on windows. --- dlls/mfplat/tests/mfplat.c | 19 +++++-- dlls/mfreadwrite/tests/mfplat.c | 16 +++++- dlls/winegstreamer/gst_private.h | 1 + dlls/winegstreamer/media_source.c | 94 ++++++++++++++++++++++++++++++- dlls/winegstreamer/mfplat.c | 69 +++++++++++++++++++++++ 5 files changed, 191 insertions(+), 8 deletions(-)
diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index a080035e477..b084cb29ab2 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -615,19 +615,15 @@ todo_wine get_event((IMFMediaEventGenerator *)video_stream, MEStreamStarted, NULL); sample_count = 10;
- /* Request one beyond EOS, otherwise EndOfStream isn't queued. */ - for (i = 0; i <= sample_count; ++i) + for (i = 0; i < sample_count; ++i) { hr = IMFMediaStream_RequestSample(video_stream, NULL); if (i == sample_count) break; -todo_wine ok(hr == S_OK, "Failed to request sample %u, hr %#x.\n", i + 1, hr); if (hr != S_OK) break; } - if (FAILED(hr)) - goto skip_source_tests;
for (i = 0; i < sample_count; ++i) { @@ -661,14 +657,25 @@ todo_wine }
if (i == sample_count) + { + IMFMediaEvent *event; + + /* MEEndOfStream isn't queued until after a one request beyond the last frame is submitted */ + Sleep(100); + hr = IMFMediaEventGenerator_GetEvent((IMFMediaEventGenerator *)video_stream, MF_EVENT_FLAG_NO_WAIT, &event); + ok (hr == MF_E_NO_EVENTS_AVAILABLE, "Unexpected hr %#x.\n", hr); + + hr = IMFMediaStream_RequestSample(video_stream, NULL); + ok (hr == S_OK || hr == MF_E_END_OF_STREAM, "Unexpected hr %#x.\n", hr); get_event((IMFMediaEventGenerator *)video_stream, MEEndOfStream, NULL); + } +
hr = IMFMediaStream_RequestSample(video_stream, NULL); ok(hr == MF_E_END_OF_STREAM, "Unexpected hr %#x.\n", hr);
get_event((IMFMediaEventGenerator *)mediasource, MEEndOfPresentation, NULL);
-skip_source_tests: IMFMediaStream_Release(video_stream); IMFMediaTypeHandler_Release(handler); IMFPresentationDescriptor_Release(descriptor); diff --git a/dlls/mfreadwrite/tests/mfplat.c b/dlls/mfreadwrite/tests/mfplat.c index 0e5053f905f..ef25bbcb3eb 100644 --- a/dlls/mfreadwrite/tests/mfplat.c +++ b/dlls/mfreadwrite/tests/mfplat.c @@ -727,7 +727,6 @@ static void test_source_reader(void)
hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &actual_index, &stream_flags, ×tamp, &sample); -todo_wine ok(hr == S_OK, "Failed to get a sample, hr %#x.\n", hr); if (hr != S_OK) goto skip_read_sample; @@ -753,8 +752,23 @@ todo_wine ×tamp, &sample); ok(hr == S_OK, "Failed to get a sample, hr %#x.\n", hr); ok(actual_index == 0, "Unexpected stream index %u\n", actual_index); + /* TODO: gstreamer outputs .wav sample in increments of 4096, instead of 4410 */ +todo_wine +{ ok(stream_flags == MF_SOURCE_READERF_ENDOFSTREAM, "Unexpected stream flags %#x.\n", stream_flags); ok(!sample, "Unexpected sample object.\n"); +} + if(!stream_flags) + { + IMFSample_Release(sample); + + hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, &actual_index, &stream_flags, + ×tamp, &sample); + ok(hr == S_OK, "Failed to get a sample, hr %#x.\n", hr); + ok(actual_index == 0, "Unexpected stream index %u\n", actual_index); + ok(stream_flags == MF_SOURCE_READERF_ENDOFSTREAM, "Unexpected stream flags %#x.\n", stream_flags); + ok(!sample, "Unexpected sample object.\n"); + }
hr = IMFSourceReader_ReadSample(reader, MF_SOURCE_READER_FIRST_AUDIO_STREAM, MF_SOURCE_READER_CONTROLF_DRAIN, &actual_index, &stream_flags, ×tamp, &sample); diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 3a8020b6760..28e424439d8 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -80,6 +80,7 @@ extern HRESULT mfplat_get_class_object(REFCLSID rclsid, REFIID riid, void **obj) HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN; IMFMediaType *mf_media_type_from_caps(const GstCaps *caps) DECLSPEC_HIDDEN; GstCaps *caps_from_mf_media_type(IMFMediaType *type) DECLSPEC_HIDDEN; +IMFSample *mf_sample_from_gst_buffer(GstBuffer *in) DECLSPEC_HIDDEN;
HRESULT winegstreamer_stream_handler_create(REFIID riid, void **obj) DECLSPEC_HIDDEN;
diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 9c6e7bfd539..dbc656a24da 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -58,11 +58,13 @@ struct media_stream STREAM_RUNNING, } state; DWORD stream_id; + BOOL eos; };
enum source_async_op { SOURCE_ASYNC_START, + SOURCE_ASYNC_REQUEST_SAMPLE, };
struct source_async_command @@ -78,6 +80,11 @@ struct source_async_command GUID format; PROPVARIANT position; } start; + struct + { + struct media_stream *stream; + IUnknown *token; + } request_sample; } u; };
@@ -311,6 +318,8 @@ static void start_pipeline(struct media_source *source, struct source_async_comm GST_SEEK_TYPE_SET, position->u.hVal.QuadPart / 100, GST_SEEK_TYPE_NONE, 0);
gst_pad_push_event(stream->my_sink, seek_event); + + stream->eos = FALSE; }
if (selected) @@ -334,6 +343,61 @@ static void start_pipeline(struct media_source *source, struct source_async_comm gst_element_set_state(source->container, GST_STATE_PLAYING); }
+static void dispatch_end_of_presentation(struct media_source *source) +{ + PROPVARIANT empty = {.vt = VT_EMPTY}; + unsigned int i; + + /* A stream has ended, check whether all have */ + for (i = 0; i < source->stream_count; i++) + { + struct media_stream *stream = source->streams[i]; + + if (stream->state != STREAM_INACTIVE && !stream->eos) + return; + } + + IMFMediaEventQueue_QueueEventParamVar(source->event_queue, MEEndOfPresentation, &GUID_NULL, S_OK, &empty); +} + +static void wait_on_sample(struct media_stream *stream, IUnknown *token) +{ + PROPVARIANT empty_var = {.vt = VT_EMPTY}; + GstSample *gst_sample; + GstBuffer *buffer; + IMFSample *sample; + + TRACE("%p, %p\n", stream, token); + + g_signal_emit_by_name(stream->appsink, "pull-sample", &gst_sample); + if (gst_sample) + { + buffer = gst_sample_get_buffer(gst_sample); + + TRACE("PTS = %llu\n", (unsigned long long int) GST_BUFFER_PTS(buffer)); + + sample = mf_sample_from_gst_buffer(buffer); + gst_sample_unref(gst_sample); + + if (token) + IMFSample_SetUnknown(sample, &MFSampleExtension_Token, token); + + IMFMediaEventQueue_QueueEventParamUnk(stream->event_queue, MEMediaSample, &GUID_NULL, S_OK, (IUnknown *)sample); + IMFSample_Release(sample); + } + else + { + g_object_get(stream->appsink, "eos", &stream->eos, NULL); + if (stream->eos) + { + if (token) + IUnknown_Release(token); + IMFMediaEventQueue_QueueEventParamVar(stream->event_queue, MEEndOfStream, &GUID_NULL, S_OK, &empty_var); + dispatch_end_of_presentation(stream->parent_source); + } + } +} + static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result) { struct media_source *source = impl_from_async_commands_callback_IMFAsyncCallback(iface); @@ -353,6 +417,9 @@ static HRESULT WINAPI source_async_commands_Invoke(IMFAsyncCallback *iface, IMFA case SOURCE_ASYNC_START: start_pipeline(source, command); break; + case SOURCE_ASYNC_REQUEST_SAMPLE: + wait_on_sample(command->u.request_sample.stream, command->u.request_sample.token); + break; }
IUnknown_Release(state); @@ -640,13 +707,37 @@ static HRESULT WINAPI media_stream_GetStreamDescriptor(IMFMediaStream* iface, IM static HRESULT WINAPI media_stream_RequestSample(IMFMediaStream *iface, IUnknown *token) { struct media_stream *stream = impl_from_IMFMediaStream(iface); + struct source_async_command *command; + HRESULT hr;
TRACE("(%p)->(%p)\n", iface, token);
if (stream->state == STREAM_SHUTDOWN) return MF_E_SHUTDOWN;
- return E_NOTIMPL; + if (stream->state == STREAM_INACTIVE) + { + WARN("Stream isn't active\n"); + return MF_E_MEDIA_SOURCE_WRONGSTATE; + } + + if (stream->eos) + { + return MF_E_END_OF_STREAM; + } + + if (SUCCEEDED(hr = source_create_async_op(SOURCE_ASYNC_REQUEST_SAMPLE, &command))) + { + command->u.request_sample.stream = stream; + if (token) + IUnknown_AddRef(token); + command->u.request_sample.token = token; + + /* Once pause support is added, this will need to put into a stream queue, and synchronization will need to be added*/ + hr = MFPutWorkItem(stream->parent_source->async_commands_queue, &stream->parent_source->async_commands_callback, &command->IUnknown_iface); + } + + return hr; }
static const IMFMediaStreamVtbl media_stream_vtbl = @@ -729,6 +820,7 @@ static HRESULT new_media_stream(struct media_source *source, GstPad *pad, DWORD object->stream_id = stream_id;
object->state = STREAM_INACTIVE; + object->eos = FALSE;
if (FAILED(hr = MFCreateEventQueue(&object->event_queue))) goto fail; diff --git a/dlls/winegstreamer/mfplat.c b/dlls/winegstreamer/mfplat.c index d0071cae438..3d224a5accc 100644 --- a/dlls/winegstreamer/mfplat.c +++ b/dlls/winegstreamer/mfplat.c @@ -731,3 +731,72 @@ GstCaps *caps_from_mf_media_type(IMFMediaType *type) FIXME("Unrecognized major type %s\n", debugstr_guid(&major_type)); return NULL; } + +/* IMFSample = GstBuffer + IMFBuffer = GstMemory */ + +/* TODO: Future optimization could be to create a custom + IMFMediaBuffer wrapper around GstMemory, and to utilize + gst_memory_new_wrapped on IMFMediaBuffer data. However, + this wouldn't work if we allow the callers to allocate + the buffers. */ + +IMFSample* mf_sample_from_gst_buffer(GstBuffer *gst_buffer) +{ + IMFMediaBuffer *mf_buffer = NULL; + GstMapInfo map_info = {0}; + LONGLONG duration, time; + BYTE *mapped_buf = NULL; + IMFSample *out = NULL; + HRESULT hr; + + if (FAILED(hr = MFCreateSample(&out))) + goto done; + + duration = GST_BUFFER_DURATION(gst_buffer); + time = GST_BUFFER_PTS(gst_buffer); + + if (FAILED(hr = IMFSample_SetSampleDuration(out, duration / 100))) + goto done; + + if (FAILED(hr = IMFSample_SetSampleTime(out, time / 100))) + goto done; + + if (!gst_buffer_map(gst_buffer, &map_info, GST_MAP_READ)) + { + hr = E_FAIL; + goto done; + } + + if (FAILED(hr = MFCreateMemoryBuffer(map_info.maxsize, &mf_buffer))) + goto done; + + if (FAILED(hr = IMFMediaBuffer_Lock(mf_buffer, &mapped_buf, NULL, NULL))) + goto done; + + memcpy(mapped_buf, map_info.data, map_info.size); + + if (FAILED(hr = IMFMediaBuffer_Unlock(mf_buffer))) + goto done; + + if (FAILED(hr = IMFMediaBuffer_SetCurrentLength(mf_buffer, map_info.size))) + goto done; + + if (FAILED(hr = IMFSample_AddBuffer(out, mf_buffer))) + goto done; + +done: + if (mf_buffer) + IMFMediaBuffer_Release(mf_buffer); + if (map_info.data) + gst_buffer_unmap(gst_buffer, &map_info); + if (FAILED(hr)) + { + ERR("Failed to copy IMFSample to GstBuffer, hr = %#x\n", hr); + if (out) + IMFSample_Release(out); + out = NULL; + } + + return out; +}