[PATCH v2 0/2] MR9710: winegstreamer: Read input in chunks of 256KiB instead of 512KiB.
Supersedes !8183. JR East Train Simulator's IMFByteStream implementation relies on the following two properties: - Reads are always aligned on a 256KiB boundary. - Reads are never larger than 256KiB. When chunking was initially benchmarked[1], a chunk size of 512KiB only performed marginally better than 256KiB, so this change has a negligible performance impact while being more correct. I'm testing using a WAV handler here because it's easy to create the data for it in memory (avoids including a large video file in git), but the same behavior can be observed with other handlers, such as MP4. [1] https://gitlab.winehq.org/wine/wine/-/merge_requests/2390#note_27314 -- v2: winegstreamer: Read input in chunks of 256KiB. mfsrcsnk/tests: Test that byte streams are read in chunks of 0x40000. https://gitlab.winehq.org/wine/wine/-/merge_requests/9710
From: Charlotte Pabst <cpabst@codeweavers.com> Some games rely on this constant. --- dlls/mfsrcsnk/tests/mfsrcsnk.c | 299 +++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/dlls/mfsrcsnk/tests/mfsrcsnk.c b/dlls/mfsrcsnk/tests/mfsrcsnk.c index 03d206a5f55..97049bc3e3c 100644 --- a/dlls/mfsrcsnk/tests/mfsrcsnk.c +++ b/dlls/mfsrcsnk/tests/mfsrcsnk.c @@ -824,6 +824,304 @@ static void test_thinning(void) IMFMediaSource_Release(source); } +struct test_stream +{ + IMFByteStream iface; + LONG refcount; + + IMFByteStream *inner; +}; + +static struct test_stream *impl_from_IMFByteStream(IMFByteStream *iface) +{ + return (struct test_stream *) iface; +} + +static HRESULT WINAPI teststream_QueryInterface(IMFByteStream *iface, REFIID riid, void **obj) +{ + struct test_stream *stream = impl_from_IMFByteStream(iface); + + if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IMFByteStream)) + { + *obj = &stream->iface; + IMFByteStream_AddRef(iface); + return S_OK; + } + + return IMFByteStream_QueryInterface(stream->inner, riid, obj); +} + +static ULONG WINAPI teststream_AddRef(IMFByteStream *iface) +{ + struct test_stream *stream = impl_from_IMFByteStream(iface); + return InterlockedIncrement(&stream->refcount); +} + +static ULONG WINAPI teststream_Release(IMFByteStream *iface) +{ + struct test_stream *stream = impl_from_IMFByteStream(iface); + ULONG refcount = InterlockedDecrement(&stream->refcount); + + if (!refcount) { + IMFByteStream_Release(stream->inner); + free(stream); + } + + return refcount; +} + +static HRESULT WINAPI teststream_GetCapabilities(IMFByteStream *iface, DWORD *capabilities) +{ + return IMFByteStream_GetCapabilities(impl_from_IMFByteStream(iface)->inner, capabilities); +} + +static HRESULT WINAPI teststream_GetLength(IMFByteStream *iface, QWORD *length) +{ + return IMFByteStream_GetLength(impl_from_IMFByteStream(iface)->inner, length); +} + +static HRESULT WINAPI teststream_SetLength(IMFByteStream *iface, QWORD length) +{ + return IMFByteStream_SetLength(impl_from_IMFByteStream(iface)->inner, length); +} + +static HRESULT WINAPI teststream_GetCurrentPosition(IMFByteStream *iface, QWORD *position) +{ + return IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, position); +} + +static HRESULT WINAPI teststream_SetCurrentPosition(IMFByteStream *iface, QWORD position) +{ + todo_wine_if(!(position % 0x40000 == 0)) + ok(position % 0x40000 == 0, "IMFByteStream::SetCurrentPosition pos=%lld should be aligned on 0x40000 boundary.\n", position); + return IMFByteStream_SetCurrentPosition(impl_from_IMFByteStream(iface)->inner, position); +} + +static HRESULT WINAPI teststream_IsEndOfStream(IMFByteStream *iface, BOOL *ret) +{ + return IMFByteStream_IsEndOfStream(impl_from_IMFByteStream(iface)->inner, ret); +} + +static HRESULT WINAPI teststream_Read(IMFByteStream *iface, BYTE *buffer, ULONG size, ULONG *read_len) +{ + QWORD pos = 0; + HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine_if(!(pos % 0x40000 == 0)) + ok(pos % 0x40000 == 0, "IMFByteStream::Read pos=%lld should be aligned on 0x40000 boundary.\n", pos); + todo_wine_if(!(size <= 0x40000)) + ok(size <= 0x40000, "IMFByteStream::BeginRead size=%lu should not be larger than 0x40000.\n", size); + return IMFByteStream_Read(impl_from_IMFByteStream(iface)->inner, buffer, size, read_len); +} + +static HRESULT WINAPI teststream_BeginRead(IMFByteStream *iface, BYTE *data, ULONG size, IMFAsyncCallback *callback, + IUnknown *state) +{ + QWORD pos = 0; + HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine_if(!(pos % 0x40000 == 0)) + ok(pos % 0x40000 == 0, "IMFByteStream::BeginRead pos=%lld should be aligned on 0x40000 boundary.\n", pos); + todo_wine_if(!(size <= 0x40000)) + ok(size <= 0x40000, "IMFByteStream::BeginRead size=%lu should not be larger than 0x40000.\n", size); + return IMFByteStream_BeginRead(impl_from_IMFByteStream(iface)->inner, data, size, callback, state); +} + +static HRESULT WINAPI teststream_EndRead(IMFByteStream *iface, IMFAsyncResult *result, ULONG *byte_read) +{ + return IMFByteStream_EndRead(impl_from_IMFByteStream(iface)->inner, result, byte_read); +} + +static HRESULT WINAPI teststream_Write(IMFByteStream *iface, const BYTE *data, ULONG size, ULONG *written) +{ + return IMFByteStream_Write(impl_from_IMFByteStream(iface)->inner, data, size, written); +} + +static HRESULT WINAPI teststream_BeginWrite(IMFByteStream *iface, const BYTE *data, ULONG size, + IMFAsyncCallback *callback, IUnknown *state) +{ + return IMFByteStream_BeginWrite(impl_from_IMFByteStream(iface)->inner, data, size, callback, state); +} + +static HRESULT WINAPI teststream_EndWrite(IMFByteStream *iface, IMFAsyncResult *result, ULONG *written) +{ + return IMFByteStream_EndWrite(impl_from_IMFByteStream(iface)->inner, result, written); +} + +static HRESULT WINAPI teststream_Seek(IMFByteStream *iface, MFBYTESTREAM_SEEK_ORIGIN origin, LONGLONG offset, + DWORD flags, QWORD *current) +{ + HRESULT ret_hr = IMFByteStream_Seek(impl_from_IMFByteStream(iface)->inner, origin, offset, flags, current); + + QWORD pos = 0; + HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine_if(!(pos % 0x40000 == 0)) + ok(pos % 0x40000 == 0, "IMFByteStream::Seek pos=%lld should be aligned on 0x40000 boundary.\n", pos); + + return ret_hr; +} + +static HRESULT WINAPI teststream_Flush(IMFByteStream *iface) +{ + return IMFByteStream_Flush(impl_from_IMFByteStream(iface)->inner); +} + +static HRESULT WINAPI teststream_Close(IMFByteStream *iface) +{ + return IMFByteStream_Close(impl_from_IMFByteStream(iface)->inner); +} + +static const IMFByteStreamVtbl teststreamvtbl = +{ + teststream_QueryInterface, + teststream_AddRef, + teststream_Release, + teststream_GetCapabilities, + teststream_GetLength, + teststream_SetLength, + teststream_GetCurrentPosition, + teststream_SetCurrentPosition, + teststream_IsEndOfStream, + teststream_Read, + teststream_BeginRead, + teststream_EndRead, + teststream_Write, + teststream_BeginWrite, + teststream_EndWrite, + teststream_Seek, + teststream_Flush, + teststream_Close, +}; + +static IMFByteStream *create_test_stream(IMFByteStream *inner) +{ + struct test_stream *stream = calloc(1, sizeof *stream); + if (!stream) + return NULL; + + stream->iface.lpVtbl = &teststreamvtbl; + stream->refcount = 1; + stream->inner = inner; + + return &stream->iface; +} + +static void test_source_buffer_size(void) +{ + /* using WAV source as example - but this should hold for any of our builtin sources */ + static const GUID CLSID_WAVByteStreamHandler = {0x42c9b9f5,0x16fc,0x47ef,{0xaf,0x22,0xda,0x05,0xf7,0xc8,0x42,0xe3}}; + static const ULONG sizes[3] = + { + 0x5000, /* smaller */ + 0x50000, /* larger */ + 0x500000, /* much larger */ + }; + IMFByteStream *byte_stream; + IMFMediaSource *source; + IMFMediaStream *stream; + IMFPresentationDescriptor *pres_desc; + IMFAsyncCallback *callback; + PROPVARIANT propvar; + HRESULT hr; + + for (UINT i = 0; i < 3; i++) + { + ULONG read_size = 0, wav_size = sizes[i]; + BYTE *wav_data; + const BYTE header[] = { +#define EMBED_U32(X) (X) & 0xff, ((X) >> 8) & 0xff, ((X) >> 16) & 0xff, ((X) >> 24) & 0xff +#define EMBED_U16(X) (X) & 0xff, ((X) >> 8) & 0xff + 'R', 'I', 'F', 'F', + EMBED_U32(wav_size - 8), + 'W', 'A', 'V', 'E', + 'f', 'm', 't', ' ', + EMBED_U32(16), + EMBED_U16(1), /* PCM */ + EMBED_U16(1), /* channels */ + EMBED_U32(48000), /* rate */ + EMBED_U32((48000*8*1)/8), /* bytes per second */ + EMBED_U16((8*1)/8), /* block alignment */ + EMBED_U16(8), /* bits per sample */ + 'd', 'a', 't', 'a', + EMBED_U32(wav_size - 44), +#undef EMBED_U16 +#undef EMBED_U32 + }; + _Static_assert(sizeof header == 44); + + if (!(wav_data = calloc(wav_size, 1))) + { + win_skip("Failed to allocate memory for WAV data.\n"); + return; + } + memcpy(wav_data, header, sizeof header); + + byte_stream = create_byte_stream(wav_data, wav_size); + byte_stream = create_test_stream(byte_stream); + + free(wav_data); + + hr = create_source(&CLSID_WAVByteStreamHandler, byte_stream, &source); + if (FAILED(hr)) + { + win_skip("Failed to create WAV source: %#lx.\n", hr); + return; + } + + hr = IMFMediaSource_CreatePresentationDescriptor(source, &pres_desc); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + propvar.vt = VT_EMPTY; + hr = IMFMediaSource_Start(source, pres_desc, &GUID_NULL, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFPresentationDescriptor_Release(pres_desc); + + callback = create_test_callback(TRUE); + + hr = wait_media_event(source, callback, MENewStream, 100, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_UNKNOWN, "got vt %u\n", propvar.vt); + stream = (IMFMediaStream *)propvar.punkVal; + IMFMediaStream_AddRef(stream); + PropVariantClear(&propvar); + + hr = wait_media_event(source, callback, MESourceStarted, 100, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = wait_media_event(stream, callback, MEStreamStarted, 100, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + while (read_size < wav_size - sizeof header) + { + IMFSample *sample; + DWORD size; + if ((hr = IMFMediaStream_RequestSample(stream, NULL)) != S_OK) break; + if ((hr = wait_media_event(stream, callback, MEMediaSample, 100, &propvar)) != S_OK) break; + if ((hr = IUnknown_QueryInterface(propvar.punkVal, &IID_IMFSample, (void **) &sample)) != S_OK) break; + if ((hr = IMFSample_GetTotalLength(sample, &size)) != S_OK) break; + read_size += size; + IMFSample_Release(sample); + PropVariantClear(&propvar); + } + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(read_size == wav_size - sizeof header, "Incomplete bytes read: %lu.\n", read_size); + + hr = IMFMediaSource_Stop(source); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = wait_media_event(source, callback, MESourceStopped, 100, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_EMPTY, "got vt %u\n", propvar.vt); + hr = wait_media_event(stream, callback, MEStreamStopped, 100, &propvar); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(propvar.vt == VT_EMPTY, "got vt %u\n", propvar.vt); + + IMFMediaSource_Release(source); + IMFMediaStream_Release(stream); + IMFAsyncCallback_Release(callback); + IMFByteStream_Release(byte_stream); + } +} + START_TEST(mfsrcsnk) { HRESULT hr; @@ -833,6 +1131,7 @@ START_TEST(mfsrcsnk) test_wave_sink(); test_thinning(); + test_source_buffer_size(); hr = MFShutdown(); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9710
From: Charlotte Pabst <cpabst@codeweavers.com> --- dlls/mfsrcsnk/tests/mfsrcsnk.c | 6 ------ dlls/winegstreamer/wg_parser.c | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/dlls/mfsrcsnk/tests/mfsrcsnk.c b/dlls/mfsrcsnk/tests/mfsrcsnk.c index 97049bc3e3c..acd9c77b236 100644 --- a/dlls/mfsrcsnk/tests/mfsrcsnk.c +++ b/dlls/mfsrcsnk/tests/mfsrcsnk.c @@ -892,7 +892,6 @@ static HRESULT WINAPI teststream_GetCurrentPosition(IMFByteStream *iface, QWORD static HRESULT WINAPI teststream_SetCurrentPosition(IMFByteStream *iface, QWORD position) { - todo_wine_if(!(position % 0x40000 == 0)) ok(position % 0x40000 == 0, "IMFByteStream::SetCurrentPosition pos=%lld should be aligned on 0x40000 boundary.\n", position); return IMFByteStream_SetCurrentPosition(impl_from_IMFByteStream(iface)->inner, position); } @@ -907,9 +906,7 @@ static HRESULT WINAPI teststream_Read(IMFByteStream *iface, BYTE *buffer, ULONG QWORD pos = 0; HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if(!(pos % 0x40000 == 0)) ok(pos % 0x40000 == 0, "IMFByteStream::Read pos=%lld should be aligned on 0x40000 boundary.\n", pos); - todo_wine_if(!(size <= 0x40000)) ok(size <= 0x40000, "IMFByteStream::BeginRead size=%lu should not be larger than 0x40000.\n", size); return IMFByteStream_Read(impl_from_IMFByteStream(iface)->inner, buffer, size, read_len); } @@ -920,9 +917,7 @@ static HRESULT WINAPI teststream_BeginRead(IMFByteStream *iface, BYTE *data, ULO QWORD pos = 0; HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if(!(pos % 0x40000 == 0)) ok(pos % 0x40000 == 0, "IMFByteStream::BeginRead pos=%lld should be aligned on 0x40000 boundary.\n", pos); - todo_wine_if(!(size <= 0x40000)) ok(size <= 0x40000, "IMFByteStream::BeginRead size=%lu should not be larger than 0x40000.\n", size); return IMFByteStream_BeginRead(impl_from_IMFByteStream(iface)->inner, data, size, callback, state); } @@ -956,7 +951,6 @@ static HRESULT WINAPI teststream_Seek(IMFByteStream *iface, MFBYTESTREAM_SEEK_OR QWORD pos = 0; HRESULT hr = IMFByteStream_GetCurrentPosition(impl_from_IMFByteStream(iface)->inner, &pos); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine_if(!(pos % 0x40000 == 0)) ok(pos % 0x40000 == 0, "IMFByteStream::Seek pos=%lld should be aligned on 0x40000 boundary.\n", pos); return ret_hr; diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index 1a29015356e..6880cfd410e 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -97,7 +97,7 @@ struct wg_parser struct input_cache_chunk input_cache_chunks[4]; }; -static const unsigned int input_cache_chunk_size = 512 << 10; +static const unsigned int input_cache_chunk_size = 256 << 10; struct wg_parser_stream { @@ -1245,9 +1245,6 @@ static GstFlowReturn src_getrange_cb(GstPad *pad, GstObject *parent, return GST_FLOW_OK; } - if (size >= input_cache_chunk_size || sizeof(void*) == 4) - return issue_read_request(parser, offset, size, buffer); - if (offset >= parser->file_size) return GST_FLOW_EOS; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9710
On Mon Jan 5 11:09:46 2026 +0000, Rémi Bernon wrote:
Please keep coding style consistent with canonical and surrounding Wine code, including for tests. This includes parameter case style, brace wrapping style, helper naming patterns, Interlocked* for reference count, hr and malloc checks, etc. Tried to fix everything you mentioned, let me know if I missed something.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/9710#note_126403
participants (2)
-
Charlotte Pabst -
Charlotte Pabst (@CharlottePabst)