c++ - Media Foundation 获取拓扑节点的 IMFMediaType

标签 c++ ms-media-foundation

我编写了一个基于 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 的 future 可能就是这样。

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

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

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

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

关于c++ - Media Foundation 获取拓扑节点的 IMFMediaType,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57562439/

相关文章:

C++ IOCP 服务器容器信息

c++ - 有没有办法用 Boost.Log 实现多个独立的日志系统?

c++ - 摩西中对 `gzopen' 的 undefined reference

c++ - 具有静态变量的非托管静态库的托管 C++ 包装器挂起

winapi - Color Converter DSP 的 IMFTransform 接口(interface)在 SetInputType/SetOutputType 上给出 E_INVALIDARG

c# - MediaFoundation 找不到视频捕获仿真器驱动程序。但 DirectShow 确实

c++ - 等待文件存在且不被其他人占用

c++ - 在 Windows 中读取/写入各种音频文件元数据

video-streaming - 媒体基金会 : SPS/PPS problem with Intel hardware MFT

c++ - WMAV2 MFT编码器