Ram*_*san 9 c++ directx video-streaming h.264 ms-media-foundation
我正在尝试使用Windows Media Foundation将从图像(RGBA)源(桌面/相机)捕获的RGBA缓冲区编码为原始H264,传输它们并实时解码在另一端接收的原始H264帧。我正在尝试至少达到30 fps。编码器工作得很好,但解码器却不能。
我了解Microsoft WMF MFT在发出编码/解码数据之前最多缓冲30帧。
图像源仅在发生变化时才会发出帧,而不是连续的RGBA缓冲区流,因此,我的目的是为每个MFT的每个输入缓冲区获得一个编码/解码数据的缓冲区,以便我可以流式传输实时数据并进行渲染。
当我使图像源发送连续更改(通过刺激更改)时,编码器和解码器均能够发出至少10到15 fps的帧。编码器能够利用硬件加速支持。我能够在编码器端达到30 fps,并且还没有使用DirectX表面实现硬件辅助解码。这里的问题不是帧速率,而是MFT对数据的缓冲。
因此,我尝试通过发送MFT_MESSAGE_COMMAND_DRAIN命令并反复调用ProcessOutput直到解码器返回MF_E_TRANSFORM_NEED_MORE_INPUT来消耗解码器MFT 。现在发生的事情是,解码器现在每30个输入h264缓冲区仅发出一帧,我甚至用连续的数据流对其进行了测试,其行为是相同的。好像解码器将所有中间帧都丢弃在GOP中。
如果只缓冲前几个帧,这对我来说是可以的,但是即使在SPS和PPS解析阶段之后,我的解码器实现也仅在其缓冲区一直充满时才输出。
我遇到了Google的铬源代码(https://github.com/adobe/chromium/blob/master/content/common/gpu/media/dxva_video_decode_accelerator.cc),它们遵循相同的方法。
mpDecoder->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, NULL);
Run Code Online (Sandbox Code Playgroud)
我的实现基于 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等一起使用。我选择WMF作为我的POC,因为在任何MFT不可用的情况下Windows Media Foundation隐式支持硬件/ GPU /软件后备,并且在内部无需太多编码即可选择最佳的MFT。
我遇到了视频质量问题,尽管bitrate属性设置为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);
}
Run Code Online (Sandbox Code Playgroud)
//处理传入的样本。忽略时间戳和持续时间参数,我们只是实时渲染数据。
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);
}
Run Code Online (Sandbox Code Playgroud)
//查找并返回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;
}
Run Code Online (Sandbox Code Playgroud)
//根据编码数据重建样本
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;
}
Run Code Online (Sandbox Code Playgroud)
//处理解码器的输出样本
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;
}
Run Code Online (Sandbox Code Playgroud)
//写出解码后的样本
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;
}
Run Code Online (Sandbox Code Playgroud)
更新: 启用CODECAPI_AVLowLatency模式后,解码器的输出现在令人满意,但是与发送方相比,流中有2秒的延迟,我能够实现15至20fps,这比以前的要好得多。当有更多的更改从源推送到编码器时,质量降低。我尚未实现硬件加速解码。
Update2: 我发现时间戳和持续时间设置如果设置不当,会影响视频质量。关键是,我的图像源不能以恒定的速率发射帧,但是看起来像编码器和解码器期望恒定的帧速率。当我将持续时间设置为常数并以恒定的步长增加采样时间时,视频质量似乎更好,但不是最好的。我认为我做的不是正确的方法。有什么办法可以指定有关可变帧率的编码器和解码器?
Update3: 设置CODECAPI_AVEncMPVDefaultBPictureCount(0)和CODECAPI_AVEncCommonLowLatency属性后,我可以从编码器和解码器获得可接受的性能。尚未探索硬件加速解码。我希望如果实施硬件解码,我将能够获得最佳性能。
视频质量仍然很差,边缘和曲线不清晰。文字看起来模糊,是不可接受的。视频和图像的质量还可以,但文本和形状则不行。
更新4
似乎某些颜色信息在YUV二次采样阶段丢失了。我尝试将RGBA缓冲区转换为YUV2,然后返回,虽然颜色丢失可见,但还不错。YUV转换造成的损失不如RGBA-> YUV2-> H264-> YUV2-> RGBA转换后呈现的图像质量差。显然,不仅YUV2转换是造成质量损失的唯一原因,而且H264编码器还会造成混叠。如果H264编码不引入锯齿效果,我仍然可以获得更好的视频质量。我将探索WMV编解码器。仍然困扰着我唯一的一点是这个,代码效果很好,并且能够捕获屏幕并将流以mp4格式保存在文件中。唯一的区别是,与上述代码中以MFVideoFormat_RGB32作为输入类型的接收器写入器方法相比,我使用的是MFVideoFormat_YUY2输入格式的Media Foundation转换。我仍然希望通过Media Foundation本身可以获得更好的质量。问题是,如果我分别在MFT_REGISTER_TYPE_INFO(MFTEnum)/ SetInputType中将MFVideoFormat_ARGB32指定为输入格式,则MFTEnum / ProcessInput将失败。
原版的:
解码图像(在RGBA-> YUV2-> H264-> YUV2-> RGBA转换之后):
单击以在新选项卡中打开以查看完整图像,以便您可以看到锯齿效果。
经过大量研究和努力,问题得到解决。颜色质量问题是由于基于软件的颜色转换导致混叠(编码器中的 RGB 到 YUV 并返回到解码器)。使用硬件加速的颜色转换器解决了锯齿和图像质量问题。
为 CODECAPI_AVEncMPVGOPSize、CODECAPI_AVEncMPVDefaultBPictureCount 和 CODECAPI_AVEncCommonLowLatency 属性设置最佳值解决了缓冲问题。
归档时间: |
|
查看次数: |
949 次 |
最近记录: |