[PATCH 0/4] MR10654: winegstreamer: Fix wg_parser/native video stride mismatch.
YV12, NV12, I420 formats has min alignment of 2 bytes for Y plane stride on native, but winegstreamer uses 4 bytes. Additionally YV12 and I420 expect U and V plane stride to be exactly 1/2 of the Y plane stride, yet winegstreamer also aligns those to 4 bytes. This breaks some games. This commit adds a custom buffer pool to wg_parser which affords us control over what stride to use, just like wg_transform. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10654
From: Yuxuan Shui <yshui@codeweavers.com> --- dlls/winegstreamer/unix_private.h | 10 +++ dlls/winegstreamer/unixlib.c | 96 +++++++++++++++++++++++++++ dlls/winegstreamer/wg_transform.c | 107 +++--------------------------- 3 files changed, 117 insertions(+), 96 deletions(-) diff --git a/dlls/winegstreamer/unix_private.h b/dlls/winegstreamer/unix_private.h index 3199e362fe6..f44a310fdae 100644 --- a/dlls/winegstreamer/unix_private.h +++ b/dlls/winegstreamer/unix_private.h @@ -26,6 +26,7 @@ #include <stdbool.h> #include <gst/gst.h> #include <gst/audio/audio.h> +#include <gst/video/video.h> /* unixlib.c */ @@ -112,4 +113,13 @@ extern void wg_allocator_release_sample(GstAllocator *allocator, struct wg_sampl extern gboolean gst_element_register_winegstreamerstepper(GstPlugin *plugin); +void buffer_add_video_meta(GstBuffer *buffer, GstVideoInfo *info); + +G_BEGIN_DECLS +G_DECLARE_FINAL_TYPE(WgVideoBufferPool, wg_video_buffer_pool, WG, VIDEO_BUFFER_POOL, + GstVideoBufferPool); +extern WgVideoBufferPool *wg_video_buffer_pool_create(GstCaps *caps, GstVideoInfo *info, + gsize max_size, GstAllocator *allocator, GstVideoAlignment *align); +G_END_DECLS + #endif /* __WINE_WINEGSTREAMER_UNIX_PRIVATE_H */ diff --git a/dlls/winegstreamer/unixlib.c b/dlls/winegstreamer/unixlib.c index ac671bbb514..c4e33dd6899 100644 --- a/dlls/winegstreamer/unixlib.c +++ b/dlls/winegstreamer/unixlib.c @@ -339,3 +339,99 @@ void set_max_threads(GstElement *element) g_object_set(element, "max-threads", max_threads, NULL); } } + +typedef struct _WgVideoBufferPool +{ + GstVideoBufferPool parent; + GstVideoInfo *info; +} WgVideoBufferPool; + +G_DEFINE_TYPE(WgVideoBufferPool, wg_video_buffer_pool, GST_TYPE_VIDEO_BUFFER_POOL); + +void buffer_add_video_meta(GstBuffer *buffer, GstVideoInfo *info) +{ + GstVideoMeta *meta; + + if (!(meta = gst_buffer_get_video_meta(buffer))) + meta = gst_buffer_add_video_meta(buffer, GST_VIDEO_FRAME_FLAG_NONE, + info->finfo->format, info->width, info->height); + + if (!meta) + GST_ERROR("Failed to add video meta to buffer %"GST_PTR_FORMAT, buffer); + else + { + memcpy(meta->offset, info->offset, sizeof(info->offset)); + memcpy(meta->stride, info->stride, sizeof(info->stride)); + } +} + +static GstFlowReturn wg_video_buffer_pool_alloc_buffer(GstBufferPool *gst_pool, GstBuffer **buffer, + GstBufferPoolAcquireParams *params) +{ + GstBufferPoolClass *parent_class = GST_BUFFER_POOL_CLASS(wg_video_buffer_pool_parent_class); + WgVideoBufferPool *pool = (WgVideoBufferPool *)gst_pool; + GstFlowReturn ret; + + GST_LOG("%"GST_PTR_FORMAT", buffer %p, params %p", pool, buffer, params); + + if (!(ret = parent_class->alloc_buffer(gst_pool, buffer, params))) + { + buffer_add_video_meta(*buffer, pool->info); + GST_INFO("%"GST_PTR_FORMAT" allocated buffer %"GST_PTR_FORMAT, pool, *buffer); + } + + return ret; +} + +static void wg_video_buffer_pool_init(WgVideoBufferPool *pool) +{ +} + +static void wg_video_buffer_pool_dispose(GObject *obj) +{ + WgVideoBufferPool *pool = (WgVideoBufferPool *)(obj); + if (pool->info) + { + gst_video_info_free(pool->info); + pool->info = NULL; + } + G_OBJECT_CLASS(wg_video_buffer_pool_parent_class)->dispose(obj); +} + +static void wg_video_buffer_pool_class_init(WgVideoBufferPoolClass *klass) +{ + GObjectClass *base_class; + GstBufferPoolClass *pool_class = GST_BUFFER_POOL_CLASS(klass); + pool_class->alloc_buffer = wg_video_buffer_pool_alloc_buffer; + + base_class = G_OBJECT_CLASS(klass); + base_class->dispose = wg_video_buffer_pool_dispose; +} + +WgVideoBufferPool *wg_video_buffer_pool_create(GstCaps *caps, GstVideoInfo *info, + gsize max_size, GstAllocator *allocator, GstVideoAlignment *align) +{ + WgVideoBufferPool *pool; + GstStructure *config; + + if (!(pool = g_object_new(wg_video_buffer_pool_get_type(), NULL))) + return NULL; + + pool->info = info; + if (!(config = gst_buffer_pool_get_config(GST_BUFFER_POOL(pool)))) + GST_ERROR("Failed to get %"GST_PTR_FORMAT" config.", pool); + else + { + gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); + gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + gst_buffer_pool_config_set_video_alignment(config, align); + + gst_buffer_pool_config_set_params(config, caps, max_size, 0, 0); + gst_buffer_pool_config_set_allocator(config, allocator, NULL); + if (!gst_buffer_pool_set_config(GST_BUFFER_POOL(pool), config)) + GST_ERROR("Failed to set %"GST_PTR_FORMAT" config.", pool); + } + + GST_INFO("Created %"GST_PTR_FORMAT, pool); + return pool; +} diff --git a/dlls/winegstreamer/wg_transform.c b/dlls/winegstreamer/wg_transform.c index 1537a88d614..d90e13bc137 100644 --- a/dlls/winegstreamer/wg_transform.c +++ b/dlls/winegstreamer/wg_transform.c @@ -179,98 +179,6 @@ static void align_video_info_planes(MFVideoInfo *video_info, gsize plane_align, } } -typedef struct -{ - GstVideoBufferPool parent; - GstVideoInfo info; -} WgVideoBufferPool; - -typedef struct -{ - GstVideoBufferPoolClass parent_class; -} WgVideoBufferPoolClass; - -G_DEFINE_TYPE(WgVideoBufferPool, wg_video_buffer_pool, GST_TYPE_VIDEO_BUFFER_POOL); - -static void buffer_add_video_meta(GstBuffer *buffer, GstVideoInfo *info) -{ - GstVideoMeta *meta; - - if (!(meta = gst_buffer_get_video_meta(buffer))) - meta = gst_buffer_add_video_meta(buffer, GST_VIDEO_FRAME_FLAG_NONE, - info->finfo->format, info->width, info->height); - - if (!meta) - GST_ERROR("Failed to add video meta to buffer %"GST_PTR_FORMAT, buffer); - else - { - memcpy(meta->offset, info->offset, sizeof(info->offset)); - memcpy(meta->stride, info->stride, sizeof(info->stride)); - } -} - -static GstFlowReturn wg_video_buffer_pool_alloc_buffer(GstBufferPool *gst_pool, GstBuffer **buffer, - GstBufferPoolAcquireParams *params) -{ - GstBufferPoolClass *parent_class = GST_BUFFER_POOL_CLASS(wg_video_buffer_pool_parent_class); - WgVideoBufferPool *pool = (WgVideoBufferPool *)gst_pool; - GstFlowReturn ret; - - GST_LOG("%"GST_PTR_FORMAT", buffer %p, params %p", pool, buffer, params); - - if (!(ret = parent_class->alloc_buffer(gst_pool, buffer, params))) - { - buffer_add_video_meta(*buffer, &pool->info); - GST_INFO("%"GST_PTR_FORMAT" allocated buffer %"GST_PTR_FORMAT, pool, *buffer); - } - - return ret; -} - -static void wg_video_buffer_pool_init(WgVideoBufferPool *pool) -{ -} - -static void wg_video_buffer_pool_class_init(WgVideoBufferPoolClass *klass) -{ - GstBufferPoolClass *pool_class = GST_BUFFER_POOL_CLASS(klass); - pool_class->alloc_buffer = wg_video_buffer_pool_alloc_buffer; -} - -static WgVideoBufferPool *wg_video_buffer_pool_create(GstCaps *caps, gsize plane_align, gsize output_plane_stride, - GstAllocator *allocator, MFVideoInfo *video_info, GstVideoAlignment *align) -{ - WgVideoBufferPool *pool; - GstStructure *config; - gsize max_size; - - if (!(pool = g_object_new(wg_video_buffer_pool_get_type(), NULL))) - return NULL; - - gst_video_info_from_caps(&pool->info, caps); - max_size = pool->info.size; - align_video_info_planes(video_info, plane_align, output_plane_stride, &pool->info, align); - /* GStreamer assumes NV12 pools must accommodate a stride alignment of 4, but we use 2 */ - max_size = max(max_size, pool->info.size); - - if (!(config = gst_buffer_pool_get_config(GST_BUFFER_POOL(pool)))) - GST_ERROR("Failed to get %"GST_PTR_FORMAT" config.", pool); - else - { - gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); - gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); - gst_buffer_pool_config_set_video_alignment(config, align); - - gst_buffer_pool_config_set_params(config, caps, max_size, 0, 0); - gst_buffer_pool_config_set_allocator(config, allocator, NULL); - if (!gst_buffer_pool_set_config(GST_BUFFER_POOL(pool), config)) - GST_ERROR("Failed to set %"GST_PTR_FORMAT" config.", pool); - } - - GST_INFO("Created %"GST_PTR_FORMAT, pool); - return pool; -} - static GstFlowReturn transform_sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *buffer) { struct wg_transform *transform = gst_pad_get_element_private(pad); @@ -322,18 +230,25 @@ static gboolean transform_sink_query_allocation(struct wg_transform *transform, const char *mime_type; GstStructure *params; gboolean needs_pool; + GstVideoInfo *info; + gsize max_size; GstCaps *caps; GST_LOG("transform %p, %"GST_PTR_FORMAT, transform, query); gst_query_parse_allocation(query, &caps, &needs_pool); + info = gst_video_info_new_from_caps(caps); + max_size = info->size; + align_video_info_planes(&transform->output_info, transform->attrs.output_plane_align, + transform->attrs.output_plane_stride, info, &align); + /* GStreamer assumes NV12 pools must accommodate a stride alignment of 4, but we use 2 */ + max_size = max(max_size, info->size); mime_type = gst_structure_get_name(gst_caps_get_structure(caps, 0)); if (strcmp(mime_type, "video/x-raw") || !needs_pool) return false; - if (!(pool = wg_video_buffer_pool_create(caps, transform->attrs.output_plane_align, - transform->attrs.output_plane_stride, transform->allocator, &transform->output_info, &align))) + if (!(pool = wg_video_buffer_pool_create(caps, info, max_size, transform->allocator, &align))) return false; if ((params = gst_structure_new("video-meta", @@ -351,11 +266,11 @@ static gboolean transform_sink_query_allocation(struct wg_transform *transform, if (!gst_buffer_pool_set_active(GST_BUFFER_POOL(pool), true)) GST_ERROR("%"GST_PTR_FORMAT" failed to activate.", pool); - gst_query_add_allocation_pool(query, GST_BUFFER_POOL(pool), pool->info.size, 0, 0); + gst_query_add_allocation_pool(query, GST_BUFFER_POOL(pool), info->size, 0, 0); gst_query_add_allocation_param(query, transform->allocator, NULL); GST_INFO("Proposing %"GST_PTR_FORMAT", buffer size %#zx, %"GST_PTR_FORMAT", for %"GST_PTR_FORMAT, - pool, pool->info.size, transform->allocator, query); + pool, info->size, transform->allocator, query); g_object_unref(pool); return true; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10654
From: Yuxuan Shui <yshui@codeweavers.com> --- dlls/mfreadwrite/tests/mfplat.c | 153 ++++++++++++++++++++++++++ dlls/mfreadwrite/tests/resource.rc | 4 + dlls/mfreadwrite/tests/test_align.mp4 | Bin 0 -> 2991 bytes 3 files changed, 157 insertions(+) create mode 100644 dlls/mfreadwrite/tests/test_align.mp4 diff --git a/dlls/mfreadwrite/tests/mfplat.c b/dlls/mfreadwrite/tests/mfplat.c index 95e940e2449..82ade919670 100644 --- a/dlls/mfreadwrite/tests/mfplat.c +++ b/dlls/mfreadwrite/tests/mfplat.c @@ -2045,6 +2045,158 @@ static void test_interfaces(void) IMFStreamDescriptor_Release(audio_streams[i]); } +static void test_source_reader_stride(void) +{ + IMFAttributes *attributes; + IMFMediaType *mediatype; + IMFSourceReader *reader; + IMFByteStream *stream; + UINT32 compressed; + UINT64 framesize; + UINT32 stride; + GUID subtype; + HRESULT hr; + + if (!pMFCreateMFByteStreamOnStream) + { + win_skip("MFCreateMFByteStreamOnStream() not found\n"); + return; + } + + winetest_push_context("%s", "test_align.mp4"); + + /* test_align.mp4 is a video of solid red, so it has predictable YUV values: + * Y = 0x51, U = 0x5A, V = 0xF0 + * + * we can then use these values to check if all planes are at the expected offset. */ + stream = get_resource_stream("test_align.mp4"); + /* Create the source reader with video processing enabled. This allows + * outputting RGB formats. */ + MFCreateAttributes(&attributes, 1); + hr = IMFAttributes_SetUINT32(attributes, &MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = MFCreateSourceReaderFromByteStream(stream, attributes, &reader); + if (FAILED(hr)) + { + skip("MFCreateSourceReaderFromByteStream() failed, is G-Streamer missing?\n"); + IMFByteStream_Release(stream); + IMFAttributes_Release(attributes); + winetest_pop_context(); + return; + } + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFAttributes_Release(attributes); + + hr = IMFSourceReader_SetStreamSelection(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Current media type. */ + hr = IMFSourceReader_GetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediatype); + hr = IMFMediaType_GetGUID(mediatype, &MF_MT_SUBTYPE, &subtype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(IsEqualGUID(&subtype, &MFVideoFormat_H264), "Got subtype %s.\n", debugstr_guid(&subtype)); + + hr = IMFMediaType_GetUINT64(mediatype, &MF_MT_FRAME_SIZE, &framesize); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(framesize == ((UINT64)162 << 32 | 120), "Got frame size %ux%u.\n", + (unsigned int)(framesize >> 32), (unsigned int)framesize); + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); + todo_wine ok(hr == MF_E_ATTRIBUTENOTFOUND, "Unexpected hr %#lx.\n", hr); + + IMFMediaType_Release(mediatype); + + winetest_push_context("NV12"); + + hr = MFCreateMediaType(&mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_SUBTYPE, &MFVideoFormat_NV12); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(mediatype); + + hr = IMFSourceReader_GetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_GetGUID(mediatype, &MF_MT_SUBTYPE, &subtype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(IsEqualGUID(&subtype, &MFVideoFormat_NV12), "Got subtype %s.\n", debugstr_guid(&subtype)); + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(stride == 162, "Got stride %u.\n", stride); + compressed = 0; + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!compressed, "Unexpected compressed\n"); + IMFMediaType_Release(mediatype); + + winetest_pop_context(); + + winetest_push_context("YV12"); + + hr = MFCreateMediaType(&mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_SUBTYPE, &MFVideoFormat_YV12); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(mediatype); + + hr = IMFSourceReader_GetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_GetGUID(mediatype, &MF_MT_SUBTYPE, &subtype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(IsEqualGUID(&subtype, &MFVideoFormat_YV12), "Got subtype %s.\n", debugstr_guid(&subtype)); + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(stride == 162, "Got stride %u.\n", stride); + compressed = 0; + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!compressed, "Unexpected compressed\n"); + IMFMediaType_Release(mediatype); + + winetest_pop_context(); + + winetest_push_context("I420"); + + hr = MFCreateMediaType(&mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_MAJOR_TYPE, &MFMediaType_Video); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_SetGUID(mediatype, &MF_MT_SUBTYPE, &MFVideoFormat_I420); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFSourceReader_SetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaType_Release(mediatype); + + hr = IMFSourceReader_GetCurrentMediaType(reader, MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediatype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaType_GetGUID(mediatype, &MF_MT_SUBTYPE, &subtype); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(IsEqualGUID(&subtype, &MFVideoFormat_I420), "Got subtype %s.\n", debugstr_guid(&subtype)); + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(stride == 162, "Got stride %u.\n", stride); + compressed = 0; + hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!compressed, "Unexpected compressed\n"); + IMFMediaType_Release(mediatype); + + winetest_pop_context(); + + IMFSourceReader_Release(reader); + IMFByteStream_Release(stream); + + winetest_pop_context(); +} + static void test_source_reader_transforms(BOOL enable_processing, BOOL enable_advanced) { static const struct attribute_desc h264_stream_type_desc[] = @@ -3901,6 +4053,7 @@ START_TEST(mfplat) test_interfaces(); test_source_reader("test.wav", false); test_source_reader("test.mp4", true); + test_source_reader_stride(); test_source_reader_from_media_source(); test_source_reader_transforms(FALSE, FALSE); test_source_reader_transforms(TRUE, FALSE); diff --git a/dlls/mfreadwrite/tests/resource.rc b/dlls/mfreadwrite/tests/resource.rc index 5a737b1ad85..9b2e459794a 100644 --- a/dlls/mfreadwrite/tests/resource.rc +++ b/dlls/mfreadwrite/tests/resource.rc @@ -25,3 +25,7 @@ test.wav RCDATA test.wav /* ffmpeg -f lavfi -i smptebars -f lavfi -i "sine=frequency=1000" -t 1.0 -r 25 -f mp4 -vcodec h264 -vf scale=160x120 -acodec aac test.mp4 */ /* @makedep: test.mp4 */ test.mp4 RCDATA test.mp4 + +/* ffmpeg -f lavfi -i color=c=red:s=162x120:d=1 -t 1.0 -r 25 -f mp4 -vcodec h264 -an test_align.mp4 */ +/* @makedep: test_align.mp4 */ +test_align.mp4 RCDATA test_align.mp4 diff --git a/dlls/mfreadwrite/tests/test_align.mp4 b/dlls/mfreadwrite/tests/test_align.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..fc19f39ca55361d9fcc1458eef4ac4a4d79c4e88 GIT binary patch literal 2991 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 literal 0 HcmV?d00001 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10654
From: Yuxuan Shui <yshui@codeweavers.com> YV12, NV12, I420 formats has min alignment of 2 bytes for Y plane stride on native, but winegstreamer uses 4 bytes. Additionally YV12 and I420 expect U and V plane stride to be exactly 1/2 of the Y plane stride, yet winegstreamer also aligns those to 4 bytes. This breaks some games. This commit adds a custom buffer pool to wg_parser which affords us control over what stride to use, just like wg_transform. --- dlls/winegstreamer/unixlib.c | 8 +++- dlls/winegstreamer/wg_parser.c | 82 +++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/dlls/winegstreamer/unixlib.c b/dlls/winegstreamer/unixlib.c index c4e33dd6899..9ca19f89f5a 100644 --- a/dlls/winegstreamer/unixlib.c +++ b/dlls/winegstreamer/unixlib.c @@ -427,7 +427,13 @@ WgVideoBufferPool *wg_video_buffer_pool_create(GstCaps *caps, GstVideoInfo *info gst_buffer_pool_config_set_video_alignment(config, align); gst_buffer_pool_config_set_params(config, caps, max_size, 0, 0); - gst_buffer_pool_config_set_allocator(config, allocator, NULL); + if (allocator) gst_buffer_pool_config_set_allocator(config, allocator, NULL); + else + { + GstAllocationParams alloc_params; + gst_allocation_params_init(&alloc_params); + gst_buffer_pool_config_set_allocator(config, NULL, &alloc_params); + } if (!gst_buffer_pool_set_config(GST_BUFFER_POOL(pool), config)) GST_ERROR("Failed to set %"GST_PTR_FORMAT" config.", pool); } diff --git a/dlls/winegstreamer/wg_parser.c b/dlls/winegstreamer/wg_parser.c index da414af6134..58a1787922b 100644 --- a/dlls/winegstreamer/wg_parser.c +++ b/dlls/winegstreamer/wg_parser.c @@ -771,6 +771,79 @@ static GstFlowReturn sink_chain_cb(GstPad *pad, GstObject *parent, GstBuffer *bu return GST_FLOW_OK; } +static gboolean sink_query_allocation(struct wg_parser *parser, GstQuery *query) +{ + gint gst_stride, native_stride; + GstVideoAlignment align = {0}; + bool needs_alignment_fix; + WgVideoBufferPool *pool; + const char *mime_type; + GstCaps *caps = NULL; + gboolean needs_pool; + gint aligned_height; + GstVideoInfo *info; + gsize max_size; + + GST_LOG("wg_parser %p, %"GST_PTR_FORMAT, parser, query); + + gst_query_parse_allocation(query, &caps, &needs_pool); + + mime_type = gst_structure_get_name(gst_caps_get_structure(caps, 0)); + if (strcmp(mime_type, "video/x-raw") || !needs_pool) + return false; + + info = gst_video_info_new_from_caps(caps); + gst_stride = GST_ROUND_UP_4(info->width); + native_stride = GST_ROUND_UP_2(info->width); + + /* For NV12, Y and UV planes have the same stride, and need 2-bytes alignment instead of 4. */ + needs_alignment_fix = (info->finfo->format == GST_VIDEO_FORMAT_NV12 && native_stride != gst_stride) || + /* For YV12 or I420, Y plane stride needs 2-bytes alignment instead of 4, and both U and V + * planes need to have strides that are exactly half of the Y plane's stride. Yet gstreamer + * aligns both U and V to multiples of 4 as well. */ + ((info->finfo->format == GST_VIDEO_FORMAT_YV12 || info->finfo->format == GST_VIDEO_FORMAT_I420) && + (native_stride != gst_stride || (native_stride & 6))); + + if (!needs_alignment_fix) + /* Don't bother with buffer pool if we don't need to realign the video frames. Unlike + * wg_transform we don't need a buffer pool to function.*/ + return false; + + max_size = info->size; + aligned_height = GST_ROUND_UP_2(info->height); + info->stride[0] = GST_ROUND_UP_2(info->width); + info->offset[0] = 0; + align.stride_align[0] = 1; + + info->offset[1] = info->stride[0] * aligned_height; + if (info->finfo->format == GST_VIDEO_FORMAT_NV12) + { + /* NV12, UV packed plane has the same stride as the Y plane */ + info->stride[1] = info->stride[0]; + info->size = info->offset[1] + info->stride[0] * aligned_height / 2; + } + else + { + /* I420/YV12. U and V plane each has half of the stride of the Y plane. */ + info->stride[1] = info->stride[2] = info->stride[0] / 2; + info->offset[2] = info->offset[1] + info->stride[1] * aligned_height / 2; + info->size = info->offset[2] + info->stride[1] * aligned_height / 2; + } + max_size = max(max_size, info->size); + + if (!(pool = wg_video_buffer_pool_create(caps, info, max_size, NULL, &align))) + return false; + if (!gst_buffer_pool_set_active(GST_BUFFER_POOL(pool), true)) + GST_ERROR("%"GST_PTR_FORMAT" failed to activate.", pool); + + gst_query_add_allocation_pool(query, GST_BUFFER_POOL(pool), info->size, 0, 0); + + GST_INFO("Proposing %"GST_PTR_FORMAT", buffer size %#zx, for %"GST_PTR_FORMAT, pool, info->size, query); + + g_object_unref(pool); + return true; +} + static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) { struct wg_parser_stream *stream = gst_pad_get_element_private(pad); @@ -842,9 +915,16 @@ static gboolean sink_query_cb(GstPad *pad, GstObject *parent, GstQuery *query) return TRUE; } + case GST_QUERY_ALLOCATION: + if (sink_query_allocation(parser, query)) + return true; + break; + default: - return gst_pad_query_default (pad, parent, query); + break; } + + return gst_pad_query_default(pad, parent, query); } static struct wg_parser_stream *create_stream(struct wg_parser *parser) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10654
From: Yuxuan Shui <yshui@codeweavers.com> Now both wg_parser and wg_transform uses a 2-byte alignment for YV12/NV12/I420 strides, we can update wg_format_get_stride to report the correct strides for these formats. --- dlls/mfreadwrite/tests/mfplat.c | 6 +++--- dlls/winegstreamer/main.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dlls/mfreadwrite/tests/mfplat.c b/dlls/mfreadwrite/tests/mfplat.c index 82ade919670..c1f4d8213e4 100644 --- a/dlls/mfreadwrite/tests/mfplat.c +++ b/dlls/mfreadwrite/tests/mfplat.c @@ -2126,7 +2126,7 @@ static void test_source_reader_stride(void) ok(IsEqualGUID(&subtype, &MFVideoFormat_NV12), "Got subtype %s.\n", debugstr_guid(&subtype)); hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(stride == 162, "Got stride %u.\n", stride); + ok(stride == 162, "Got stride %u.\n", stride); compressed = 0; hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -2154,7 +2154,7 @@ static void test_source_reader_stride(void) ok(IsEqualGUID(&subtype, &MFVideoFormat_YV12), "Got subtype %s.\n", debugstr_guid(&subtype)); hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(stride == 162, "Got stride %u.\n", stride); + ok(stride == 162, "Got stride %u.\n", stride); compressed = 0; hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -2182,7 +2182,7 @@ static void test_source_reader_stride(void) ok(IsEqualGUID(&subtype, &MFVideoFormat_I420), "Got subtype %s.\n", debugstr_guid(&subtype)); hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_DEFAULT_STRIDE, &stride); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(stride == 162, "Got stride %u.\n", stride); + ok(stride == 162, "Got stride %u.\n", stride); compressed = 0; hr = IMFMediaType_GetUINT32(mediatype, &MF_MT_COMPRESSED, &compressed); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/dlls/winegstreamer/main.c b/dlls/winegstreamer/main.c index c7893f71026..24814c138b2 100644 --- a/dlls/winegstreamer/main.c +++ b/dlls/winegstreamer/main.c @@ -860,7 +860,7 @@ unsigned int wg_format_get_stride(const struct wg_format *format) case WG_VIDEO_FORMAT_I420: case WG_VIDEO_FORMAT_NV12: case WG_VIDEO_FORMAT_YV12: - return ALIGN(width, 4); /* Y plane */ + return ALIGN(width, 2); /* Y plane */ case WG_VIDEO_FORMAT_UNKNOWN: FIXME("Cannot calculate stride for unknown video format.\n"); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10654
I spent some time trying to figure out how exactly different pixel formats should be aligned. And I have some discoveries. One major difference is that native `IMFMediaBuffer` implements `IMF2DBuffer` while ours doesn't. This means native `IMFMediaBuffer` can in theory have any alignment, since the user can figure out the actual stride used by calling `IMF2DBuffer::Lock`. In my testing, the Y plane stride alignment is actually 16 bytes. Seems like games expect the Y plane stride alignment to be 2 bytes when `IMF2DBuffer` interface is not available. At least the games we had problems with (e.g. River City Girls) are like this. So that's what this commit does. An alternative fix will be implementing `IMF2DBuffer` for our media buffers and report our stride through `Lock2D`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10654#note_136011
participants (2)
-
Yuxuan Shui -
Yuxuan Shui (@yshui)