2017-02-16 96 views
0

我正在开发使用MediaFoundation SourceReader技术的USB摄像头流式桌面应用程序。该相机具有USB3.0支持,并可提供60fps的1080p MJPG视频格式分辨率。异步MFT不发送MFTransformHaveOutput事件(英特尔硬件MJPEG解码器MFT)

我用软件MJPEG解码器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天,我正在努力解决这个问题。

在此先感谢。

+0

什么摄像头是? (只是好奇) – YePhIcK

+0

我使用的是这个链接中提到的相机:https://www.e-consystems.com/13mp-autofocus-usb-camera.asp – Abi

回答

0

首先,你不需要调用这个:

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

MFT中负责这一点,因为它是异步的,你可以认为这是真的。你可以检查它是真的调用GetUINT32。

二:

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);,你应该在beginnig中调用一次。

与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
  • 结束解码

,因为你告诉该流已经结束MFT你永远不会收到outputsample就在第一个输入采样过程之后。

阅读本: 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两次会发生什么情况。此代码不正确。您不能正确处理调用。只有在ProcessInput完成时才应调用OnReadSample。总体设计似乎并不正确。

UPDATE

当您收到CMFSourceReader :: OnReadSample样品,你只需要在排队列表(队列(样本))的样品。 要管理样本列表,你可以使用这种类型的代码:SamplePool/ThreadSafeQueue

在CMFSourceReader ::调用,当您收到METransformNeedInput,刚刚出列(样本),并调用ProcessInputSample。

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

两件事情:

  • 您可以拨打m_pReader->在节目的开头ReadSample三次,并等待在列表三个样品。当你有三个样本时,你可以开始解码,就像这样,你会确信当METransformNeedInput发生时,你有一个样本可以处理。您可以在此时调用m_pReader-> ReadSample,在ProcessInputSample之后在列表中维护三个样本。
  • 解码器处理速度可能比源读取器读取样本过快,或者相反。因此,检查当METransformNeedInput时,列表中总是有样本。该策略是在解码过程中保持合理的采样计数,假设有三个。
+0

感谢您对我的查询的详细解答, MOFO。在发布这个问题之前,我尝试过这种方式,但没有运气。现在,我已经根据您的意见修改了我的代码,并且仍然执行了二进制代码,面临同样的问题。我已将修改的代码附加到以下Dropbox链接中.https://www.dropbox.com/s/9pu2rddar2mx42x/HwMJPEGDecoderMFT_Code_Query.txt?dl = 0。当我必须将此MFT_MESSAGE_NOTIFY_END_OF_STREAM发送给MFT时,请让我知道吗?我在MFT中设置了这个标志,同时改变了MJPG视频格式。这是正确的方式告诉MFT吗?任何帮助? – Abi

+0

我编辑我的消息。如果可能的话发布完整的代码。 – mofo77

+0

谢谢mofo。是的,你是对的。输入事件正在调用两次。自从我今天休假以后,我会在明天更改密码并向您发送密码。再一次感谢你。 – Abi