媒体基础 - 如何在 MFT(媒体基础转换)中更改帧大小

Posted

技术标签:

【中文标题】媒体基础 - 如何在 MFT(媒体基础转换)中更改帧大小【英文标题】:Media Foundation - How to change frame-size in MFT (Media Foundation Transform) 【发布时间】:2015-01-16 10:30:47 【问题描述】:

我正在尝试实现能够旋转视频的 MFT。旋转本身将在变换函数内完成。为此,我需要更改输出帧大小,但我不知道该怎么做。

作为起点,我使用了 Microsoft 提供的 MFT_Grayscale 示例。我将此 MFT 作为转换节点包含在部分拓扑中

HRESULT Player::AddBranchToPartialTopology(
    IMFTopology *pTopology,
    IMFPresentationDescriptor *pSourcePD,
    DWORD iStream
    )

    ...
    IMFTopologyNode pTransformNode = NULL;
    ...
    hr = CreateTransformNode(CLSID_GrayscaleMFT, &pTransformNode);
    ...
    hr = pSourceNode->ConnectOutput(0, pTransformNode, 0);
    hr = pTransformNode->ConnectOutput(0, pOutputNode, 0);
    ...

到目前为止,此代码正在运行。灰度 mft 已应用并按预期工作。无论如何,我想改变这个 mft 来处理视频旋转。所以让我们假设我想将视频旋转 90 度。为此,我的输入框的宽度和高度必须切换。我尝试了不同的东西,但它们都没有按预期工作。 基于此线程How to change Media Foundation Transform output frame(video) size? 中的第一条评论,我开始更改 SetOutputType 的实现。我在 GetOutputType 中调用了 GetAttributeSize 来接收实际的 frame_size。当我尝试设置新的 frame_size 时失败(开始播放时我收到 hresult 0xc00d36b4(指定的数据无效、不一致或不受此对象支持)

HRESULT CGrayscale::SetOutputType(
    DWORD           dwOutputStreamID,
    IMFMediaType    *pType, // Can be NULL to clear the output type.
    DWORD           dwFlags 
    )
 ....
    //Receive the actual frame_size of pType (works as expected)
    hr = MFGetAttributeSize(
    pType,
    MF_MT_FRAME_SIZE,
    &width,
    &height
    ));
    ...
    //change the framesize 
    hr = MFSetAttributeSize(
    pType,
    MF_MT_FRAME_SIZE,
    height,
    width
    ));

我确定我在这里错过了一些东西,所以任何提示将不胜感激。

提前致谢

【问题讨论】:

我刚碰到这个问题。在我尝试写下答案之前,您是否还在此处寻找解决方案?还是您自己解决了/继续前进? 您好,感谢您的回复。虽然我实际上不再研究这个话题,但我仍然对如何解决这个问题感兴趣。因此,如果您愿意在这里写一个答案,我们将不胜感激:) 【参考方案1】:

在 W8+ 中有一个 transform 可用,它应该进行旋转。我自己没有太多运气,但大概可以让它工作。我假设这对你来说不是一个可行的解决方案。

更有趣的案例是创建一个 MFT 来进行转换。

事实证明,将“灰度”转换为旋转器需要多个步骤。

1)正如您所推测的,您需要影响输出类型的帧大小。但是,更改传递给 SetOutputType 的类型是错误的。发送到 SetOutputType 的 pType 是客户端要求您支持的类型。将该媒体类型更改为他们要求的其他,然后返回 S_OK 表示您支持它是没有意义的。

您需要更改的是从 GetOutputAvailableType 返回的值。

2) 在计算从 GetOutputAvailableType 发回的类型时,您需要基于客户端发送给 SetInputType 的 IMFMediaType,并进行一些更改。是的,您想要调整 MF_MT_FRAME_SIZE,但您可能还需要调整 MF_MT_DEFAULT_STRIDE、MF_MT_GEOMETRIC_APERTURE 和(可能)MF_MT_MINIMUM_DISPLAY_APERTURE。可以想象,您可能还需要调整 MF_MT_SAMPLE_SIZE。

