mfmediaengine: Implement SVR.

This commit replaces the SampleGrabberSink with a dedicated Video Sink,
referred to as the Simple Video Renderer (SVR).

This brings it more inline with Windows and provides the benefit of
having direct access to the IMFSample, removing the need to copy the
sample data.

Based on a patch by Nikolay Sivov.
This commit is contained in:
Brendan McGrath 2024-04-11 15:24:34 +10:00
parent 67c1c3c255
commit 3b141329be
5 changed files with 1350 additions and 153 deletions

View file

@ -5,4 +5,5 @@ EXTRADLLFLAGS = -Wb,--prefer-native
SOURCES = \
main.c \
mediaengine_classes.idl
mediaengine_classes.idl \
video_frame_sink.c

View file

@ -32,6 +32,8 @@
#include "mmdeviceapi.h"
#include "audiosessiontypes.h"
#include "mediaengine_private.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(mfplat);
@ -138,8 +140,8 @@ struct media_engine
IMFMediaEngineEx IMFMediaEngineEx_iface;
IMFGetService IMFGetService_iface;
IMFAsyncCallback session_events;
IMFAsyncCallback sink_events;
IMFAsyncCallback load_handler;
IMFSampleGrabberSinkCallback grabber_callback;
LONG refcount;
IMFMediaEngineNotify *callback;
IMFAttributes *attributes;
@ -166,6 +168,7 @@ struct media_engine
IMFMediaSource *source;
IMFPresentationDescriptor *pd;
PROPVARIANT start_position;
struct video_frame_sink *frame_sink;
} presentation;
struct effects video_effects;
struct effects audio_effects;
@ -784,16 +787,16 @@ static struct media_engine *impl_from_session_events_IMFAsyncCallback(IMFAsyncCa
return CONTAINING_RECORD(iface, struct media_engine, session_events);
}
static struct media_engine *impl_from_sink_events_IMFAsyncCallback(IMFAsyncCallback *iface)
{
return CONTAINING_RECORD(iface, struct media_engine, sink_events);
}
static struct media_engine *impl_from_load_handler_IMFAsyncCallback(IMFAsyncCallback *iface)
{
return CONTAINING_RECORD(iface, struct media_engine, load_handler);
}
static struct media_engine *impl_from_IMFSampleGrabberSinkCallback(IMFSampleGrabberSinkCallback *iface)
{
return CONTAINING_RECORD(iface, struct media_engine, grabber_callback);
}
static unsigned int get_gcd(unsigned int a, unsigned int b)
{
unsigned int m;
@ -1000,6 +1003,10 @@ static HRESULT WINAPI media_engine_session_events_Invoke(IMFAsyncCallback *iface
IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_ENDED, 0, 0);
break;
case MEEndOfPresentation:
video_frame_sink_notify_end_of_presentation(engine->presentation.frame_sink);
break;
}
failed:
@ -1022,6 +1029,48 @@ static const IMFAsyncCallbackVtbl media_engine_session_events_vtbl =
media_engine_session_events_Invoke,
};
static ULONG WINAPI media_engine_sink_events_AddRef(IMFAsyncCallback *iface)
{
struct media_engine *engine = impl_from_sink_events_IMFAsyncCallback(iface);
return IMFMediaEngineEx_AddRef(&engine->IMFMediaEngineEx_iface);
}
static ULONG WINAPI media_engine_sink_events_Release(IMFAsyncCallback *iface)
{
struct media_engine *engine = impl_from_sink_events_IMFAsyncCallback(iface);
return IMFMediaEngineEx_Release(&engine->IMFMediaEngineEx_iface);
}
static HRESULT WINAPI media_engine_sink_events_Invoke(IMFAsyncCallback *iface, IMFAsyncResult *result)
{
struct media_engine *engine = impl_from_sink_events_IMFAsyncCallback(iface);
MF_MEDIA_ENGINE_EVENT event = IMFAsyncResult_GetStatus(result);
EnterCriticalSection(&engine->cs);
switch (event)
{
case MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY:
IMFMediaEngineNotify_EventNotify(engine->callback, event, 0, 0);
break;
default:
;
}
LeaveCriticalSection(&engine->cs);
return S_OK;
}
static const IMFAsyncCallbackVtbl media_engine_sink_events_vtbl =
{
media_engine_callback_QueryInterface,
media_engine_sink_events_AddRef,
media_engine_sink_events_Release,
media_engine_callback_GetParameters,
media_engine_sink_events_Invoke,
};
static ULONG WINAPI media_engine_load_handler_AddRef(IMFAsyncCallback *iface)
{
struct media_engine *engine = impl_from_load_handler_IMFAsyncCallback(iface);
@ -1122,7 +1171,6 @@ static HRESULT media_engine_create_audio_renderer(struct media_engine *engine, I
static HRESULT media_engine_create_video_renderer(struct media_engine *engine, IMFTopologyNode **node)
{
IMFMediaType *media_type;
IMFActivate *activate;
UINT32 output_format;
GUID subtype;
HRESULT hr;
@ -1148,33 +1196,47 @@ static HRESULT media_engine_create_video_renderer(struct media_engine *engine, I
IMFMediaType_SetGUID(media_type, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
IMFMediaType_SetGUID(media_type, &MF_MT_SUBTYPE, &subtype);
hr = MFCreateSampleGrabberSinkActivate(media_type, &engine->grabber_callback, &activate);
hr = create_video_frame_sink(media_type, &engine->sink_events, &engine->presentation.frame_sink);
IMFMediaType_Release(media_type);
if (FAILED(hr))
return hr;
if (SUCCEEDED(hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, node)))
{
IMFTopologyNode_SetObject(*node, (IUnknown *)activate);
IMFTopologyNode_SetUINT32(*node, &MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
}
IMFStreamSink *sink;
video_frame_sink_query_iface(engine->presentation.frame_sink, &IID_IMFStreamSink, (void **)&sink);
IMFActivate_Release(activate);
IMFTopologyNode_SetObject(*node, (IUnknown *)sink);
IMFTopologyNode_SetUINT32(*node, &MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE);
IMFStreamSink_Release(sink);
}
engine->video_frame.output_format = output_format;
return hr;
}
/* must be called with engine->cs held */
static void media_engine_clear_presentation(struct media_engine *engine)
{
if (engine->presentation.source)
{
/* critical section can not be held during shutdown, as shut down requires all pending
* callbacks to complete, and some callbacks require this cs */
LeaveCriticalSection(&engine->cs);
IMFMediaSource_Shutdown(engine->presentation.source);
EnterCriticalSection(&engine->cs);
IMFMediaSource_Release(engine->presentation.source);
}
if (engine->presentation.pd)
IMFPresentationDescriptor_Release(engine->presentation.pd);
if (engine->presentation.frame_sink)
{
video_frame_sink_release(engine->presentation.frame_sink);
engine->presentation.frame_sink = NULL;
}
memset(&engine->presentation, 0, sizeof(engine->presentation));
}
@ -1273,7 +1335,7 @@ static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMedi
if (SUCCEEDED(hr = MFCreateTopology(&topology)))
{
IMFTopologyNode *sar_node = NULL, *audio_src = NULL;
IMFTopologyNode *grabber_node = NULL, *video_src = NULL;
IMFTopologyNode *svr_node = NULL, *video_src = NULL;
if (engine->flags & MF_MEDIA_ENGINE_REAL_TIME_MODE)
IMFTopology_SetUINT32(topology, &MF_LOW_LATENCY, TRUE);
@ -1307,24 +1369,24 @@ static HRESULT media_engine_create_topology(struct media_engine *engine, IMFMedi
if (FAILED(hr = media_engine_create_source_node(source, pd, sd_video, &video_src)))
WARN("Failed to create video source node, hr %#lx.\n", hr);
if (FAILED(hr = media_engine_create_video_renderer(engine, &grabber_node)))
WARN("Failed to create video grabber node, hr %#lx.\n", hr);
if (FAILED(hr = media_engine_create_video_renderer(engine, &svr_node)))
WARN("Failed to create simple video render node, hr %#lx.\n", hr);
if (grabber_node && video_src)
if (svr_node && video_src)
{
IMFTopology_AddNode(topology, video_src);
IMFTopology_AddNode(topology, grabber_node);
IMFTopology_AddNode(topology, svr_node);
if (FAILED(hr = media_engine_create_effects(engine->video_effects.effects, engine->video_effects.count,
video_src, grabber_node, topology)))
video_src, svr_node, topology)))
WARN("Failed to create video effect nodes, hr %#lx.\n", hr);
}
if (SUCCEEDED(hr))
IMFTopologyNode_GetTopoNodeID(video_src, &engine->video_frame.node_id);
if (grabber_node)
IMFTopologyNode_Release(grabber_node);
if (svr_node)
IMFTopologyNode_Release(svr_node);
if (video_src)
IMFTopologyNode_Release(video_src);
}
@ -1467,6 +1529,7 @@ static ULONG WINAPI media_engine_AddRef(IMFMediaEngineEx *iface)
static void free_media_engine(struct media_engine *engine)
{
EnterCriticalSection(&engine->cs);
if (engine->callback)
IMFMediaEngineNotify_Release(engine->callback);
if (engine->clock)
@ -1489,6 +1552,7 @@ static void free_media_engine(struct media_engine *engine)
IMFDXGIDeviceManager_Release(engine->device_manager);
}
SysFreeString(engine->current_source);
LeaveCriticalSection(&engine->cs);
DeleteCriticalSection(&engine->cs);
free(engine->video_frame.buffer);
free(engine);
@ -2300,7 +2364,11 @@ static HRESULT WINAPI media_engine_Shutdown(IMFMediaEngineEx *iface)
{
media_engine_set_flag(engine, FLAGS_ENGINE_SHUT_DOWN, TRUE);
media_engine_clear_presentation(engine);
/* critical section can not be held during shutdown, as shut down requires all pending
* callbacks to complete, and some callbacks require this cs */
LeaveCriticalSection(&engine->cs);
IMFMediaSession_Shutdown(engine->session);
EnterCriticalSection(&engine->cs);
}
LeaveCriticalSection(&engine->cs);
@ -2353,8 +2421,10 @@ static void media_engine_adjust_destination_for_ratio(const struct media_engine
static void media_engine_update_d3d11_frame_surface(ID3D11DeviceContext *context, struct media_engine *engine)
{
D3D11_TEXTURE2D_DESC surface_desc;
IMFMediaBuffer *media_buffer;
IMFSample *sample;
if (!(engine->flags & FLAGS_ENGINE_NEW_FRAME))
if (!video_frame_sink_get_sample(engine->presentation.frame_sink, &sample))
return;
ID3D11Texture2D_GetDesc(engine->video_frame.d3d11.source, &surface_desc);
@ -2370,13 +2440,24 @@ static void media_engine_update_d3d11_frame_surface(ID3D11DeviceContext *context
surface_desc.Width = 0;
}
if (engine->video_frame.buffer_size == surface_desc.Width * surface_desc.Height)
if (SUCCEEDED(IMFSample_ConvertToContiguousBuffer(sample, &media_buffer)))
{
ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource *)engine->video_frame.d3d11.source,
0, NULL, engine->video_frame.buffer, surface_desc.Width, 0);
BYTE *buffer;
DWORD buffer_size;
if (SUCCEEDED(IMFMediaBuffer_Lock(media_buffer, &buffer, NULL, &buffer_size)))
{
if (buffer_size == surface_desc.Width * surface_desc.Height)
{
ID3D11DeviceContext_UpdateSubresource(context, (ID3D11Resource *)engine->video_frame.d3d11.source,
0, NULL, buffer, surface_desc.Width, 0);
}
IMFMediaBuffer_Unlock(media_buffer);
}
IMFMediaBuffer_Release(media_buffer);
}
media_engine_set_flag(engine, FLAGS_ENGINE_NEW_FRAME, FALSE);
IMFSample_Release(sample);
}
static HRESULT media_engine_transfer_to_d3d11_texture(struct media_engine *engine, ID3D11Texture2D *texture,
@ -2572,8 +2653,9 @@ static HRESULT WINAPI media_engine_OnVideoStreamTick(IMFMediaEngineEx *iface, LO
hr = E_POINTER;
else
{
*pts = engine->video_frame.pts;
hr = *pts == MINLONGLONG ? S_FALSE : S_OK;
MFTIME clocktime;
IMFPresentationClock_GetTime(engine->clock, &clocktime);
hr = video_frame_sink_get_pts(engine->presentation.frame_sink, clocktime, pts);
}
LeaveCriticalSection(&engine->cs);
@ -3169,127 +3251,6 @@ static const IMFGetServiceVtbl media_engine_get_service_vtbl =
media_engine_gs_GetService,
};
static HRESULT WINAPI media_engine_grabber_callback_QueryInterface(IMFSampleGrabberSinkCallback *iface,
REFIID riid, void **obj)
{
if (IsEqualIID(riid, &IID_IMFSampleGrabberSinkCallback) ||
IsEqualIID(riid, &IID_IUnknown))
{
*obj = iface;
IMFSampleGrabberSinkCallback_AddRef(iface);
return S_OK;
}
*obj = NULL;
return E_NOINTERFACE;
}
static ULONG WINAPI media_engine_grabber_callback_AddRef(IMFSampleGrabberSinkCallback *iface)
{
struct media_engine *engine = impl_from_IMFSampleGrabberSinkCallback(iface);
return IMFMediaEngineEx_AddRef(&engine->IMFMediaEngineEx_iface);
}
static ULONG WINAPI media_engine_grabber_callback_Release(IMFSampleGrabberSinkCallback *iface)
{
struct media_engine *engine = impl_from_IMFSampleGrabberSinkCallback(iface);
return IMFMediaEngineEx_Release(&engine->IMFMediaEngineEx_iface);
}
static HRESULT WINAPI media_engine_grabber_callback_OnClockStart(IMFSampleGrabberSinkCallback *iface,
MFTIME systime, LONGLONG start_offset)
{
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnClockStop(IMFSampleGrabberSinkCallback *iface,
MFTIME systime)
{
struct media_engine *engine = impl_from_IMFSampleGrabberSinkCallback(iface);
EnterCriticalSection(&engine->cs);
media_engine_set_flag(engine, FLAGS_ENGINE_FIRST_FRAME, FALSE);
engine->video_frame.pts = MINLONGLONG;
LeaveCriticalSection(&engine->cs);
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnClockPause(IMFSampleGrabberSinkCallback *iface,
MFTIME systime)
{
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnClockRestart(IMFSampleGrabberSinkCallback *iface,
MFTIME systime)
{
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnClockSetRate(IMFSampleGrabberSinkCallback *iface,
MFTIME systime, float rate)
{
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnSetPresentationClock(IMFSampleGrabberSinkCallback *iface,
IMFPresentationClock *clock)
{
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnProcessSample(IMFSampleGrabberSinkCallback *iface,
REFGUID major_type, DWORD sample_flags, LONGLONG sample_time, LONGLONG sample_duration,
const BYTE *buffer, DWORD buffer_size)
{
struct media_engine *engine = impl_from_IMFSampleGrabberSinkCallback(iface);
EnterCriticalSection(&engine->cs);
if (!(engine->flags & FLAGS_ENGINE_FIRST_FRAME))
{
IMFMediaEngineNotify_EventNotify(engine->callback, MF_MEDIA_ENGINE_EVENT_FIRSTFRAMEREADY, 0, 0);
media_engine_set_flag(engine, FLAGS_ENGINE_FIRST_FRAME, TRUE);
}
engine->video_frame.pts = sample_time;
if (engine->video_frame.buffer_size < buffer_size)
{
free(engine->video_frame.buffer);
if ((engine->video_frame.buffer = malloc(buffer_size)))
engine->video_frame.buffer_size = buffer_size;
}
if (engine->video_frame.buffer)
{
memcpy(engine->video_frame.buffer, buffer, buffer_size);
engine->flags |= FLAGS_ENGINE_NEW_FRAME;
}
LeaveCriticalSection(&engine->cs);
return S_OK;
}
static HRESULT WINAPI media_engine_grabber_callback_OnShutdown(IMFSampleGrabberSinkCallback *iface)
{
return S_OK;
}
static const IMFSampleGrabberSinkCallbackVtbl media_engine_grabber_callback_vtbl =
{
media_engine_grabber_callback_QueryInterface,
media_engine_grabber_callback_AddRef,
media_engine_grabber_callback_Release,
media_engine_grabber_callback_OnClockStart,
media_engine_grabber_callback_OnClockStop,
media_engine_grabber_callback_OnClockPause,
media_engine_grabber_callback_OnClockRestart,
media_engine_grabber_callback_OnClockSetRate,
media_engine_grabber_callback_OnSetPresentationClock,
media_engine_grabber_callback_OnProcessSample,
media_engine_grabber_callback_OnShutdown,
};
static HRESULT WINAPI media_engine_factory_QueryInterface(IMFMediaEngineClassFactory *iface, REFIID riid, void **obj)
{
if (IsEqualIID(riid, &IID_IMFMediaEngineClassFactory) ||
@ -3318,15 +3279,15 @@ static ULONG WINAPI media_engine_factory_Release(IMFMediaEngineClassFactory *ifa
static HRESULT init_media_engine(DWORD flags, IMFAttributes *attributes, struct media_engine *engine)
{
UINT32 output_format;
UINT64 playback_hwnd;
UINT64 playback_hwnd = 0;
IMFClock *clock;
HRESULT hr;
engine->IMFMediaEngineEx_iface.lpVtbl = &media_engine_vtbl;
engine->IMFGetService_iface.lpVtbl = &media_engine_get_service_vtbl;
engine->session_events.lpVtbl = &media_engine_session_events_vtbl;
engine->sink_events.lpVtbl = &media_engine_sink_events_vtbl;
engine->load_handler.lpVtbl = &media_engine_load_handler_vtbl;
engine->grabber_callback.lpVtbl = &media_engine_grabber_callback_vtbl;
engine->refcount = 1;
engine->flags = (flags & MF_MEDIA_ENGINE_CREATEFLAGS_MASK) | FLAGS_ENGINE_PAUSED;
engine->default_playback_rate = 1.0;

View file

@ -0,0 +1,29 @@
/*
* Copyright 2019 Nikolay Sivov for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "mfmediaengine.h"
struct video_frame_sink;
HRESULT create_video_frame_sink(IMFMediaType *media_type, IMFAsyncCallback *events_callback,
struct video_frame_sink **sink);
HRESULT video_frame_sink_query_iface(struct video_frame_sink *object, REFIID riid, void **obj);
ULONG video_frame_sink_release(struct video_frame_sink *sink);
int video_frame_sink_get_sample(struct video_frame_sink *sink, IMFSample **sample);
HRESULT video_frame_sink_get_pts(struct video_frame_sink *sink, MFTIME clocktime, LONGLONG *pts);
void video_frame_sink_notify_end_of_presentation(struct video_frame_sink *sink);

View file

@ -1282,6 +1282,7 @@ static void test_TransferVideoFrame(void)
HRESULT hr;
DWORD res;
BSTR url;
LONGLONG pts;
stream = load_resource(L"i420-64x64.avi", L"video/avi");
@ -1343,6 +1344,7 @@ static void test_TransferVideoFrame(void)
ok(!res, "Unexpected res %#lx.\n", res);
SetRect(&dst_rect, 0, 0, desc.Width, desc.Height);
IMFMediaEngineEx_OnVideoStreamTick(notify->media_engine, &pts);
hr = IMFMediaEngineEx_TransferVideoFrame(notify->media_engine, (IUnknown *)texture, NULL, &dst_rect, NULL);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
@ -2331,8 +2333,12 @@ static void test_GetSeekable(void)
ok(time_range == NULL || broken(time_range == (IMFMediaTimeRange *)0xdeadbeef) /* <= Win10 1507 */,
"Got unexpected pointer.\n");
/* IMFMediaEngineEx_Shutdown can release in parallel. A small sleep allows this test to pass more
* often than not. But given its a matter of timing, this test is marked flaky
*/
Sleep(10);
refcount = IMFMediaEngineEx_Release(media_engine);
todo_wine
flaky_wine
ok(!refcount, "Got unexpected refcount %lu.\n", refcount);
/* Unseekable bytestreams */
@ -2631,7 +2637,6 @@ static void test_SetCurrentTime(void)
ok(hr == MF_E_SHUTDOWN, "Unexpected hr %#lx.\n", hr);
refcount = IMFMediaEngineEx_Release(media_engine);
todo_wine
ok(!refcount, "Got unexpected refcount %lu.\n", refcount);
/* Unseekable bytestreams */

File diff suppressed because it is too large Load diff