【问题标题】:Asynchronous MFT is not sending MFTransformHaveOutput Event(Intel Hardware MJPEG Decoder MFT)异步 MFT 未发送 MFTransformHaveOutput 事件(英特尔硬件 MJPEG 解码器 MFT)
【发布时间】:2017-07-05 18:21:33
【问题描述】:

我正在使用 MediaFoundation SourceReader 技术开发 USB 摄像头流式桌面应用程序。该相机支持 USB3.0,1080p MJPG 视频格式分辨率为 60fps。

我使用 Software MJPEG Decoder MFT 将 MJPG 转换为 YUY2 帧,然后转换为 RGB32 帧在窗口上绘制。使用此软件解码器时,我只能在窗口上渲染 30fps,而不是 60fps。我在这个网站上发布了一个问题,并得到了一些使用英特尔硬件 MJPEG 解码器 MFT 来解决丢帧问题的建议。

为了使用这个硬件 MJPEG 解码器,我已经解决了异步 MFT 处理模型,并通过 IMFTransform 接口为 IMFMediaEventGenerator 配置了异步回调。

使用 ProcessMessage 方法调用 MFT_MESSAGE_NOTIFY_START_OF_STREAM 后,我收到了两次 MFTransfromNeedInput 事件,但没有收到来自 MFT 的 MFTransformHaveOutput 事件。

我在这里分享了我的代码供您参考:

IMFTransform* m_pTransform = NULL;

HRESULT EnumDecoderMFT ()
{
    HRESULT hr;
    IMFActivate** ppActivate;   
    UINT32 numDecodersMJPG = 0;
    LPWSTR lpMFTName = 0;

    MFT_REGISTER_TYPE_INFO inputFilter = {MFMediaType_Video,MFVideoFormat_MJPG};
    MFT_REGISTER_TYPE_INFO outputFilter = {MFMediaType_Video,MFVideoFormat_YUY2};

    UINT32 unFlags = MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_ASYNCMFT | MFT_ENUM_FLAG_LOCALMFT | MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER;

    hr = MFTEnumEx(MFT_CATEGORY_VIDEO_DECODER, unFlags, &inputFilter, &outputFilter, &ppActivate, &numDecodersMJPG);
    if (FAILED(hr)) return hr; 

    hr = ppActivate[0]->GetAllocatedString(MFT_FRIENDLY_NAME_Attribute,&lpMFTName,0);
    if (FAILED(hr)) return hr;

    // Activate transform
    hr = ppActivate[0]->ActivateObject(__uuidof(IMFTransform), (void**)&m_pTransform);
    if (FAILED(hr)) return hr;

    hr = hr = m_pTransform->GetAttributes(&pAttributes);
    if (SUCCEEDED(hr))
    {
        hr = pAttributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);           
        if(FAILED(hr)) return hr;

        hr = pAttributes->SetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE,TRUE);        
        if(FAILED(hr)) return hr;

        hr = pAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE);
        if(FAILED(hr)) return hr;

        hr = m_pTransform->QueryInterface(IID_IMFMediaEventGenerator,(void**)&m_pEventGenerator);           
        if(FAILED(hr)) return hr;

        hr = m_pEventGenerator->BeginGetEvent((IMFAsyncCallback*)this,NULL);
        if(FAILED(hr)) return hr;

        pAttributes->Release();
    }

    SafeRelease(&ppActivate[0]);

    CoTaskMemFree(ppActivate);

    return hr;      
}