3)您没有说您是否打算在流开始时固定旋转量,或者在播放过程中发生变化。当我写这篇文章时,我使用了从 IMFTransform::GetAttributes 返回的 IMFAttributes 来指定旋转。在处理每一帧之前,读取当前值。为了使这项工作正常进行,您需要能够从 OnProcessOutput 发回 MF_E_TRANSFORM_STREAM_CHANGE。

4) 懒惰,我不想弄清楚如何旋转 NV12 或 YUY2 或类似的东西。但是对于 RGB32,有现成的功能可以做到这一点。所以当我的 GetInputAvailableType 被调用时,我要求 RGB32。

我尝试支持其他输入类型,如 RGB24、RGB565 等,但遇到了问题。当您的输出类型为 RGB24 时,MF 会在下游添加另一个 MFT,以将 RGB24 转换回更容易使用的东西(可能是 RGB32)。而且 MFT 支持在中途更改媒体类型。我能够通过接受各种输入子类型来实现这一点,但始终输出 RGB32,按指定旋转。

这听起来很复杂,但大多数情况下并非如此。如果您阅读代码,您可能会说“哦,我明白了”。我会为您提供我的源代码,但我不确定它对您有多大用处。它是用 c# 编写的,而你问的是 c++。

另一方面,我正在制作一个模板,以便更轻松地编写 MFT。 ~ 十几行 c# 代码来创建最简单的 MFT。根据 VS 的分析/计算代码指标(不包括模板)计算,c# 旋转 MFT 约为 131 行。我正在试验一个c++版本,但还是有点粗糙。

我是不是忘记了什么?大概是一堆东西。就像不要忘记为您的 MFT 生成一个新的 Guid 而不是使用灰度。但我认为我已经达到了最高点。

编辑:现在我的 c++ 版本的模板开始工作,我觉得发布一些实际代码很舒服。这可能会使上面的一些观点更清楚。例如在 #2 中,我谈到基于输入类型的输出类型。您可以在 CreateOutputFromInput 中看到这种情况。而实际的轮换代码在WriteIt()中。

我已经稍微简化了代码的大小,但希望这会让你“哦,我明白了。”

void OnProcessSample(IMFSample *pSample, bool Discontinuity, int InputMessageNumber)

    HRESULT hr = S_OK;

    int i = MFGetAttributeUINT32(GetAttributes(), AttribRotate, 0);
    i &= 7;

    // Will the output use different dimensions than the input?
    bool IsOdd = (i & 1) == 1;

    // Does the current AttribRotate rotation give a different 
    // orientation than the old one?
    if (IsOdd != m_WasOdd)
    
        // Yes, change the output type.
        OutputSample(NULL, InputMessageNumber);
        m_WasOdd = IsOdd;
    

    // Process it.
    DoWork(pSample, (RotateFlipType)i);

    // Send the modified input sample to the output sample queue.
    OutputSample(pSample, InputMessageNumber);


void OnSetInputType()

    HRESULT hr = S_OK;

    m_imageWidthInPixels = 0;
    m_imageHeightInPixels = 0;
    m_cbImageSize = 0;
    m_lInputStride = 0;

    IMFMediaType *pmt = GetInputType();

    // type can be null to clear
    if (pmt != NULL)
    
        hr = MFGetAttributeSize(pmt, MF_MT_FRAME_SIZE, &m_imageWidthInPixels, &m_imageHeightInPixels);
        ThrowExceptionForHR(hr);

        hr = pmt->GetUINT32(MF_MT_DEFAULT_STRIDE, &m_lInputStride);
        ThrowExceptionForHR(hr);

        // Calculate the image size (not including padding)
        m_cbImageSize = m_imageHeightInPixels * m_lInputStride;
    
    else
    
        // Since the input must be set before the output, nulling the 
        // input must also clear the output.  Note that nulling the 
        // input is only valid if we are not actively streaming.

        SetOutputType(NULL);
    


