Windows Media Foundation MFT缓冲和视频质量问题(颜色丢失,而不是平滑的曲线,尤其是文本)

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转换之后):

单击以在新选项卡中打开以查看完整图像,以便您可以看到锯齿效果。

在此处输入图片说明

Ram*_*san 1

经过大量研究和努力,问题得到解决。颜色质量问题是由于基于软件的颜色转换导致混叠(编码器中的 RGB 到 YUV 并返回到解码器)。使用硬件加速的颜色转换器解决了锯齿和图像质量问题。

为 CODECAPI_AVEncMPVGOPSize、CODECAPI_AVEncMPVDefaultBPictureCount 和 CODECAPI_AVEncCommonLowLatency 属性设置最佳值解决了缓冲问题。