You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多屏幕环境下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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 15:25:14