HRESULT Invoke(IMFAsyncResult *pResult)
{
    HRESULT hr = S_OK,hrStatus;
    MediaEventType meType = MEUnknown;  // Event type
    IMFMediaEvent *pEvent = NULL;

    // Get the event from the event queue.
    hr = m_pEventGenerator->EndGetEvent(pResult, &pEvent);      //Completes an asynchronous request for the next event in the queue.
    if(FAILED(hr)) return hr;

    // Get the event type. 
    hr = pEvent->GetType(&meType);
    if(FAILED(hr)) return hr;

    hr = pEvent->GetStatus(&hrStatus);
    if(FAILED(hr)) return hr;

    if(SUCCEEDED(hrStatus))
    {
        if(meType == METransformNeedInput)
        {       
            SetEvent(m_hNeedInputEvent);
        }
        else if(meType == METransformHaveOutput)
        {           
            SetEvent(m_hHaveOutputEvent);
        }
        else if(meType == METransformDrainComplete)
        {
            hr = m_pTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH,0);
            if(FAILED(hr)) return hr;
        }
        else if(meType == MEError)
        {
            PROPVARIANT pValue;
            hr = pEvent->GetValue(&pValue);         
            if(FAILED(hr)) return hr;   
        }

        hr = m_pEventGenerator->BeginGetEvent((IMFAsyncCallback*)this,NULL);    
        if(FAILED(hr)) return hr;
    }

done:
    SafeRelease(&pEvent);
    return S_OK;
}

HRESULT CMFSourceReader::OnReadSample(
    HRESULT hrStatus,
    DWORD  dwStreamIndex ,
    DWORD  dwStreamFlags ,
    LONGLONG  llTimestamp ,
    IMFSample *pSample      // Can be NULL
    )
{
    HRESULT hr = S_OK;
    IMFMediaBuffer *pBuffer = NULL;
    DWORD dwcbTotLen = 0;           
    IMFSample *mftOutSample = NULL;

    EnterCriticalSection(&m_critsec);

    if (FAILED(hrStatus))
    {
        hr = hrStatus;
    }

    if (SUCCEEDED(hr))
    {
        if (pSample != NULL)
        {
            if(dwStreamIndex == 0)      //VideoStream
            {                   
                if(m_pTransform)
                {   
                    hr = m_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
                    if(FAILED(hr))  return hr;

                    m_dwWaitObj = WaitForSingleObject(m_hNeedInputEvent,INFINITE);
                    if(m_dwWaitObj == WAIT_OBJECT_0)
                    {                           
                        hr = ProcessInputSample(pSample);
                        if(FAILED(hr))  return hr;
                    }

                    m_dwWaitObj = WaitForSingleObject(m_hHaveOutputEvent,INFINITE);
                    if(m_dwWaitObj == WAIT_OBJECT_0)
                    {
                        hr = ProcessOutputSample(&mftOutSample);
                        if(FAILED(hr))  return hr;
                    }
                }
            }
        }
    }

    if(SUCCEEDED(hr))
    {
        if(m_pReader != NULL)
        {
            hr = m_pReader->ReadSample(
                (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
                0,
                NULL,   // actual
                NULL,   // flags
                NULL,   // timestamp
                NULL    // sample
                );
            if(FAILED(hr)) return hr;
        }
    }

    SafeRelease(&mftOutSample);

    LeaveCriticalSection(&m_critsec);
    return hr; 
}

HRESULT ProcessOutputSample(IMFSample **pOutSample)
{
    HRESULT hr = S_OK;
    MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
    DWORD processOutputStatus = 0,mftOutFlags = 0;
    MFT_OUTPUT_STREAM_INFO StreamInfo;
    IMFSample *mftOutSample = NULL;
    IMFMediaBuffer *pOutBuffer = NULL;

    if(m_pTransform != NULL)
    {   
        hr = m_pTransform->GetOutputStreamInfo(0, &StreamInfo);
        if(FAILED(hr)) return hr;

        DWORD status = 0;
        hr = m_pTransform->GetOutputStatus(&status);
        if (FAILED(hr)) return hr;

        hr = MFCreateSample(&mftOutSample);
        if(FAILED(hr)) return hr;

        hr = MFCreateMemoryBuffer(StreamInfo.cbSize, &pOutBuffer);
        if(FAILED(hr)) return hr;

        hr = mftOutSample->AddBuffer(pOutBuffer);
        if(FAILED(hr)) return hr;

        outputDataBuffer.dwStreamID = 0;
        outputDataBuffer.dwStatus = 0;
        outputDataBuffer.pEvents = NULL;
        outputDataBuffer.pSample = mftOutSample;

        hr = m_pTransform->ProcessOutput(0, 1, &outputDataBuffer, &processOutputStatus);            
        if(FAILED(hr)) return hr;

        hr = m_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0);
        if (FAILED(hr))  return hr;

        hr = m_pTransform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
        if (FAILED(hr))  return hr;

        if(mftOutSample)
        {
            *pOutSample = mftOutSample;
            (*pOutSample)->AddRef();
        }

        ResetEvent(m_hHaveOutputEvent);
    }

    SafeRelease(&mftOutSample);
    SafeRelease(&pOutBuffer);

    return hr;
}

