Media Foundation - 如何在 MFT(Media Foundation Transform)中更改帧大小
Media Foundation - How to change frame-size in MFT (Media Foundation Transform)
我正在尝试实现能够旋转视频的 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 度。为此,我的输入框的宽度和高度必须切换。我尝试了不同的方法,但其中 none 的方法按预期工作。
基于此线程 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
));
}
我确定我在这里漏掉了什么,所以任何提示都将不胜感激。
提前致谢
W8+ 中有一个 transform 可以进行旋转。我自己并没有多少运气,但大概可以让它发挥作用。我假设这对您来说不是一个可行的解决方案。
更有趣的案例是创建一个 MFT 来执行 t运行sform。
原来有很多步骤可以将 'Grayscale' 变成旋转器。
1) 如您所料,您需要影响输出类型的帧大小。但是,更改传递给 SetOutputType 的类型是错误的。发送到 SetOutputType 的 pType 是客户端要求您支持的类型。将该媒体类型更改为 other 而非他们要求的内容,然后返回 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,按指定旋转。
这听起来很复杂,但实际上并非如此。如果您阅读代码,您可能会 "Oh, I get it." 我会向您提供我的源代码,但我不确定它对您有多大用处。它在 c# 中,而你问的是 c++。
另一方面,我正在制作一个模板,以便更轻松地编写 MFT。 ~十几行 C# 代码来创建最简单的 MFT。根据 VS 的 Analyze/Calculate 代码指标(不包括模板)计算,c# 旋转 MFT 约为 131 行。我正在尝试使用 c++ 版本,但它仍然有点粗糙。
我是不是忘记了什么?可能是一堆东西。就像不要忘记为您的 MFT 生成一个新的 Guid 而不是使用 Grayscale 的。但我认为我已经达到了最高点。
编辑:现在我的 c++ 版本的模板开始工作了,我很乐意发布一些实际代码。这可能会使上面的一些观点更清楚。例如在 #2 中,我谈到了基于输入类型的输出类型。您可以在 CreateOutputFromInput 中看到发生的情况。而实际的旋转代码在 WriteIt().
我已经简化了代码的大小,但希望这会让你达到 "Oh, I get it."
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;
}
我正在尝试实现能够旋转视频的 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 度。为此,我的输入框的宽度和高度必须切换。我尝试了不同的方法,但其中 none 的方法按预期工作。 基于此线程 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
));
}
我确定我在这里漏掉了什么,所以任何提示都将不胜感激。
提前致谢
W8+ 中有一个 transform 可以进行旋转。我自己并没有多少运气,但大概可以让它发挥作用。我假设这对您来说不是一个可行的解决方案。
更有趣的案例是创建一个 MFT 来执行 t运行sform。
原来有很多步骤可以将 'Grayscale' 变成旋转器。
1) 如您所料,您需要影响输出类型的帧大小。但是,更改传递给 SetOutputType 的类型是错误的。发送到 SetOutputType 的 pType 是客户端要求您支持的类型。将该媒体类型更改为 other 而非他们要求的内容,然后返回 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,按指定旋转。
这听起来很复杂,但实际上并非如此。如果您阅读代码,您可能会 "Oh, I get it." 我会向您提供我的源代码,但我不确定它对您有多大用处。它在 c# 中,而你问的是 c++。
另一方面,我正在制作一个模板,以便更轻松地编写 MFT。 ~十几行 C# 代码来创建最简单的 MFT。根据 VS 的 Analyze/Calculate 代码指标(不包括模板)计算,c# 旋转 MFT 约为 131 行。我正在尝试使用 c++ 版本,但它仍然有点粗糙。
我是不是忘记了什么?可能是一堆东西。就像不要忘记为您的 MFT 生成一个新的 Guid 而不是使用 Grayscale 的。但我认为我已经达到了最高点。
编辑:现在我的 c++ 版本的模板开始工作了,我很乐意发布一些实际代码。这可能会使上面的一些观点更清楚。例如在 #2 中,我谈到了基于输入类型的输出类型。您可以在 CreateOutputFromInput 中看到发生的情况。而实际的旋转代码在 WriteIt().
我已经简化了代码的大小,但希望这会让你达到 "Oh, I get it."
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;
}