多屏幕环境下Media Foundation(EVR)显示黑屏问题求助
问题描述
开发基于C# .NET的Windows Form应用,使用Media Foundation的EVR(Enhanced Video Renderer)实现USB摄像头画面显示。单屏幕环境运行正常,但在多屏幕系统中,当显示窗口大部分位于副屏幕时画面黑屏(例如60%在副屏、40%在主屏时黑屏,反之正常)。
用户初始化EVR引擎的示例代码:
private IMFActivate pActive = null; private object objVideoSink = null, objVideoRenderer = null, objVideoDisplayControl = null; private IMFMediaSink pVideoSink = null; private IMFVideoRenderer pVideoRenderer = null; private IMFVideoDisplayControl pVideoDisplayControl = null; private IMFStreamSink pStreamSink = null; private IMFMediaTypeHandler pSinkMediaTypeHandler = null; private int sinkMediaTypeCount = 0; private IMFMediaType pVideoOutType = null; private IMFMediaEventGenerator pEventGenerator = null, pstreamSinkEventGenerator = null; private SecureConference.AV.MediaEventHandler mediaEvtHandler = null, streamSinkMediaEvtHandler = null; private IMFVideoSampleAllocator pVideoSampleAllocator = null; private object objVideoSampleAllocator = null, objD3DManager = null; private IMFSample pD3DVideoSample = null; private IMFMediaBuffer pDstBuffer = null; private IMF2DBuffer p2DBuffer = null; private IMFPresentationClock pClock = null; private IMFPresentationTimeSource pTimeSource = null; private void Init() { HResult hr = MFExtern.MFCreateVideoRendererActivate(this.Handle, out pActive); hr = pActive.ActivateObject(typeof(IMFMediaSink).GUID, out objVideoSink); pVideoSink = (IMFMediaSink)objVideoSink; hr = MFExtern.MFGetService( pVideoSink, MFServices.MR_VIDEO_RENDER_SERVICE, typeof(IMFVideoRenderer).GUID, out objVideoRenderer ); pVideoRenderer = (IMFVideoRenderer)objVideoRenderer; hr = pVideoRenderer.InitializeRenderer(null, null); hr = MFExtern.MFGetService( pVideoSink, MFServices.MR_VIDEO_RENDER_SERVICE, typeof(IMFVideoDisplayControl).GUID, out objVideoDisplayControl ); pVideoDisplayControl = objVideoDisplayControl as IMFVideoDisplayControl; hr = pVideoDisplayControl.SetVideoWindow(this.Handle); hr = pVideoDisplayControl.SetVideoPosition(null, new MediaFoundation.Misc.MFRect(0, 0, this.Width, this.Height)); hr = pVideoSink.GetStreamSinkByIndex(0, out pStreamSink); hr = pStreamSink.GetMediaTypeHandler(out pSinkMediaTypeHandler); hr = pSinkMediaTypeHandler.GetMediaTypeCount(out sinkMediaTypeCount); hr = MFExtern.MFCreateMediaType(out pVideoOutType); hr = pVideoOutType.SetGUID(MFAttributesClsid.MF_MT_MAJOR_TYPE, MFMediaType.Video); hr = pVideoOutType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, MFMediaType.RGB32); hr = pVideoOutType.SetUINT32(MFAttributesClsid.MF_MT_INTERLACE_MODE, 2); hr = pVideoOutType.SetUINT32(MediaFoundation.MFAttributesClsid.MF_MT_ALL_SAMPLES_INDEPENDENT, 1); // to be adjusted hr = SetMFSize(MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO, 1, 1); //hr = SetMFSize(MFAttributesClsid.MF_MT_FRAME_SIZE, this.Width, this.Height); hr = SetMFSize(MFAttributesClsid.MF_MT_FRAME_SIZE, miImageWidth, miImageHeight); //hr = pVideoOutType.SetUINT64(MFAttributesClsid.MF_MT_PIXEL_ASPECT_RATIO, 4294967297); //hr = pVideoOutType.SetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, 2748779069920); hr = pSinkMediaTypeHandler.SetCurrentMediaType(pVideoOutType); mediaEvtHandler = new MediaEventHandler(); streamSinkMediaEvtHandler = new MediaEventHandler(); //hr = MFExtern.MFGetService( // pVideoSink, // MFServices.sink, // typeof(IMFMediaEventGenerator).GUID, // out objEventGenerator // ); //pEventGenerator = (IMFMediaEventGenerator)objEventGenerator; pEventGenerator = (IMFMediaEventGenerator)pVideoSink; hr = pEventGenerator.BeginGetEvent(mediaEvtHandler, pEventGenerator); pstreamSinkEventGenerator = (IMFMediaEventGenerator)pStreamSink; hr = pstreamSinkEventGenerator.BeginGetEvent(streamSinkMediaEvtHandler, pstreamSinkEventGenerator); hr = MFExtern.MFGetService( pStreamSink, MFServices.MR_VIDEO_ACCELERATION_SERVICE, typeof(IMFVideoSampleAllocator).GUID, out objVideoSampleAllocator ); pVideoSampleAllocator = (IMFVideoSampleAllocator)objVideoSampleAllocator; hr = MFExtern.MFGetService( pVideoSink, MFServices.MR_VIDEO_ACCELERATION_SERVICE, Guid.Parse("a0cade0f-06d5-4cf4-a1c7-f3cdd725aa75"), out objD3DManager ); hr = pVideoSampleAllocator.SetDirectXManager(objD3DManager); hr = pVideoSampleAllocator.InitializeSampleAllocator(1, pVideoOutType); hr = pVideoSampleAllocator.AllocateSample(out pD3DVideoSample); hr = pD3DVideoSample.GetBufferByIndex(0, out pDstBuffer); p2DBuffer = (IMF2DBuffer)pDstBuffer; hr = MFExtern.MFCreatePresentationClock(out pClock); hr = MFExtern.MFCreateSystemTimeSource(out pTimeSource); hr = pClock.SetTimeSource(pTimeSource); pVideoSink.SetPresentationClock(pClock); pClock.Start(0); } // and once I received a bitmap (bmp2), I put it to the engine: private void SetBitmap(Bitmap bmp2, long timestamp) { Rectangle rect = new Rectangle(0, 0, bmp2.Width, bmp2.Height); BitmapData bd = bmp2.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); bitmapBuffer = bd.Scan0; int bitmapBufferLength = 4 * bmp2.Width * bmp2.Height; HResult hr = HResult.S_OK; if (!(bmp2.Width == miImageWidth && bmp2.Height == miImageHeight)) { miImageWidth = bmp2.Width; miImageHeight = bmp2.Height; hr = SetMFSize(MFAttributesClsid.MF_MT_FRAME_SIZE, miImageWidth, miImageHeight); } if (mlStartTimestamp == 0) { mlStartTimestamp = timestamp; hr = pD3DVideoSample.SetSampleTime(0); } else { hr = pD3DVideoSample.SetSampleTime(timestamp - mlStartTimestamp); } hr = pD3DVideoSample.SetSampleDuration(sampleDuration); hr = p2DBuffer.ContiguousCopyFrom(bitmapBuffer, bitmapBufferLength); hr = pD3DVideoSample.SetUINT32(MFAttributesClsid.MFSampleExtension_FrameCorruption, 0); if (bitmapCount > 0) { hr = pD3DVideoSample.SetUINT32(MFAttributesClsid.MFSampleExtension_Discontinuity, 0); } hr = pD3DVideoSample.SetUINT32(MFAttributesClsid.MFSampleExtension_CleanPoint, 1); hr = pStreamSink.ProcessSample(pD3DVideoSample); bmp2.UnlockBits(bd); }
解决方案
多屏幕下EVR黑屏的核心原因是:EVR初始化时绑定了主屏幕的D3D设备,窗口跨屏后设备上下文不匹配,或未及时更新显示参数。可通过以下步骤修复:
1. 监听窗口移动事件,实时更新显示区域
窗口在屏幕间移动时,主动通知EVR更新显示位置,确保其识别当前窗口所在的显示设备。在Form的Move事件中添加处理逻辑:
private void Form_Move(object sender, EventArgs e) { if (pVideoDisplayControl != null) { Rectangle clientRect = this.ClientRectangle; // 更新视频显示位置,null表示使用媒体原生尺寸 HResult hr = pVideoDisplayControl.SetVideoPosition(null, new MediaFoundation.Misc.MFRect(0, 0, clientRect.Width, clientRect.Height)); // 强制刷新画面 pVideoDisplayControl.RepaintVideo(); } }
2. 处理EVR设备重置事件
跨屏时EVR的D3D设备可能需要重置,需监听IMFMediaEventGenerator的设备变更事件,触发显示组件重新初始化。修改MediaEventHandler的事件处理逻辑:
public class MediaEventHandler : IMFAsyncCallback { public HResult Invoke(IMFAsyncResult pAsyncResult) { IMFMediaEventGenerator pEventGenerator = pAsyncResult.AsyncState as IMFMediaEventGenerator; IMFMediaEvent pEvent = null; HResult hr = pEventGenerator.EndGetEvent(pAsyncResult, out pEvent); if (hr == HResult.S_OK) { MediaEventType eventType; hr = pEvent.GetType(out eventType); switch (eventType) { // 捕获显示设备变更事件 case MediaEventType.MEVideoDisplayChanged: case MediaEventType.MECodecDeviceChanged: ReinitializeEVRDisplay(); break; // 其他事件处理... } pEventGenerator.BeginGetEvent(this, pEventGenerator); Marshal.ReleaseComObject(pEvent); } return hr; } // 实现IMFAsyncCallback的其他接口方法... }
实现ReinitializeEVRDisplay方法,重置显示组件:
private void ReinitializeEVRDisplay() { if (pVideoDisplayControl != null && pVideoSampleAllocator != null) { // 释放现有D3D样本资源 Marshal.ReleaseComObject(pD3DVideoSample); Marshal.ReleaseComObject(pDstBuffer); Marshal.ReleaseComObject(p2DBuffer); // 重新绑定窗口并设置显示位置 HResult hr = pVideoDisplayControl.SetVideoWindow(this.Handle); Rectangle clientRect = this.ClientRectangle; hr = pVideoDisplayControl.SetVideoPosition(null, new MediaFoundation.Misc.MFRect(0, 0, clientRect.Width, clientRect.Height)); // 重新初始化样本分配器 hr = pVideoSampleAllocator.InitializeSampleAllocator(1, pVideoOutType); hr = pVideoSampleAllocator.AllocateSample(out pD3DVideoSample); hr = pD3DVideoSample.GetBufferByIndex(0, out pDstBuffer); p2DBuffer = (IMF2DBuffer)pDstBuffer; } }
3. 初始化时配置EVR自适应多屏
创建IMFActivate时添加属性,允许EVR在多屏场景下自动切换D3D设备:
HResult hr = MFExtern.MFCreateVideoRendererActivate(this.Handle, out pActive); // 允许EVR使用覆盖层渲染,提升多屏兼容性 hr = pActive.SetUINT32(MFAttributesClsid.MFVideoRendererAttribute_AllowOverlays, 1); // 禁用硬件加速(若硬件加速导致多屏兼容问题),或设置为1保留加速 hr = pActive.SetUINT32(MFAttributesClsid.MFVideoRendererAttribute_EnableDXVA, 0);
4. 使用EVR内置时钟替代手动创建时钟
手动创建的IMFPresentationClock可能在跨屏时出现同步问题,改为使用EVR内部提供的时钟:
// 移除手动创建时钟的代码 // hr = MFExtern.MFCreatePresentationClock(out pClock); // hr = MFExtern.MFCreateSystemTimeSource(out pTimeSource); // hr = pClock.SetTimeSource(pTimeSource); // pVideoSink.SetPresentationClock(pClock); // pClock.Start(0); // 获取EVR内置的演示时钟 IMFGetService pGetService = pVideoSink as IMFGetService; if (pGetService != null) { object objClock = null; hr = pGetService.GetService(MFServices.MR_PRESENTATION_CLOCK_SERVICE, typeof(IMFPresentationClock).GUID, out objClock); pClock = (IMFPresentationClock)objClock; hr = pClock.Start(0); }
内容的提问来源于stack exchange,提问作者Linus
相关产品推荐
相关产品推荐

