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

问题描述 投票:9回答:2

我正在尝试使用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命令来耗尽解码器MFT,并重复调用ProcessOutput直到解码器返回MF_E_TRANSFORM_NEED_MORE_INPUT。现在发生的是解码器现在每30个输入h264缓冲区只发出一帧,我用连续的数据流测试它,行为相同。看起来解码器丢弃了GOP中的所有中间帧。

如果它只缓冲前几帧,那对我来说没问题,但是我的解码器实现只有在它的缓冲区一直满,甚至在SPS和PPS解析阶段之后才会输出。

我遇到了谷歌的铬源代码(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工作的实时用例或者像英特尔的QuickSync一样。我选择WMF作为我的POC,因为Windows Media Foundation隐含地支持硬件/ GPU /软件回退,以防任何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);
}

//查找并返回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,与之前相比要好得多。当从源到编码器的更多数量的更改时,质量会下降。我还没有实现硬件加速解码。

Update2:我发现如果设置不正确,时间戳和持续时间设置会影响视频质量。问题是,我的图像源不会以恒定速率发射帧,但看起来编码器和解码器期望帧速率恒定。当我将持续时间设置为常数并以恒定步长递增采样时间时,视频质量似乎更好但不是最大。我不认为我正在做的是正确的方法。有没有办法指定关于可变帧率的编码器和解码器?

Update3:在设置CODECAPI_AVEncMPVDefaultBPictureCount(0),CODECAPI_AVEncCommonRealTime和CODECAPI_AVEncCommonLowLatency属性后,我能够从编码器和解码器获得可接受的性能。然而,要探索硬件加速解码。我希望如果实现硬件解码,我将能够达到最佳性能。

视频质量仍然很差,边缘和曲线也不清晰。文字看起来很模糊,这是不可接受的。视频和图像的质量是可以的,但文本和形状的质量没有。

UPDATE4

似乎有些颜色信息在YUV子采样阶段丢失了。我尝试将RGBA缓冲区转换为YUV2然后返回,但颜色丢失是可见的但不错。由于YUV转换造成的损失没有RGBA-> YUV2 - > H264 - > YUV2 - > RGBA转换后渲染的图像质量差。很明显,YUV2转换不仅是质量损失的唯一原因,而且H264编码器还会导致混叠。如果H264编码没有引入混叠效果,我仍然可以获得更好的视频质量。我要去探索WMV编解码器。唯一让我烦恼的是this,代码运行良好,能够捕获屏幕并将文件中的流保存为mp4格式。这里唯一的区别是我在MFVideoFormat_YUY2输入格式中使用Media foundation变换,而在上述代码中使用MFVideoFormat_RGB32作为输入类型的sink writer方法。我仍然希望通过媒体基金会本身实现更好的质量。如果我分别在MFT_REGISTER_TYPE_INFO(MFTEnum)/ SetInputType中指定MFVideoFormat_ARGB32作为输入格式,则MFTEnum / ProcessInput失败。

原版的:

enter image description here

解码后的图像(RGBA后 - > YUV2 - > H264 - > YUV2 - > RGBA转换):

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

enter image description here

c++ video-streaming directx h.264 ms-media-foundation
2个回答
4
投票

大多数消费者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

2
投票

我想了解你的问题。

我的程序ScreenCaptureEncode使用默认的Microsoft编码器设置:

  • 简介:基线
  • 等级:40
  • CODECAPI_AVEncCommonQuality:70
  • 比特率:2000000

根据我的结果,我认为质量好/可接受。

您可以使用MF_MT_MPEG2_PROFILE / MF_MT_MPEG2_LEVEL / MF_MT_AVG_BITRATE更改配置文件/级别/比特率。对于CODECAPI_AVEncCommonQuality,似乎你正在尝试使用本地注册的编码器,因为你在Win7上,将该值设置为100,我猜。

但我不认为这会改变事情。

所以。

这是3个带键盘打印屏幕的截图:

  • 屏幕
  • 编码屏幕,由全屏模式的视频播放器播放
  • 编码屏幕,由非全屏模式的视频播放器播放

enter image description here

最后两张图片来自同一视频编码文件。视频播放器在不以全屏模式播放时会引入混叠。与原始屏幕相比,使用默认编码器设置,以全屏模式播放的相同编码文件并不是那么糟糕。你应该试试这个。我认为我们必须更密切地关注这一点。

我认为别名来自您的视频播放器,因为不能以全屏模式播放。

PS:我使用视频播放器MPC-HC。

PS2:我的程序需要改进:

  • (不确定)使用IDirect3D9Ex来改进缓冲机制。在Windows7上,为了渲染,IDirect3D9Ex更好(没有交换缓冲区)。也许它对于捕获屏幕(待办事项列表)来说是一样的。
  • 我应该使用两个线程,一个用于捕获屏幕,一个用于编码。

编辑

你读过这个:

CODECAPI_AVLowLatencyMode

当延迟应该最小化时,低延迟模式对于实时通信或实时捕获非常有用。但是,低延迟模式也可能降低解码或编码质量。

关于为什么我的程序使用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更多是针对自然内容而非屏幕捕获而设计的。

他应该提到问题与文本捕获特别相关。

您刚刚找到了编码器限制。我只是认为没有编码器针对文本编码进行优化,具有可接受的延伸,就像我提到的视频播放器渲染一样。

您会在最终视频捕获中看到别名,因为它是影片中的固定信息。全屏播放此电影(与拍摄相同)即可。

在Windows上,根据屏幕分辨率计算文本。所以显示总是好的。

这是我的最后一个结论。

© www.soinside.com 2019 - 2024. All rights reserved.