IMFMediaType *CreateOutputFromInput(IMFMediaType *inType)

    // For some MFTs, the output type is the same as the input type.  
    // However, since we are rotating, several attributes in the 
    // media type (like frame size) must be different on our output.  
    // This routine generates the appropriate output type for the 
    // current input type, given the current state of m_WasOdd.

    IMFMediaType *pOutputType = CloneMediaType(inType);

    if (m_WasOdd)
    
        HRESULT hr;
        UINT32 h, w;

        // Intentionally backward
        hr = MFGetAttributeSize(inType, MF_MT_FRAME_SIZE, &h, &w);
        ThrowExceptionForHR(hr);

        hr = MFSetAttributeSize(pOutputType, MF_MT_FRAME_SIZE, w, h);
        ThrowExceptionForHR(hr);

        MFVideoArea *a = GetArea(inType, MF_MT_GEOMETRIC_APERTURE);
        if (a != NULL)
        
            a->Area.cy = h;
            a->Area.cx = w;
            SetArea(pOutputType, MF_MT_GEOMETRIC_APERTURE, a);
        

        a = GetArea(inType, MF_MT_MINIMUM_DISPLAY_APERTURE);
        if (a != NULL)
        
            a->Area.cy = h;
            a->Area.cx = w;
            SetArea(pOutputType, MF_MT_MINIMUM_DISPLAY_APERTURE, a);
        

        hr = pOutputType->SetUINT32(MF_MT_DEFAULT_STRIDE, w * 4);
        ThrowExceptionForHR(hr);
    

    return pOutputType;


void WriteIt(BYTE *pBuffer, RotateFlipType fm)

    Bitmap *v = new Bitmap((int)m_imageWidthInPixels, (int)m_imageHeightInPixels, (int)m_lInputStride, PixelFormat32bppRGB, pBuffer);
    if (v == NULL)
        throw (HRESULT)E_OUTOFMEMORY;

    try
    
        Status s;

        s = v->RotateFlip(fm);
        if (s != Ok)
            throw (HRESULT)E_UNEXPECTED;

        Rect r;

        if (!m_WasOdd)
        
            r.Width = (int)m_imageWidthInPixels;
            r.Height = (int)m_imageHeightInPixels;
        
        else
        
            r.Height = (int)m_imageWidthInPixels;
            r.Width = (int)m_imageHeightInPixels;
        

        BitmapData bmd;
        bmd.Width = r.Width,
        bmd.Height = r.Height,
        bmd.Stride = 4*bmd.Width;
        bmd.PixelFormat = PixelFormat32bppARGB; 
        bmd.Scan0 = (VOID*)pBuffer;
        bmd.Reserved = NULL;

        s = v->LockBits(&r, ImageLockModeRead + ImageLockModeUserInputBuf, PixelFormat32bppRGB, &bmd);
        if (s != Ok)
            throw (HRESULT)E_UNEXPECTED;

        s = v->UnlockBits(&bmd);
        if (s != Ok)
            throw (HRESULT)E_UNEXPECTED;
    
    catch(...)
    
        delete v;
        throw;
    

    delete v;

【讨论】:

感谢您写出这么好的答案!我也对你所说的模板感兴趣。它可以在任何地方在线获得吗? 虽然我觉得它还没有完全“完成”,但我现在可以使用一些反馈。您对 c++ 或 c# 模板感兴趣吗? 您从未说过您想要 c++ 还是 c# 版本。两者现在都在http://www.LimeGreenSocks.com/MFT 准备好测试版。欢迎反馈。

以上是关于媒体基础 - 如何在 MFT(媒体基础转换)中更改帧大小的主要内容,如果未能解决你的问题,请参考以下文章

使用 C# 的 Microsoft 媒体基础转换 (MFT)?

Windows MFT(媒体基础转换)解码器未返回正确的采样时间或持续时间

如何通过媒体基础使用英特尔 Quicksync 进行解码?

如何在媒体基础上定制视频媒体/流接收器请求RGB32帧?

MFT 自定义图像过滤器

如何在媒体会话解析的拓扑末尾添加我的 MFT?