HRESULT ProcessInputSample(IMFSample *pInputSample)
{
    HRESULT hr;

    if(m_pTransform != NULL)
    {               
        hr = m_pTransform->ProcessInput(0, pInputSample, 0);
        if(FAILED(hr)) return hr;

        hr = m_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM,0);
        if(FAILED(hr)) return hr;

        ResetEvent(m_hNeedInputEvent);
    }

    return hr;
}

我在我的代码中注释了 ProcessOutputSample() 方法并检查了,连续 MFT 发送 MFTransformNeedInput 事件类型。在 ProcessInput 示例之后,我有 ProcessOutput 方法,但它返回了 E_UNEXPECTED 错误。我在 MSDN 中读到过这个错误,他们提到我不应该调用 IMFTransform:: ProcessOutput 方法而不接收 MFTransformHaveOutput 事件。

我有什么遗漏吗?我可以在 MediaFoundation 中使用英特尔硬件 MJPEG 解码器 MFT 吗?有人提供使用此解码器的示例吗?过去 4 天,我一直在努力解决这个问题。

提前致谢。

【问题讨论】:

  • 那是什么网络摄像头? (只是好奇)
  • 我正在使用此链接中提到的相机:e-consystems.com/13mp-autofocus-usb-camera.asp
  • @Abi,我也面临同样的问题。你能解决问题吗?如果是的话,你能指导我看任何样品吗?

标签: video visual-c++ media ms-media-foundation


【解决方案1】:

首先,你不需要这样称呼:

hr = pAttributes->SetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE,TRUE);

Mft 对此负责,因为它是异步的,所以您可以假设它为 TRUE。您只需检查调用 GetUINT32 是否真的是 TRUE。

第二:

hr = pAttributes->SetUINT32(MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, TRUE);

这不是为了 MFT。此属性适用于源读取器或接收器写入器:MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS attribute

从您的代码中,我看到的问题是您总是在 OnReadSample 中调用 hr = m_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);,并且您应该在开始时调用一次。

同样适用于 ProcessInputSample 和 ProcessOutputSample,你调用 hr = m_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM,0);,你告诉 MFT 流已经结束......

你的代码应该处理这样的事情:

  • 开始解码
  • MFT_MESSAGE_NOTIFY_START_OF_STREAM
  • 根据需要处理输入
  • 根据需要处理输出
  • ...
  • ...
  • 根据需要处理输入
  • 根据需要处理输出
  • MFT_MESSAGE_NOTIFY_END_OF_STREAM
  • 结束解码

您永远不会收到 outputsample,因为您告诉 MFT 流在第一个 inputsample 处理之后就结束了。

阅读: MFT_MESSAGE_NOTIFY_START_OF_STREAM

通知媒体基础转换 (MFT) 第一个样本即将被处理。

是第一个样本,不是所有样本。

编辑

CMFSourceReader::OnReadSample 中还有一个问题:

m_dwWaitObj = WaitForSingleObject(m_hNeedInputEvent,INFINITE);

if(m_dwWaitObj == WAIT_OBJECT_0)
{                           
   hr = ProcessInputSample(pSample);
   if(FAILED(hr))  return hr;
}

m_dwWaitObj = WaitForSingleObject(m_hHaveOutputEvent,INFINITE);

if(m_dwWaitObj == WAIT_OBJECT_0)
{
   hr = ProcessOutputSample(&mftOutSample);
   if(FAILED(hr))  return hr;
}

