[PATCH v2 0/4] MR10864: winegstreamer: Normalize PTS values so the stream starts at zero.
mpegpsdemux uses raw PTS values from the stream, which often start at a non-zero timestamp. To avoid an initial delay, we normalize the PTS by subtracting the first raw value so that the buffer timeline begins at zero. Without this adjustment, the first sample would not be presented until its original (non-zero) timestamp. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59565 -- v2: winegstreamer: Rebase PTS values so the stream starts at zero. winegstreamer: Change wg_parser_create argument from bool to bit flags. quartz/tests: Add a timestamp test for the first sample. winegstreamer: Make sure every stream has a buffer on initialization. https://gitlab.winehq.org/wine/wine/-/merge_requests/10864
From: Akihiro Sagawa <sagawa.aki@gmail.com> --- dlls/winegstreamer/wg_parser.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index da414af6134..0fcb213d07a 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1756,6 +1756,19 @@ static NTSTATUS wg_parser_connect(void *args) stream->enabled = true; } + /* Make sure every stream has a buffer. */ + for (i = 0; i < parser->stream_count; ++i) + { + GstBuffer *buffer; + struct wg_parser_stream *stream = parser->streams[i]; + buffer = wait_parser_stream_buffer(parser, stream); + if (stream->enabled && !stream->eos && !buffer) + { + GST_ERROR("Failed to get a buffer, stream #%d.", stream->number); + goto out; + } + } + pthread_mutex_unlock(&parser->mutex); parser->next_offset = 0; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10864
From: Akihiro Sagawa <sagawa.aki@gmail.com> --- dlls/quartz/tests/mpegsplit.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dlls/quartz/tests/mpegsplit.c b/dlls/quartz/tests/mpegsplit.c index bebb7d39fcc..32d33b1fbef 100644 --- a/dlls/quartz/tests/mpegsplit.c +++ b/dlls/quartz/tests/mpegsplit.c @@ -1084,6 +1084,7 @@ struct testfilter HANDLE eos_event; unsigned int sample_count, eos_count, new_segment_count, byte_count; REFERENCE_TIME segment_start, segment_end_min, segment_end_max, seek_start, seek_end; + REFERENCE_TIME sample_start; LONGLONG read_position; }; @@ -1212,6 +1213,9 @@ static HRESULT WINAPI testsink_Receive(struct strmbase_sink *iface, IMediaSample if (winetest_debug > 1) trace("%04lx: Got sample with timestamps %I64d-%I64d.\n", GetCurrentThreadId(), start, end); + if (filter->sample_count == 0 && filter->byte_count == 0) + filter->sample_start = start; + ok(filter->new_segment_count, "Expected NewSegment() before Receive().\n"); IPin_QueryInterface(iface->pin.peer, &IID_IMediaSeeking, (void **)&seeking); @@ -2102,6 +2106,11 @@ static void test_video_file(void) ok(testsink_video.byte_count == 1214, "Video sink got %u bytes.\n", testsink_video.byte_count); ok(testsink_audio.byte_count == 8777, "Audio sink got %u bytes.\n", testsink_audio.byte_count); + /* Native uses 55 for the first sample timestamp, but there's no need to copy the exact value. */ + todo_wine ok(min(testsink_video.sample_start, testsink_audio.sample_start) < 100, + "The first sample timestamp is off by more 100, video %I64d and audio %I64d.\n", + testsink_video.sample_start, testsink_audio.sample_start); + IAMStreamSelect_Release(sel); IPin_Release(source_video); IPin_Release(source_audio); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10864
From: Akihiro Sagawa <sagawa.aki@gmail.com> --- dlls/winegstreamer/gst_private.h | 2 +- dlls/winegstreamer/main.c | 6 +++--- dlls/winegstreamer/media_source.c | 2 +- dlls/winegstreamer/quartz_parser.c | 14 +++++++------- dlls/winegstreamer/unixlib.h | 7 ++++++- dlls/winegstreamer/wg_parser.c | 2 +- dlls/winegstreamer/wm_reader.c | 4 ++-- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/dlls/winegstreamer/gst_private.h b/dlls/winegstreamer/gst_private.h index 7a5c2f3f4b0..71ef17985ce 100644 --- a/dlls/winegstreamer/gst_private.h +++ b/dlls/winegstreamer/gst_private.h @@ -70,7 +70,7 @@ HRESULT wg_sample_queue_create(struct wg_sample_queue **out); void wg_sample_queue_destroy(struct wg_sample_queue *queue); void wg_sample_queue_flush(struct wg_sample_queue *queue, bool all); -wg_parser_t wg_parser_create(bool output_compressed); +wg_parser_t wg_parser_create(UINT32 flags); /* see wg_parser_create_flag */ void wg_parser_destroy(wg_parser_t parser); HRESULT wg_parser_connect(wg_parser_t parser, uint64_t file_size, const WCHAR *uri); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index 6a8bd4b6413..5cafcc1491f 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -160,16 +160,16 @@ static HRESULT wg_media_type_to_mf(const struct wg_media_type *wg_media_type, IM return E_NOTIMPL; } -wg_parser_t wg_parser_create(bool output_compressed) +wg_parser_t wg_parser_create(UINT32 flags) { struct wg_parser_create_params params = { - .output_compressed = output_compressed, + .flags = flags, .err_on = ERR_ON(quartz), .warn_on = WARN_ON(quartz), }; - TRACE("output_compressed %d.\n", output_compressed); + TRACE("flags %#x.\n", flags); if (WINE_UNIX_CALL(unix_wg_parser_create, ¶ms)) return 0; diff --git a/dlls/winegstreamer/media_source.c b/dlls/winegstreamer/media_source.c index 0735e74bdf3..145dbfd09cf 100644 --- a/dlls/winegstreamer/media_source.c +++ b/dlls/winegstreamer/media_source.c @@ -1670,7 +1670,7 @@ static HRESULT media_source_create(struct object_context *context, IMFMediaSourc if (FAILED(hr = MFAllocateWorkQueue(&object->async_commands_queue))) goto fail; - if (!(parser = wg_parser_create(FALSE))) + if (!(parser = wg_parser_create(0))) { hr = E_OUTOFMEMORY; goto fail; diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index 38971f2fcb9..cb8bb702bb1 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -1878,16 +1878,16 @@ static HRESULT decodebin_parser_source_get_media_type(struct parser_source *pin, return VFW_S_NO_MORE_ITEMS; } -static HRESULT parser_create(BOOL output_compressed, struct parser **parser) +static HRESULT parser_create(UINT32 flags, struct parser **parser) { struct parser *object; if (!(object = calloc(1, sizeof(*object)))) return E_OUTOFMEMORY; - object->output_compressed = output_compressed; + object->output_compressed = flags & WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED; - if (!(object->wg_parser = wg_parser_create(output_compressed))) + if (!(object->wg_parser = wg_parser_create(flags))) { free(object); return E_OUTOFMEMORY; @@ -1907,7 +1907,7 @@ HRESULT decodebin_parser_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr; - if (FAILED(hr = parser_create(FALSE, &object))) + if (FAILED(hr = parser_create(0, &object))) return hr; strmbase_filter_init(&object->filter, outer, &CLSID_decodebin_parser, &filter_ops); @@ -2481,7 +2481,7 @@ HRESULT wave_parser_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr; - if (FAILED(hr = parser_create(TRUE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED, &object))) return hr; strmbase_filter_init(&object->filter, outer, &CLSID_WAVEParser, &filter_ops); @@ -2590,7 +2590,7 @@ HRESULT avi_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr; - if (FAILED(hr = parser_create(TRUE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED, &object))) return hr; strmbase_filter_init(&object->filter, outer, &CLSID_AviSplitter, &filter_ops); @@ -2752,7 +2752,7 @@ HRESULT mpeg_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr; - if (FAILED(hr = parser_create(TRUE, &object))) + if (FAILED(hr = parser_create(WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED, &object))) return hr; strmbase_filter_init(&object->filter, outer, &CLSID_MPEG1Splitter, &mpeg_splitter_ops); diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index f545df7ad0b..42d206c32e7 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -218,10 +218,15 @@ struct wg_init_gstreamer_params UINT8 err_on; }; +enum wg_parser_create_flag +{ + WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED = 1, +}; + struct wg_parser_create_params { wg_parser_t parser; - UINT8 output_compressed; + UINT32 flags; UINT8 err_on; UINT8 warn_on; }; diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 0fcb213d07a..05cb9e5d9fc 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -1893,7 +1893,7 @@ static NTSTATUS wg_parser_create(void *args) pthread_cond_init(&parser->init_cond, NULL); pthread_cond_init(&parser->read_cond, NULL); pthread_cond_init(&parser->read_done_cond, NULL); - parser->output_compressed = params->output_compressed; + parser->output_compressed = params->flags & WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED; parser->err_on = params->err_on; parser->warn_on = params->warn_on; GST_DEBUG("Created winegstreamer parser %p.", parser); diff --git a/dlls/winegstreamer/wm_reader.c b/dlls/winegstreamer/wm_reader.c index 6a800e4448c..6a1ca98424c 100644 --- a/dlls/winegstreamer/wm_reader.c +++ b/dlls/winegstreamer/wm_reader.c @@ -1494,7 +1494,7 @@ static HRESULT init_stream(struct wm_reader *reader) HRESULT hr; WORD i; - if (!(wg_parser = wg_parser_create(FALSE))) + if (!(wg_parser = wg_parser_create(0))) return E_OUTOFMEMORY; reader->wg_parser = wg_parser; @@ -1621,7 +1621,7 @@ static HRESULT reinit_stream(struct wm_reader *reader, bool read_compressed) wg_parser_destroy(reader->wg_parser); reader->wg_parser = 0; - if (!(wg_parser = wg_parser_create(read_compressed))) + if (!(wg_parser = wg_parser_create(read_compressed ? WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED : 0))) return E_OUTOFMEMORY; reader->wg_parser = wg_parser; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10864
From: Akihiro Sagawa <sagawa.aki@gmail.com> mpegpsdemux uses raw PTS values from the stream, which often start at a non-zero timestamp. To avoid an initial delay, we rebase the PTS by subtracting the first raw value so that the buffer timeline begins at zero for the MPEG-I splitter in DirectShow. Without this adjustment, the first sample would not be presented until its original (non-zero) timestamp in MPEG-1 system stream. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59565 --- dlls/quartz/tests/mpegsplit.c | 2 +- dlls/winegstreamer/quartz_parser.c | 3 ++- dlls/winegstreamer/unixlib.h | 1 + dlls/winegstreamer/wg_parser.c | 26 ++++++++++++++++++++++++-- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/dlls/quartz/tests/mpegsplit.c b/dlls/quartz/tests/mpegsplit.c index 32d33b1fbef..06e32828e86 100644 --- a/dlls/quartz/tests/mpegsplit.c +++ b/dlls/quartz/tests/mpegsplit.c @@ -2107,7 +2107,7 @@ static void test_video_file(void) ok(testsink_audio.byte_count == 8777, "Audio sink got %u bytes.\n", testsink_audio.byte_count); /* Native uses 55 for the first sample timestamp, but there's no need to copy the exact value. */ - todo_wine ok(min(testsink_video.sample_start, testsink_audio.sample_start) < 100, + ok(min(testsink_video.sample_start, testsink_audio.sample_start) < 100, "The first sample timestamp is off by more 100, video %I64d and audio %I64d.\n", testsink_video.sample_start, testsink_audio.sample_start); diff --git a/dlls/winegstreamer/quartz_parser.c b/dlls/winegstreamer/quartz_parser.c index cb8bb702bb1..1cc2466fe2e 100644 --- a/dlls/winegstreamer/quartz_parser.c +++ b/dlls/winegstreamer/quartz_parser.c @@ -2752,7 +2752,8 @@ HRESULT mpeg_splitter_create(IUnknown *outer, IUnknown **out) struct parser *object; HRESULT hr; - if (FAILED(hr = parser_create(WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED, &object))) + if (FAILED(hr = parser_create(WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED | + WG_PARSER_CREATE_FLAG_PTS_REBASED, &object))) return hr; strmbase_filter_init(&object->filter, outer, &CLSID_MPEG1Splitter, &mpeg_splitter_ops); diff --git a/dlls/winegstreamer/unixlib.h b/dlls/winegstreamer/unixlib.h index 42d206c32e7..89775a4af79 100644 --- a/dlls/winegstreamer/unixlib.h +++ b/dlls/winegstreamer/unixlib.h @@ -221,6 +221,7 @@ struct wg_init_gstreamer_params enum wg_parser_create_flag { WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED = 1, + WG_PARSER_CREATE_FLAG_PTS_REBASED = 2, }; struct wg_parser_create_params diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 05cb9e5d9fc..67c378955ac 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -68,6 +68,7 @@ struct wg_parser GstPad *my_src; guint64 file_size, start_offset, next_offset, stop_offset; + GstClockTime base_pts; guint64 next_pull_offset; gchar *uri; @@ -76,7 +77,7 @@ struct wg_parser pthread_mutex_t mutex; pthread_cond_t init_cond; - bool output_compressed; + bool output_compressed, pts_rebased; bool no_more_pads, has_duration, error; bool err_on, warn_on; @@ -366,8 +367,17 @@ static NTSTATUS wg_parser_stream_get_buffer(void *args) * that this will need modification to wg_parser_stream_notify_qos() as * well. */ + /* Because mpegpsdemux reports a non-zero PTS for the earliest buffer among + * all streams, we rebase the PTS by subtracting base_pts so that our + * stream starts at zero for the MPEG-I Splitter in quartz. */ + if ((wg_buffer->has_pts = GST_BUFFER_PTS_IS_VALID(buffer))) - wg_buffer->pts = GST_BUFFER_PTS(buffer) / 100; + { + if (GST_CLOCK_TIME_IS_VALID(parser->base_pts) && parser->pts_rebased) + wg_buffer->pts = (GST_BUFFER_PTS(buffer) - parser->base_pts) / 100; + else + wg_buffer->pts = GST_BUFFER_PTS(buffer) / 100; + } if ((wg_buffer->has_duration = GST_BUFFER_DURATION_IS_VALID(buffer))) wg_buffer->duration = GST_BUFFER_DURATION(buffer) / 100; wg_buffer->discontinuity = GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DISCONT); @@ -725,6 +735,16 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu if (!stream->has_buffer) { stream->has_buffer = true; + + /* Keep the earliest PTS for adjusting. */ + if (GST_BUFFER_PTS_IS_VALID(buffer) && + (GST_BUFFER_PTS(buffer) < parser->base_pts)) + { + parser->base_pts = GST_BUFFER_PTS(buffer); + GST_LOG("Updated base PTS to %" GST_TIME_FORMAT ".", + GST_TIME_ARGS(parser->base_pts)); + } + pthread_cond_signal(&parser->init_cond); } @@ -1894,8 +1914,10 @@ static NTSTATUS wg_parser_create(void *args) pthread_cond_init(&parser->read_cond, NULL); pthread_cond_init(&parser->read_done_cond, NULL); parser->output_compressed = params->flags & WG_PARSER_CREATE_FLAG_OUTPUT_COMPRESSED; + parser->pts_rebased = params->flags & WG_PARSER_CREATE_FLAG_PTS_REBASED; parser->err_on = params->err_on; parser->warn_on = params->warn_on; + parser->base_pts = GST_CLOCK_TIME_NONE; GST_DEBUG("Created winegstreamer parser %p.", parser); params->parser = (wg_parser_t)(ULONG_PTR)parser; return S_OK; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10864
Updated. Manual testing confirms that Media Foundation and the MPEG-2 Demultiplexer (mpg2splt.ax) do not normalize PTS values. Therefore, we only rebase PTS values when using the MPEG-1 splitter. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10864#note_145716
``` + /* Make sure every stream has a buffer. */ + for (i = 0; i < parser->stream_count; ++i) + { + GstBuffer *buffer; + struct wg_parser_stream *stream = parser->streams[i]; + buffer = wait_parser_stream_buffer(parser, stream); + if (stream->enabled && !stream->eos && !buffer) + { + GST_ERROR("Failed to get a buffer, stream #%d.", stream->number); + goto out; + } + } + ``` That's not quite what I meant; rather we should be altering the existing condition so that it just looks like "while (!parser->error && !stream->has_buffer)". -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10864#note_145852
For some reason, After I apply this patch with newest implement on the master. Atelier Ryza 3 will stuck at the intro video. If Apply old implement, game will normally play the intro video. Atelier Ryza 3 should be mp4, not mpeg1/2 so ... -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10864#note_146042
participants (4)
-
Akihiro Sagawa -
Akihiro Sagawa (@sgwaki) -
Chunhao Hung (@otakuxtom) -
Elizabeth Figura (@zfigura)