Windows Media Foundation MFT 缓冲和视频质量问题(颜色丢失,曲线不那么平滑,尤其是文本)
Windows Media Foundation MFT buffering and video quality issues (Loss of colors, not so smooth curves, especially text)
我正在尝试使用 Windows Media Foundation 将从图像 (RGBA) 源 (Desktop/Camera) 捕获的 RGBA 缓冲区编码为原始 H264,传输它们并解码接收到的原始 H264 帧实时的另一端。我试图达到至少 30 fps。编码器工作得很好,但解码器不行。
我了解 Microsoft WMF MFT 在发出 encoded/decoded 数据之前最多缓冲 30 帧。
The image source would emit frames only when there is a change occurs
and not a continuous stream of RGBA buffers, so my intention is to
obtain a buffer of encoded/decoded data for each and every input
buffer to the respective MFT so that I can stream the data in real
time and also render it.
当我使图像源发送连续变化(通过刺激变化)时,编码器和解码器都能够发出至少 10 到 15 fps。编码器能够利用硬件加速支持。我能够在编码器端实现高达 30 fps,而且我还没有使用 DirectX 表面实现硬件辅助解码。这里的问题不是帧速率,而是 MFT 对数据的缓冲。
因此,我尝试通过发送 MFT_MESSAGE_COMMAND_DRAIN 命令来耗尽解码器 MFT,并重复调用 ProcessOutput 直到解码器 returns MF_E_TRANSFORM_NEED_MORE_INPUT。现在发生的是解码器现在每 30 个输入 h264 缓冲区只发出一帧,我什至用连续的数据流测试它,行为是相同的。 看起来解码器丢弃了 GOP 中的所有中间帧。
如果它只缓冲前几帧对我来说没问题,但我的解码器实现仅在缓冲区已满时才输出,即使在 SPS 和 PPS 解析阶段之后也是如此。
我看到了 Google 的 chromium 源代码 (https://github.com/adobe/chromium/blob/master/content/common/gpu/media/dxva_video_decode_accelerator.cc),他们遵循相同的方法。
mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
我的实现是基于
https://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/EncodeTransform.cpp
和
https://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/DecodeTransform.cpp
我的问题是,我是否遗漏了什么? Windows Media Foundation 适合实时流媒体吗?。耗尽编码器和解码器是否适用于实时用例?
我只有两个选择,让这个 WMF 工作在实时用例中,或者使用像 Intel 的 QuickSync 这样的东西。我为我的 POC 选择了 WMF,因为 Windows Media Foundation 隐式支持 Hardware/GPU/Software 回退,以防任何 MFT 不可用,并且它在内部选择最佳可用的 MFT,无需太多编码。
尽管比特率 属性 设置为 3Mbps,但我遇到了视频质量问题。但与缓冲问题相比,它的优先级最低。几个星期以来,我一直在键盘上敲打自己的脑袋,这很难解决。任何帮助将不胜感激。
代码:
编码器设置:
IMFAttributes* attributes = 0;
HRESULT hr = MFCreateAttributes(&attributes, 0);
if (attributes)
{
//attributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
attributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
}//end if (attributes)
hr = MFCreateMediaType(&pMediaTypeOut);
// Set the output media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeOut);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, cVideoEncodingFormat); // MFVideoFormat_H264
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE); //18000000
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1); // 30
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, mStreamWidth, mStreamHeight);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_High);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_MAX_KEYFRAME_SPACING, 16);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonRateControlMode, eAVEncCommonRateControlMode_UnconstrainedVBR);//eAVEncCommonRateControlMode_Quality, eAVEncCommonRateControlMode_UnconstrainedCBR);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonQuality, 100);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, FALSE);
}
if (SUCCEEDED(hr))
{
BOOL allSamplesIndependent = TRUE;
hr = pMediaTypeOut->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, allSamplesIndependent);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_COMPRESSED, TRUE);
}
if (SUCCEEDED(hr))
{
hr = mpEncoder->SetOutputType(0, pMediaTypeOut, 0);
}
// 处理传入的样本。忽略时间戳和持续时间参数,我们只是实时渲染数据。
HRESULT ProcessSample(IMFSample **ppSample, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn)
{
IMFMediaBuffer *buffer = nullptr;
DWORD bufferSize;
HRESULT hr = S_FALSE;
if (ppSample)
{
hr = (*ppSample)->ConvertToContiguousBuffer(&buffer);
if (SUCCEEDED(hr))
{
buffer->GetCurrentLength(&bufferSize);
hr = ProcessInput(ppSample);
if (SUCCEEDED(hr))
{
//hr = mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
//if (SUCCEEDED(hr))
{
while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
hr = ProcessOutput(time, duration, oDtn);
}
}
}
else
{
if (hr == MF_E_NOTACCEPTING)
{
while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
hr = ProcessOutput(time, duration, oDtn);
}
}
}
}
}
return (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ? (oDtn.numBytes > 0 ? oDtn.returnCode : hr) : hr);
}
// 查找并 returns h264 MFT(在子类型参数中给出)如果可用...否则失败。
HRESULT FindDecoder(const GUID& subtype)
{
HRESULT hr = S_OK;
UINT32 count = 0;
IMFActivate **ppActivate = NULL;
MFT_REGISTER_TYPE_INFO info = { 0 };
UINT32 unFlags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_ASYNCMFT;
info.guidMajorType = MFMediaType_Video;
info.guidSubtype = subtype;
hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
unFlags,
&info,
NULL,
&ppActivate,
&count
);
if (SUCCEEDED(hr) && count == 0)
{
hr = MF_E_TOPO_CODEC_NOT_FOUND;
}
if (SUCCEEDED(hr))
{
hr = ppActivate[0]->ActivateObject(IID_PPV_ARGS(&mpDecoder));
}
CoTaskMemFree(ppActivate);
return hr;
}
// 从编码数据重建样本
HRESULT ProcessData(char *ph264Buffer, DWORD bufferLength, LONGLONG& time, LONGLONG& duration, TransformOutput &dtn)
{
dtn.numBytes = 0;
dtn.pData = NULL;
dtn.returnCode = S_FALSE;
IMFSample *pSample = NULL;
IMFMediaBuffer *pMBuffer = NULL;
// Create a new memory buffer.
HRESULT hr = MFCreateMemoryBuffer(bufferLength, &pMBuffer);
// Lock the buffer and copy the video frame to the buffer.
BYTE *pData = NULL;
if (SUCCEEDED(hr))
hr = pMBuffer->Lock(&pData, NULL, NULL);
if (SUCCEEDED(hr))
memcpy(pData, ph264Buffer, bufferLength);
pMBuffer->SetCurrentLength(bufferLength);
pMBuffer->Unlock();
// Create a media sample and add the buffer to the sample.
if (SUCCEEDED(hr))
hr = MFCreateSample(&pSample);
if (SUCCEEDED(hr))
hr = pSample->AddBuffer(pMBuffer);
LONGLONG sampleTime = time - mStartTime;
// Set the time stamp and the duration.
if (SUCCEEDED(hr))
hr = pSample->SetSampleTime(sampleTime);
if (SUCCEEDED(hr))
hr = pSample->SetSampleDuration(duration);
hr = ProcessSample(&pSample, sampleTime, duration, dtn);
::Release(&pSample);
::Release(&pMBuffer);
return hr;
}
// 处理解码器的输出样本
HRESULT ProcessOutput(LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
IMFMediaBuffer *pBuffer = NULL;
DWORD mftOutFlags;
MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
IMFSample *pMftOutSample = NULL;
MFT_OUTPUT_STREAM_INFO streamInfo;
memset(&outputDataBuffer, 0, sizeof outputDataBuffer);
HRESULT hr = mpDecoder->GetOutputStatus(&mftOutFlags);
if (SUCCEEDED(hr))
{
hr = mpDecoder->GetOutputStreamInfo(0, &streamInfo);
}
if (SUCCEEDED(hr))
{
hr = MFCreateSample(&pMftOutSample);
}
if (SUCCEEDED(hr))
{
hr = MFCreateMemoryBuffer(streamInfo.cbSize, &pBuffer);
}
if (SUCCEEDED(hr))
{
hr = pMftOutSample->AddBuffer(pBuffer);
}
if (SUCCEEDED(hr))
{
DWORD dwStatus = 0;
outputDataBuffer.dwStreamID = 0;
outputDataBuffer.dwStatus = 0;
outputDataBuffer.pEvents = NULL;
outputDataBuffer.pSample = pMftOutSample;
hr = mpDecoder->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
}
if (SUCCEEDED(hr))
{
hr = GetDecodedBuffer(outputDataBuffer.pSample, outputDataBuffer, time, duration, oDtn);
}
if (pBuffer)
{
::Release(&pBuffer);
}
if (pMftOutSample)
{
::Release(&pMftOutSample);
}
return hr;
}
// 将解码后的样本写出来
HRESULT GetDecodedBuffer(IMFSample *pMftOutSample, MFT_OUTPUT_DATA_BUFFER& outputDataBuffer, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
// ToDo: These two lines are not right. Need to work out where to get timestamp and duration from the H264 decoder MFT.
HRESULT hr = outputDataBuffer.pSample->SetSampleTime(time);
if (SUCCEEDED(hr))
{
hr = outputDataBuffer.pSample->SetSampleDuration(duration);
}
if (SUCCEEDED(hr))
{
hr = pMftOutSample->ConvertToContiguousBuffer(&pDecodedBuffer);
}
if (SUCCEEDED(hr))
{
DWORD bufLength;
hr = pDecodedBuffer->GetCurrentLength(&bufLength);
}
if (SUCCEEDED(hr))
{
byte *pEncodedYUVBuffer;
DWORD buffCurrLen = 0;
DWORD buffMaxLen = 0;
pDecodedBuffer->GetCurrentLength(&buffCurrLen);
pDecodedBuffer->Lock(&pEncodedYUVBuffer, &buffMaxLen, &buffCurrLen);
ColorConversion::YUY2toRGBBuffer(pEncodedYUVBuffer,
buffCurrLen,
mpRGBABuffer,
mStreamWidth,
mStreamHeight,
mbEncodeBackgroundPixels,
mChannelThreshold);
pDecodedBuffer->Unlock();
::Release(&pDecodedBuffer);
oDtn.pData = mpRGBABuffer;
oDtn.numBytes = mStreamWidth * mStreamHeight * 4;
oDtn.returnCode = hr; // will be S_OK..
}
return hr;
}
更新:
在启用 CODECAPI_AVLowLatency 模式后,解码器的输出现在令人满意,但是与发送方相比,流中有 2 秒的延迟,我能够达到 15 到 20fps,这比以前好很多。当从源推送到编码器的更改数量更多时,质量会下降。我还没有实现硬件加速解码。
更新2:
我发现如果设置不当,时间戳和持续时间设置会影响视频质量。问题是,我的图像源不以恒定速率发射帧,但看起来编码器和解码器期望恒定帧速率。当我将持续时间设置为常数并以恒定的步长增加采样时间时,视频质量似乎更好但不是最好的。我不认为我正在做的是正确的方法。有没有办法指定编码器和解码器关于可变帧率?
更新3:
在设置 CODECAPI_AVEncMPVDefaultBPictureCount (0) 和 CODECAPI_AVEncCommonLowLatency 属性后,我能够从编码器和解码器获得可接受的性能。尚未探索硬件加速解码。我希望如果实现硬件解码,我能达到最好的性能。
视频质量仍然很差,边缘和曲线不清晰。文字看起来很模糊,这是不可接受的。视频和图像的质量还可以,但文本和形状的质量不行。
更新4
似乎在 YUV 子采样阶段丢失了一些颜色信息。我尝试将 RGBA 缓冲区转换为 YUV2,然后再转换回来,颜色损失是可见的,但还不错。 YUV 转换造成的损失没有 RGBA-> YUV2 -> H264 -> YUV2 -> RGBA 转换后渲染的图像质量差。很明显,不仅 YUV2 转换是质量损失的唯一原因,而且 H264 编码器进一步导致混叠。如果 H264 编码不引入混叠效果,我仍然可以获得更好的视频质量。我将探索 WMV 编解码器。唯一仍然困扰我的是 this,代码运行良好,能够捕获屏幕并将流以 mp4 格式保存在文件中。这里唯一的区别是,与上述代码中使用 MFVideoFormat_RGB32 作为输入类型的接收器编写器方法相比,我使用的是具有 MFVideoFormat_YUY2 输入格式的媒体基础转换。我仍然希望有可能通过 Media Foundation 本身获得更好的质量。如果我分别在 MFT_REGISTER_TYPE_INFO (MFTEnum)/SetInputType 中指定 MFVideoFormat_ARGB32 作为输入格式,那么 MFTEnum/ProcessInput 就会失败。
原文:
解码图像(RGBA -> YUV2 -> H264 -> YUV2 -> RGBA转换后):
Click to open in the new tab to view the full image so that you can
see the aliasing effect.
大多数消费者 H.264 编码器将颜色信息子采样到 4:2:0。 (RGB 到 YUV)
这意味着甚至在编码过程开始之前,您的 RGB 位图就会丢失 75% 的颜色信息。
H.264 更适合自然内容而不是屏幕捕获。
但是有些编解码器是专门为实现屏幕内容的良好压缩而设计的。例如:https://docs.microsoft.com/en-us/windows/desktop/medfound/usingthewindowsmediavideo9screencodec
即使您增加 H.264 编码的比特率,您也只能使用原始颜色信息的 25%。
因此您的格式更改如下所示:
您从 1920x1080 红色、绿色和蓝色像素开始。你转换成YUV。现在您拥有 1920x1080 亮度、Cb 和 Cr。其中 Cb 和 Cr 是色差分量。这只是表示颜色的一种不同方式。现在,您 将 Cb 和 Cr 平面 缩放 到其原始大小的 1/4。因此,您生成的 Cb 和 Cr 通道约为 960x540,而您的亮度平面仍为 1920x1080。通过将您的颜色信息从 1920x1080 缩放到 960x540 - 您可以缩小到原始尺寸的 25%。
然后将全尺寸亮度平面和 25% 色差通道传入编码器。这种减少颜色信息的级别称为子采样到 4:2:0。编码器需要二次采样输入,并由媒体框架自动完成。除了选择不同的格式之外,您无能为力。
R = red
G = green
B = blue
Y = luminescence
U = blue difference (Cb)
V = red difference (Cr)
YUV用于分离出可以高分辨率存储或高带宽传输的亮度信号(Y),
和两个色度分量(U 和 V),可以减少带宽,二次采样,
压缩,或以其他方式单独处理以提高系统效率。
(维基百科)
Original format
RGB (4:4:4) 3 bytes per pixel
R R R R R R R R R R R R R R R R
G G G G G G G G G G G G G G G G
B B B B B B B B B B B B B B B B
Encoder input format - before H.264 compression
YUV (4:2:0) 1.5 bytes per pixel (6 bytes per 4 pixel)
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
UV UV UV UV
我正在尝试了解您的问题。
我的程序 ScreenCaptureEncode 使用默认的 Microsoft 编码器设置 :
- 个人资料:基线
- 等级:40
- CODECAPI_AVEncCommonQuality : 70
- 比特率:2000000
根据我的结果,我认为质量是 good/acceptable。
您可以将 profile/level/bitrate 更改为 MF_MT_MPEG2_PROFILE/MF_MT_MPEG2_LEVEL/MF_MT_AVG_BITRATE。
对于 CODECAPI_AVEncCommonQuality,您似乎正在尝试使用本地注册的编码器,
因为您使用的是 Win7,所以我想将该值设置为 100。
但我认为这不会显着改变事情。
所以。
这是 3 个带有键盘打印屏幕的屏幕截图:
- 屏幕
- 编码屏幕,由视频播放器以全屏模式播放
- 编码屏幕,由视频播放器以非全屏模式播放
最后两张图片来自同一个视频编码文件。
视频播放器在不以全屏模式播放时会引入锯齿。
同一个编码的文件全屏播放还不错,对比原屏,
和默认的编码器设置。
你应该试试这个。我认为我们必须更仔细地研究一下。
我认为锯齿是由您的视频播放器造成的,因为不是在全屏模式下播放。
PS : 我用的是MPC-HC视频播放器
PS2:我的程序需要改进:
- (不确定)使用 IDirect3D9Ex 改进缓冲机制。在 Windows7 上,对于渲染,IDirect3D9Ex 更好(无交换缓冲区)。
也许捕获屏幕(待办事项列表)也是如此。
- 我应该使用两个线程,一个用于捕获屏幕,一个用于编码。
编辑
你读过这个吗:
Low-latency mode is useful for real-time communications or live capture, when latency should be minimized. However, low-latency mode might also reduce the decoding or encoding quality.
关于为什么我的程序使用 MFVideoFormat_RGB32 而你的程序使用 MFVideoFormat_YUY2。默认情况下,SinkWriter 启用转换器。 SinkWriter 将 MFVideoFormat_RGB32 转换为兼容的 h264 编码器格式。对于 Microsoft 编码器,请阅读:H.264 Video Encoder
输入格式:
- MFVideoFormat_I420
- MFVideoFormat_IYUV
- MFVideoFormat_NV12
- MFVideoFormat_YUY2
- MFVideoFormat_YV12
所以没有MFVideoFormat_RGB32。我认为 SinkWriter 使用 Color Converter DSP 进行转换。
所以肯定,问题不在于在编码之前将 rgb 转换为 yuv。
PS(最后一个)
就像马库斯舒曼说的那样;
H.264 was more designed for natural content rather than screen capture.
他应该提到这个问题特别与文本捕获有关。
您刚刚发现了编码器的限制。我只是认为没有编码器针对文本编码进行优化,具有可接受的拉伸,就像我提到的视频播放器渲染一样。
您会在最终视频捕获中看到混叠,因为它是电影中的固定信息。全屏播放这部电影(与捕获相同)是可以的。
在Windows上,文本是根据屏幕分辨率计算的。所以显示总是好的。
这是我最后的结论。
经过这么多的研究和努力,问题已经解决了。颜色质量问题是由于基于软件的颜色转换导致混叠(编码器中的 RGB 到 YUV,然后在解码器中返回)。使用 hardware-accelerated color convertor 解决了锯齿和图像质量问题。
将最佳值设置为 CODECAPI_AVEncMPVGOPSize、CODECAPI_AVEncMPVDefaultBPictureCount 和 CODECAPI_AVEncCommonLowLatency 属性解决了缓冲问题。
我正在尝试使用 Windows Media Foundation 将从图像 (RGBA) 源 (Desktop/Camera) 捕获的 RGBA 缓冲区编码为原始 H264,传输它们并解码接收到的原始 H264 帧实时的另一端。我试图达到至少 30 fps。编码器工作得很好,但解码器不行。
我了解 Microsoft WMF MFT 在发出 encoded/decoded 数据之前最多缓冲 30 帧。
The image source would emit frames only when there is a change occurs and not a continuous stream of RGBA buffers, so my intention is to obtain a buffer of encoded/decoded data for each and every input buffer to the respective MFT so that I can stream the data in real time and also render it.
当我使图像源发送连续变化(通过刺激变化)时,编码器和解码器都能够发出至少 10 到 15 fps。编码器能够利用硬件加速支持。我能够在编码器端实现高达 30 fps,而且我还没有使用 DirectX 表面实现硬件辅助解码。这里的问题不是帧速率,而是 MFT 对数据的缓冲。
因此,我尝试通过发送 MFT_MESSAGE_COMMAND_DRAIN 命令来耗尽解码器 MFT,并重复调用 ProcessOutput 直到解码器 returns MF_E_TRANSFORM_NEED_MORE_INPUT。现在发生的是解码器现在每 30 个输入 h264 缓冲区只发出一帧,我什至用连续的数据流测试它,行为是相同的。 看起来解码器丢弃了 GOP 中的所有中间帧。
如果它只缓冲前几帧对我来说没问题,但我的解码器实现仅在缓冲区已满时才输出,即使在 SPS 和 PPS 解析阶段之后也是如此。
我看到了 Google 的 chromium 源代码 (https://github.com/adobe/chromium/blob/master/content/common/gpu/media/dxva_video_decode_accelerator.cc),他们遵循相同的方法。
mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
我的实现是基于 https://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/EncodeTransform.cpp
和
https://github.com/GameTechDev/ChatHeads/blob/master/VideoStreaming/DecodeTransform.cpp
我的问题是,我是否遗漏了什么? Windows Media Foundation 适合实时流媒体吗?。耗尽编码器和解码器是否适用于实时用例?
我只有两个选择,让这个 WMF 工作在实时用例中,或者使用像 Intel 的 QuickSync 这样的东西。我为我的 POC 选择了 WMF,因为 Windows Media Foundation 隐式支持 Hardware/GPU/Software 回退,以防任何 MFT 不可用,并且它在内部选择最佳可用的 MFT,无需太多编码。
尽管比特率 属性 设置为 3Mbps,但我遇到了视频质量问题。但与缓冲问题相比,它的优先级最低。几个星期以来,我一直在键盘上敲打自己的脑袋,这很难解决。任何帮助将不胜感激。
代码:
编码器设置:
IMFAttributes* attributes = 0;
HRESULT hr = MFCreateAttributes(&attributes, 0);
if (attributes)
{
//attributes->SetUINT32(MF_SINK_WRITER_DISABLE_THROTTLING, TRUE);
attributes->SetGUID(MF_TRANSCODE_CONTAINERTYPE, MFTranscodeContainerType_MPEG4);
}//end if (attributes)
hr = MFCreateMediaType(&pMediaTypeOut);
// Set the output media type.
if (SUCCEEDED(hr))
{
hr = MFCreateMediaType(&pMediaTypeOut);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetGUID(MF_MT_SUBTYPE, cVideoEncodingFormat); // MFVideoFormat_H264
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_AVG_BITRATE, VIDEO_BIT_RATE); //18000000
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_FRAME_RATE, VIDEO_FPS, 1); // 30
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeSize(pMediaTypeOut, MF_MT_FRAME_SIZE, mStreamWidth, mStreamHeight);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_INTERLACE_MODE, MFVideoInterlace_Progressive);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_MPEG2_PROFILE, eAVEncH264VProfile_High);
}
if (SUCCEEDED(hr))
{
hr = MFSetAttributeRatio(pMediaTypeOut, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_MAX_KEYFRAME_SPACING, 16);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonRateControlMode, eAVEncCommonRateControlMode_UnconstrainedVBR);//eAVEncCommonRateControlMode_Quality, eAVEncCommonRateControlMode_UnconstrainedCBR);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(CODECAPI_AVEncCommonQuality, 100);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, FALSE);
}
if (SUCCEEDED(hr))
{
BOOL allSamplesIndependent = TRUE;
hr = pMediaTypeOut->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, allSamplesIndependent);
}
if (SUCCEEDED(hr))
{
hr = pMediaTypeOut->SetUINT32(MF_MT_COMPRESSED, TRUE);
}
if (SUCCEEDED(hr))
{
hr = mpEncoder->SetOutputType(0, pMediaTypeOut, 0);
}
// 处理传入的样本。忽略时间戳和持续时间参数,我们只是实时渲染数据。
HRESULT ProcessSample(IMFSample **ppSample, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn)
{
IMFMediaBuffer *buffer = nullptr;
DWORD bufferSize;
HRESULT hr = S_FALSE;
if (ppSample)
{
hr = (*ppSample)->ConvertToContiguousBuffer(&buffer);
if (SUCCEEDED(hr))
{
buffer->GetCurrentLength(&bufferSize);
hr = ProcessInput(ppSample);
if (SUCCEEDED(hr))
{
//hr = mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
//if (SUCCEEDED(hr))
{
while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
hr = ProcessOutput(time, duration, oDtn);
}
}
}
else
{
if (hr == MF_E_NOTACCEPTING)
{
while (hr != MF_E_TRANSFORM_NEED_MORE_INPUT)
{
hr = ProcessOutput(time, duration, oDtn);
}
}
}
}
}
return (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ? (oDtn.numBytes > 0 ? oDtn.returnCode : hr) : hr);
}
// 查找并 returns h264 MFT(在子类型参数中给出)如果可用...否则失败。
HRESULT FindDecoder(const GUID& subtype)
{
HRESULT hr = S_OK;
UINT32 count = 0;
IMFActivate **ppActivate = NULL;
MFT_REGISTER_TYPE_INFO info = { 0 };
UINT32 unFlags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_ASYNCMFT;
info.guidMajorType = MFMediaType_Video;
info.guidSubtype = subtype;
hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
unFlags,
&info,
NULL,
&ppActivate,
&count
);
if (SUCCEEDED(hr) && count == 0)
{
hr = MF_E_TOPO_CODEC_NOT_FOUND;
}
if (SUCCEEDED(hr))
{
hr = ppActivate[0]->ActivateObject(IID_PPV_ARGS(&mpDecoder));
}
CoTaskMemFree(ppActivate);
return hr;
}
// 从编码数据重建样本
HRESULT ProcessData(char *ph264Buffer, DWORD bufferLength, LONGLONG& time, LONGLONG& duration, TransformOutput &dtn)
{
dtn.numBytes = 0;
dtn.pData = NULL;
dtn.returnCode = S_FALSE;
IMFSample *pSample = NULL;
IMFMediaBuffer *pMBuffer = NULL;
// Create a new memory buffer.
HRESULT hr = MFCreateMemoryBuffer(bufferLength, &pMBuffer);
// Lock the buffer and copy the video frame to the buffer.
BYTE *pData = NULL;
if (SUCCEEDED(hr))
hr = pMBuffer->Lock(&pData, NULL, NULL);
if (SUCCEEDED(hr))
memcpy(pData, ph264Buffer, bufferLength);
pMBuffer->SetCurrentLength(bufferLength);
pMBuffer->Unlock();
// Create a media sample and add the buffer to the sample.
if (SUCCEEDED(hr))
hr = MFCreateSample(&pSample);
if (SUCCEEDED(hr))
hr = pSample->AddBuffer(pMBuffer);
LONGLONG sampleTime = time - mStartTime;
// Set the time stamp and the duration.
if (SUCCEEDED(hr))
hr = pSample->SetSampleTime(sampleTime);
if (SUCCEEDED(hr))
hr = pSample->SetSampleDuration(duration);
hr = ProcessSample(&pSample, sampleTime, duration, dtn);
::Release(&pSample);
::Release(&pMBuffer);
return hr;
}
// 处理解码器的输出样本
HRESULT ProcessOutput(LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
IMFMediaBuffer *pBuffer = NULL;
DWORD mftOutFlags;
MFT_OUTPUT_DATA_BUFFER outputDataBuffer;
IMFSample *pMftOutSample = NULL;
MFT_OUTPUT_STREAM_INFO streamInfo;
memset(&outputDataBuffer, 0, sizeof outputDataBuffer);
HRESULT hr = mpDecoder->GetOutputStatus(&mftOutFlags);
if (SUCCEEDED(hr))
{
hr = mpDecoder->GetOutputStreamInfo(0, &streamInfo);
}
if (SUCCEEDED(hr))
{
hr = MFCreateSample(&pMftOutSample);
}
if (SUCCEEDED(hr))
{
hr = MFCreateMemoryBuffer(streamInfo.cbSize, &pBuffer);
}
if (SUCCEEDED(hr))
{
hr = pMftOutSample->AddBuffer(pBuffer);
}
if (SUCCEEDED(hr))
{
DWORD dwStatus = 0;
outputDataBuffer.dwStreamID = 0;
outputDataBuffer.dwStatus = 0;
outputDataBuffer.pEvents = NULL;
outputDataBuffer.pSample = pMftOutSample;
hr = mpDecoder->ProcessOutput(0, 1, &outputDataBuffer, &dwStatus);
}
if (SUCCEEDED(hr))
{
hr = GetDecodedBuffer(outputDataBuffer.pSample, outputDataBuffer, time, duration, oDtn);
}
if (pBuffer)
{
::Release(&pBuffer);
}
if (pMftOutSample)
{
::Release(&pMftOutSample);
}
return hr;
}
// 将解码后的样本写出来
HRESULT GetDecodedBuffer(IMFSample *pMftOutSample, MFT_OUTPUT_DATA_BUFFER& outputDataBuffer, LONGLONG& time, LONGLONG& duration, TransformOutput& oDtn/*output*/)
{
// ToDo: These two lines are not right. Need to work out where to get timestamp and duration from the H264 decoder MFT.
HRESULT hr = outputDataBuffer.pSample->SetSampleTime(time);
if (SUCCEEDED(hr))
{
hr = outputDataBuffer.pSample->SetSampleDuration(duration);
}
if (SUCCEEDED(hr))
{
hr = pMftOutSample->ConvertToContiguousBuffer(&pDecodedBuffer);
}
if (SUCCEEDED(hr))
{
DWORD bufLength;
hr = pDecodedBuffer->GetCurrentLength(&bufLength);
}
if (SUCCEEDED(hr))
{
byte *pEncodedYUVBuffer;
DWORD buffCurrLen = 0;
DWORD buffMaxLen = 0;
pDecodedBuffer->GetCurrentLength(&buffCurrLen);
pDecodedBuffer->Lock(&pEncodedYUVBuffer, &buffMaxLen, &buffCurrLen);
ColorConversion::YUY2toRGBBuffer(pEncodedYUVBuffer,
buffCurrLen,
mpRGBABuffer,
mStreamWidth,
mStreamHeight,
mbEncodeBackgroundPixels,
mChannelThreshold);
pDecodedBuffer->Unlock();
::Release(&pDecodedBuffer);
oDtn.pData = mpRGBABuffer;
oDtn.numBytes = mStreamWidth * mStreamHeight * 4;
oDtn.returnCode = hr; // will be S_OK..
}
return hr;
}
更新: 在启用 CODECAPI_AVLowLatency 模式后,解码器的输出现在令人满意,但是与发送方相比,流中有 2 秒的延迟,我能够达到 15 到 20fps,这比以前好很多。当从源推送到编码器的更改数量更多时,质量会下降。我还没有实现硬件加速解码。
更新2: 我发现如果设置不当,时间戳和持续时间设置会影响视频质量。问题是,我的图像源不以恒定速率发射帧,但看起来编码器和解码器期望恒定帧速率。当我将持续时间设置为常数并以恒定的步长增加采样时间时,视频质量似乎更好但不是最好的。我不认为我正在做的是正确的方法。有没有办法指定编码器和解码器关于可变帧率?
更新3: 在设置 CODECAPI_AVEncMPVDefaultBPictureCount (0) 和 CODECAPI_AVEncCommonLowLatency 属性后,我能够从编码器和解码器获得可接受的性能。尚未探索硬件加速解码。我希望如果实现硬件解码,我能达到最好的性能。
视频质量仍然很差,边缘和曲线不清晰。文字看起来很模糊,这是不可接受的。视频和图像的质量还可以,但文本和形状的质量不行。
更新4
似乎在 YUV 子采样阶段丢失了一些颜色信息。我尝试将 RGBA 缓冲区转换为 YUV2,然后再转换回来,颜色损失是可见的,但还不错。 YUV 转换造成的损失没有 RGBA-> YUV2 -> H264 -> YUV2 -> RGBA 转换后渲染的图像质量差。很明显,不仅 YUV2 转换是质量损失的唯一原因,而且 H264 编码器进一步导致混叠。如果 H264 编码不引入混叠效果,我仍然可以获得更好的视频质量。我将探索 WMV 编解码器。唯一仍然困扰我的是 this,代码运行良好,能够捕获屏幕并将流以 mp4 格式保存在文件中。这里唯一的区别是,与上述代码中使用 MFVideoFormat_RGB32 作为输入类型的接收器编写器方法相比,我使用的是具有 MFVideoFormat_YUY2 输入格式的媒体基础转换。我仍然希望有可能通过 Media Foundation 本身获得更好的质量。如果我分别在 MFT_REGISTER_TYPE_INFO (MFTEnum)/SetInputType 中指定 MFVideoFormat_ARGB32 作为输入格式,那么 MFTEnum/ProcessInput 就会失败。
原文:
解码图像(RGBA -> YUV2 -> H264 -> YUV2 -> RGBA转换后):
Click to open in the new tab to view the full image so that you can see the aliasing effect.
大多数消费者 H.264 编码器将颜色信息子采样到 4:2:0。 (RGB 到 YUV) 这意味着甚至在编码过程开始之前,您的 RGB 位图就会丢失 75% 的颜色信息。 H.264 更适合自然内容而不是屏幕捕获。 但是有些编解码器是专门为实现屏幕内容的良好压缩而设计的。例如:https://docs.microsoft.com/en-us/windows/desktop/medfound/usingthewindowsmediavideo9screencodec 即使您增加 H.264 编码的比特率,您也只能使用原始颜色信息的 25%。
因此您的格式更改如下所示:
您从 1920x1080 红色、绿色和蓝色像素开始。你转换成YUV。现在您拥有 1920x1080 亮度、Cb 和 Cr。其中 Cb 和 Cr 是色差分量。这只是表示颜色的一种不同方式。现在,您 将 Cb 和 Cr 平面 缩放 到其原始大小的 1/4。因此,您生成的 Cb 和 Cr 通道约为 960x540,而您的亮度平面仍为 1920x1080。通过将您的颜色信息从 1920x1080 缩放到 960x540 - 您可以缩小到原始尺寸的 25%。 然后将全尺寸亮度平面和 25% 色差通道传入编码器。这种减少颜色信息的级别称为子采样到 4:2:0。编码器需要二次采样输入,并由媒体框架自动完成。除了选择不同的格式之外,您无能为力。
R = red
G = green
B = blue
Y = luminescence
U = blue difference (Cb)
V = red difference (Cr)
YUV用于分离出可以高分辨率存储或高带宽传输的亮度信号(Y), 和两个色度分量(U 和 V),可以减少带宽,二次采样, 压缩,或以其他方式单独处理以提高系统效率。 (维基百科)
Original format
RGB (4:4:4) 3 bytes per pixel
R R R R R R R R R R R R R R R R
G G G G G G G G G G G G G G G G
B B B B B B B B B B B B B B B B
Encoder input format - before H.264 compression
YUV (4:2:0) 1.5 bytes per pixel (6 bytes per 4 pixel)
Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
UV UV UV UV
我正在尝试了解您的问题。
我的程序 ScreenCaptureEncode 使用默认的 Microsoft 编码器设置 :
- 个人资料:基线
- 等级:40
- CODECAPI_AVEncCommonQuality : 70
- 比特率:2000000
根据我的结果,我认为质量是 good/acceptable。
您可以将 profile/level/bitrate 更改为 MF_MT_MPEG2_PROFILE/MF_MT_MPEG2_LEVEL/MF_MT_AVG_BITRATE。 对于 CODECAPI_AVEncCommonQuality,您似乎正在尝试使用本地注册的编码器, 因为您使用的是 Win7,所以我想将该值设置为 100。
但我认为这不会显着改变事情。
所以。
这是 3 个带有键盘打印屏幕的屏幕截图:
- 屏幕
- 编码屏幕,由视频播放器以全屏模式播放
- 编码屏幕,由视频播放器以非全屏模式播放
最后两张图片来自同一个视频编码文件。 视频播放器在不以全屏模式播放时会引入锯齿。 同一个编码的文件全屏播放还不错,对比原屏, 和默认的编码器设置。 你应该试试这个。我认为我们必须更仔细地研究一下。
我认为锯齿是由您的视频播放器造成的,因为不是在全屏模式下播放。
PS : 我用的是MPC-HC视频播放器
PS2:我的程序需要改进:
- (不确定)使用 IDirect3D9Ex 改进缓冲机制。在 Windows7 上,对于渲染,IDirect3D9Ex 更好(无交换缓冲区)。 也许捕获屏幕(待办事项列表)也是如此。
- 我应该使用两个线程,一个用于捕获屏幕,一个用于编码。
编辑
你读过这个吗:
Low-latency mode is useful for real-time communications or live capture, when latency should be minimized. However, low-latency mode might also reduce the decoding or encoding quality.
关于为什么我的程序使用 MFVideoFormat_RGB32 而你的程序使用 MFVideoFormat_YUY2。默认情况下,SinkWriter 启用转换器。 SinkWriter 将 MFVideoFormat_RGB32 转换为兼容的 h264 编码器格式。对于 Microsoft 编码器,请阅读:H.264 Video Encoder
输入格式:
- MFVideoFormat_I420
- MFVideoFormat_IYUV
- MFVideoFormat_NV12
- MFVideoFormat_YUY2
- MFVideoFormat_YV12
所以没有MFVideoFormat_RGB32。我认为 SinkWriter 使用 Color Converter DSP 进行转换。
所以肯定,问题不在于在编码之前将 rgb 转换为 yuv。
PS(最后一个)
就像马库斯舒曼说的那样;
H.264 was more designed for natural content rather than screen capture.
他应该提到这个问题特别与文本捕获有关。
您刚刚发现了编码器的限制。我只是认为没有编码器针对文本编码进行优化,具有可接受的拉伸,就像我提到的视频播放器渲染一样。
您会在最终视频捕获中看到混叠,因为它是电影中的固定信息。全屏播放这部电影(与捕获相同)是可以的。
在Windows上,文本是根据屏幕分辨率计算的。所以显示总是好的。
这是我最后的结论。
经过这么多的研究和努力,问题已经解决了。颜色质量问题是由于基于软件的颜色转换导致混叠(编码器中的 RGB 到 YUV,然后在解码器中返回)。使用 hardware-accelerated color convertor 解决了锯齿和图像质量问题。
将最佳值设置为 CODECAPI_AVEncMPVGOPSize、CODECAPI_AVEncMPVDefaultBPictureCount 和 CODECAPI_AVEncCommonLowLatency 属性解决了缓冲问题。