您首先等待 m_hNeedInputEvent,然后等待 m_hHaveOutputEvent。但是如果您在 m_hHaveOutputEvent 之前收到两次 m_hNeedInputEvent 会发生什么。此代码不正确。您没有正确处理 Invoke 。只有在 ProcessInput 完成时才应调用 OnReadSample。总体设计似乎不正确。

更新

当您在 CMFSourceReader::OnReadSample 中收到一个样本时,您只需要将样本放入一个列表 (Queue(Sample)) 中排队。 要管理样本列表,您可以使用这种类型的代码:SamplePool/ThreadSafeQueue

在 CMFSourceReader::Invoke 中,当您收到 METransformNeedInput 时,只需 DeQueue(Sample) 并调用 ProcessInputSample。

在 CMFSourceReader::Invoke 中,当收到 m_hHaveOutputEvent 时,调用 ProcessOutputSample。

两件事:

  • 您可以在程序开始时调用m_pReader->ReadSample 3 次,等待列表中有3 个样本。当您有三个样本时,您可以开始解码,像这样您将确定当 METransformNeedInput 发生时,您已经准备好处理一个样本。此时可以调用 m_pReader->ReadSample,在 ProcessInputSample 之后维护列表中的三个样本。
  • 解码器的处理速度可能比源读取器读取样本的速度快,或者相反。所以检查 METransformNeedInput 时,列表中总是有样本。该策略是在解码过程中保持合理的样本数,比如说三个。

【讨论】:

  • 感谢您详细解答我的疑问,Mofo。在发布这个问题之前,我已经尝试过这种方式,但没有运气。现在,我已经根据您的 cmets 修改了我的代码并执行了二进制文件,但仍然面临同样的问题。我已在以下保管箱链接中附加了修改后的代码。dropbox.com/s/9pu2rddar2mx42x/…。当我必须将此 MFT_MESSAGE_NOTIFY_END_OF_STREAM 发送到 MFT 时,您能告诉我吗?我在更改 MJPG 视频格式时在 MFT 中设置了这个标志。这是告诉 MFT 的正确方法吗?有什么帮助吗?
  • 我编辑我的消息。如果可能,发布完整的代码。
  • 谢谢你,mofo。是的,你是对的。输入事件调用了两次。因为我今天请假,所以明天我将更改代码并将代码发送给您。再次感谢您。
  • 我已经修改了代码,在 m_hHaveOutputEvent 之前接收一次 m_hNeedInputEvent。以下 Dropbox 链接中的 PFA 示例应用程序代码。 dropbox.com/s/j78na6frnfrej1r/SourceReaderSampleApp.zip?dl=0。请查看我的示例应用程序代码,如果我做错了什么,请告诉我。我对以下语句有一个查询: OnReadSample 仅应在 ProcessInput 完成时调用 - 如何在 OnReadSample 之前调用 ProcessInput 方法?因为我们必须将 IMFSample 发送到 ProcessInput API,后者在 OnReadSample 方法中接收它。如果我理解错了,请纠正我。
  • 稍后我会检查您的代码。当然你需要先调用 OnReadSample。但一旦完成,您将再次调用 OnReadSample,仅在 ProcessInput 完成时。
【解决方案2】:

在英特尔硬件上输入第一个输入样本后,Transform 的事件生成器立即返回了相同的 E_UNEXPECTED(“未指定错误”)错误,并且进一步调用刚刚返回“Transform 需要更多输入”,但没有输出产生。不过,相同的代码在 Nvidia 机器上运行良好。经过大量的试验和研究,我发现我创建了太多的 D3d11Device 实例,在我的例子中,我分别创建了 2 到 3 个用于捕获、颜色转换和硬件编码器的设备。然而,我可以简单地重用一个 D3dDevice 实例。不过,创建多个 D3d11Device 实例可能适用于高端机器。这在任何地方都没有记录。我什至找不到“E_UNEXPECTED”错误原因的线索。哪里都没有提到。有很多类似的 StackOverflow 线程没有得到答复,即使是微软的人也无法指出问题所在,给出了完整的源代码。

重用 D3D11Device 实例解决了这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-15
    • 2020-03-05
    • 2017-09-11
    • 1970-01-01
    • 2016-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多