Media Foundation 获取拓扑节点的 IMFMediaType

Media Foundation get IMFMediaType of a topology node

我编写了一个基于 SampleGrabberSink 示例的应用程序。我的应用程序实际上如我所愿地工作,但我需要执行的一项功能是在 .mp4 源部分更改到文件中时获取视频分辨率。我最终想出了如何去做,但看起来很啰嗦,我怀疑一定有更简单的方法。

在下面的示例中,有没有办法缩短处理 MESessionStreamSinkFormatChanged 案例的代码块?看起来需要将近 40 行代码(包括初始化和清理)应该需要 1 或 2。

HRESULT RunSession(IMFMediaSession *pSession, IMFTopology *pTopology, OnVideoResolutionChangedFunc onVideoResolutionChanged)
{
  IMFMediaEvent *pEvent = NULL;
  IMFTopologyNode *pNode = nullptr;
  IMFStreamSink *pStreamSink = nullptr;
  IUnknown *pNodeObject = NULL;
  IMFMediaTypeHandler *pMediaTypeHandler = nullptr;
  IMFMediaType *pMediaType = nullptr;

  PROPVARIANT var;
  PropVariantInit(&var);

  HRESULT hr = S_OK;
  CHECK_HR(hr = pSession->SetTopology(0, pTopology));
  CHECK_HR(hr = pSession->Start(&GUID_NULL, &var));

  while(1)
  {
    HRESULT hrStatus = S_OK;
    MediaEventType met;

    CHECK_HR(hr = pSession->GetEvent(0, &pEvent));
    CHECK_HR(hr = pEvent->GetStatus(&hrStatus));
    CHECK_HR(hr = pEvent->GetType(&met));

    if(FAILED(hrStatus))
    {
      printf("Session error: 0x%x (event id: %d)\n", hrStatus, met);
      hr = hrStatus;
      goto done;
    }
    else
    {
      //printf("Session event: event id: %d\n",  met);
      switch(met)
      {
      case MESessionStreamSinkFormatChanged:
        //std::cout << "MESessionStreamSinkFormatChanged." << std::endl;

        {
          MF_TOPOLOGY_TYPE nodeType;
          UINT64 outputNode{0};
          GUID majorMediaType;
          UINT64 videoResolution{0};
          UINT32 stride{0};

          // This seems a ridiculously convoluted way to extract the change to the video resolution. There may
          // be a simpler way but then again this is the Media Foundation and COM!
          CHECK_HR_ERROR(pEvent->GetUINT64(MF_EVENT_OUTPUT_NODE, &outputNode), "Failed to get ouput node from media changed event.");
          CHECK_HR_ERROR(pTopology->GetNodeByID(outputNode, &pNode), "Failed to get topology node for output ID.");
          CHECK_HR_ERROR(pNode->GetObject(&pNodeObject), "Failed to get the node's object pointer.");
          CHECK_HR_ERROR(pNodeObject->QueryInterface(IID_PPV_ARGS(&pStreamSink)), "Failed to get media stream sink from activation object.");
          CHECK_HR_ERROR(pStreamSink->GetMediaTypeHandler(&pMediaTypeHandler), "Failed to get media type handler from stream sink.");
          CHECK_HR_ERROR(pMediaTypeHandler->GetCurrentMediaType(&pMediaType), "Failed to get current media type.");
          CHECK_HR_ERROR(pMediaType->GetMajorType(&majorMediaType), "Failed to get major media type.");

          if(majorMediaType == MFMediaType_Video)
          {
            CHECK_HR_ERROR(pMediaType->GetUINT64(MF_MT_FRAME_SIZE, &videoResolution), "Failed to get new video resolution.");
            CHECK_HR_ERROR(pMediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride), "Failed to get the new stride.");
            std::cout << "Media session video resolution changed to width " << std::to_string(HI32(videoResolution)) 
              << " and height " << std::to_string(LO32(videoResolution)) 
              << " and stride " << stride << "." << std::endl;
            if(onVideoResolutionChanged != nullptr) {
              onVideoResolutionChanged(HI32(videoResolution), LO32(videoResolution), stride);
            }
          }
          break;
        }
      default:
        break;
      }
    }

    if(met == MESessionEnded)
    {
      break;
    }
    SafeRelease(&pEvent);
    SafeRelease(&pNode);
    SafeRelease(&pStreamSink);
    SafeRelease(&pNodeObject);
    SafeRelease(&pMediaTypeHandler);
    SafeRelease(&pMediaType);
  }

done:
  SafeRelease(&pEvent);
  SafeRelease(&pNode);
  SafeRelease(&pStreamSink);
  SafeRelease(&pNodeObject);
  SafeRelease(&pMediaTypeHandler);
  SafeRelease(&pMediaType);
  return hr;
}
// This seems a ridiculously convoluted way to extract the change to the video resolution. There may
// be a simpler way but then again this is the Media Foundation and COM!

代码看起来不错。您不需要对分辨率更改做任何事情 - 您可以只检索一次媒体类型处理程序并在需要时保留指针。

针对上面有趣的评论,我想说以下几点。就像在 DirectShow 的情况下一样,Sample Grabber 是一种偷工减料并违反管道设计的方法。几乎每个人都喜欢 DirectShow Sample Grabber,因此如果有足够多的人首先为 Media Foundation 开发,那么 Media Foundation Sample Grabber 的未来可能就是这样。

分辨率更改通常是原语的业务,即源转换、转换转换和转换接收器连接。即使在这种情况下,您也会收到有关带外分辨率更改的通知(对您来说是异步通知),幸运的是 Media Foundation 及其 Sample Grabber 非常灵活,您可以首先处理这个问题。

要可靠地实现这一点,您通常需要一个自定义媒体接收器,但 Sample Grabber 甚至可以让您在这个时候偷工减料。

通过自定义接收器实现,您可以保证在您首先同意新分辨率之前不会收到具有新分辨率的媒体样本(当然您可以拒绝)。然而,使用 MESessionStreamSinkFormatChanged 事件被发布用于异步检索并且 Sample Grabber 继续处理,因此从技术上讲,您可以在获取会话事件之前使用新分辨率的帧进行抓取器回调。

如果在您的实际应用程序中,您使用流接收器创建输出节点,而不是像上面示例中那样激活媒体接收器,则您不需要使用拓扑节点检索媒体类型句柄 - 您可以直接将其拉出。