英特尔图形硬件 H264 MFT ProcessInput 调用在提供少量输入样本后失败,而使用 Nvidia 硬件 MFT 则正常工作。

10
我正在使用DesktopDuplication API捕获桌面,并在GPU中将RGBA样本转换为NV12,然后将其提供给MediaFoundation硬件H264 MFT。这在Nvidia图形和软件编码器上都很好用,但当只有Intel图形硬件MFT可用时会失败。如果我回退到Software MFT,则相同的Intel图形机器上的代码可以正常工作。我还确保了在Nvidia图形机器上实际进行硬件编码。
在Intel图形上,MFT返回MEError("未指定错误"),这仅在第一个样本被提供后发生,随后调用ProcessInput(当事件生成器触发METransformNeedInput时)返回"The callee is currently not accepting further input"。MFT在返回这些错误之前很少消耗更多的样本。这种行为很令人困惑,因为我仅在事件生成器通过IMFAsyncCallback异步触发METransformNeedInput时才提供样本,并且检查是否在提供样本后立即触发METransformHaveOutput。这真的让我感到困惑,因为相同的异步逻辑在Nvidia硬件MFT和Microsoft软件编码器上都可以正常工作。
在Intel论坛本身也有类似的未解决问题。我的代码与Intel线程中提到的代码类似,除了我还设置d3d设备管理器到编码器中。
而且还有三个其他的堆栈溢出线程报告了类似的问题,但没有给出解决方案(MFTransform编码器->ProcessInput返回E_FAIL如何为Intel MFT编码器从D11纹理创建IMFSample & 异步MFT不发送MFTransformHaveOutput事件(Intel硬件MJPEG解码器MFT))。我已经尝试了所有可能的选项,但没有改进。 颜色转换器代码取自英特尔媒体SDK样本。 我也上传了我的完整代码这里
设置d3d管理器的方法:
void SetD3dManager() {

    HRESULT hr = S_OK;

    if (!deviceManager) {

        // Create device manager
        hr = MFCreateDXGIDeviceManager(&resetToken, &deviceManager);
    }

    if (SUCCEEDED(hr)) 
    {
        if (!pD3dDevice) {

            pD3dDevice = GetDeviceDirect3D(0);
        }
    }

    if (pD3dDevice) {

        // NOTE: Getting ready for multi-threaded operation
        const CComQIPtr<ID3D10Multithread> pMultithread = pD3dDevice;
        pMultithread->SetMultithreadProtected(TRUE);

        hr = deviceManager->ResetDevice(pD3dDevice, resetToken);
        CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast<ULONG_PTR>(deviceManager.p)), "Failed to set device manager.");
    }
    else {
        cout << "Failed to get d3d device";
    }
}

获取D3DDevice:

CComPtr<ID3D11Device> GetDeviceDirect3D(UINT idxVideoAdapter)
{
    // Create DXGI factory:
    CComPtr<IDXGIFactory1> dxgiFactory;
    DXGI_ADAPTER_DESC1 dxgiAdapterDesc;

    // Direct3D feature level codes and names:

    struct KeyValPair { int code; const char* name; };

    const KeyValPair d3dFLevelNames[] =
    {
        KeyValPair{ D3D_FEATURE_LEVEL_9_1, "Direct3D 9.1" },
        KeyValPair{ D3D_FEATURE_LEVEL_9_2, "Direct3D 9.2" },
        KeyValPair{ D3D_FEATURE_LEVEL_9_3, "Direct3D 9.3" },
        KeyValPair{ D3D_FEATURE_LEVEL_10_0, "Direct3D 10.0" },
        KeyValPair{ D3D_FEATURE_LEVEL_10_1, "Direct3D 10.1" },
        KeyValPair{ D3D_FEATURE_LEVEL_11_0, "Direct3D 11.0" },
        KeyValPair{ D3D_FEATURE_LEVEL_11_1, "Direct3D 11.1" },
    };

    // Feature levels for Direct3D support
    const D3D_FEATURE_LEVEL d3dFeatureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };

    constexpr auto nFeatLevels = static_cast<UINT> ((sizeof d3dFeatureLevels) / sizeof(D3D_FEATURE_LEVEL));

    CComPtr<IDXGIAdapter1> dxgiAdapter;
    D3D_FEATURE_LEVEL featLevelCodeSuccess;
    CComPtr<ID3D11Device> d3dDx11Device;

    std::wstring_convert<std::codecvt_utf8<wchar_t>> transcoder;

    HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory));
    CHECK_HR(hr, "Failed to create DXGI factory");

    // Get a video adapter:
    dxgiFactory->EnumAdapters1(idxVideoAdapter, &dxgiAdapter);

    // Get video adapter description:
    dxgiAdapter->GetDesc1(&dxgiAdapterDesc);

    CHECK_HR(hr, "Failed to retrieve DXGI video adapter description");

    std::cout << "Selected DXGI video adapter is \'"
        << transcoder.to_bytes(dxgiAdapterDesc.Description) << '\'' << std::endl;

    // Create Direct3D device:
    hr = D3D11CreateDevice(
        dxgiAdapter,
        D3D_DRIVER_TYPE_UNKNOWN,
        nullptr,
        (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
        d3dFeatureLevels,
        nFeatLevels,
        D3D11_SDK_VERSION,
        &d3dDx11Device,
        &featLevelCodeSuccess,
        nullptr
    );

    // Might have failed for lack of Direct3D 11.1 runtime:
    if (hr == E_INVALIDARG)
    {
        // Try again without Direct3D 11.1:
        hr = D3D11CreateDevice(
            dxgiAdapter,
            D3D_DRIVER_TYPE_UNKNOWN,
            nullptr,
            (0 * D3D11_CREATE_DEVICE_SINGLETHREADED) | D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
            d3dFeatureLevels + 1,
            nFeatLevels - 1,
            D3D11_SDK_VERSION,
            &d3dDx11Device,
            &featLevelCodeSuccess,
            nullptr
        );
    }

    // Get name of Direct3D feature level that succeeded upon device creation:
    std::cout << "Hardware device supports " << std::find_if(
        d3dFLevelNames,
        d3dFLevelNames + nFeatLevels,
        [featLevelCodeSuccess](const KeyValPair& entry)
        {
            return entry.code == featLevelCodeSuccess;
        }
    )->name << std::endl;

done:

    return d3dDx11Device;
}

异步回调实现:

struct EncoderCallbacks : IMFAsyncCallback
{
    EncoderCallbacks(IMFTransform* encoder)
    {
        TickEvent = CreateEvent(0, FALSE, FALSE, 0);
        _pEncoder = encoder;
    }

    ~EncoderCallbacks()
    {
        eventGen = nullptr;
        CloseHandle(TickEvent);
    }

    bool Initialize() {

        _pEncoder->QueryInterface(IID_PPV_ARGS(&eventGen));

        if (eventGen) {

            eventGen->BeginGetEvent(this, 0);
            return true;
        }

        return false;
    }

    // dummy IUnknown impl
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return E_NOTIMPL; }
    virtual ULONG STDMETHODCALLTYPE AddRef(void) override { return 1; }
    virtual ULONG STDMETHODCALLTYPE Release(void) override { return 1; }

    virtual HRESULT STDMETHODCALLTYPE GetParameters(DWORD* pdwFlags, DWORD* pdwQueue) override
    {
        // we return immediately and don't do anything except signaling another thread
        *pdwFlags = MFASYNC_SIGNAL_CALLBACK;
        *pdwQueue = MFASYNC_CALLBACK_QUEUE_IO;
        return S_OK;
    }

    virtual HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult* pAsyncResult) override
    {
        IMFMediaEvent* event = 0;
        eventGen->EndGetEvent(pAsyncResult, &event);
        if (event)
        {
            MediaEventType type;
            event->GetType(&type);
            switch (type)
            {
            case METransformNeedInput: InterlockedIncrement(&NeedsInput); break;
            case METransformHaveOutput: InterlockedIncrement(&HasOutput); break;
            }
            event->Release();
            SetEvent(TickEvent);
        }

        eventGen->BeginGetEvent(this, 0);
        return S_OK;
    }

    CComQIPtr<IMFMediaEventGenerator> eventGen = nullptr;
    HANDLE TickEvent;
    IMFTransform* _pEncoder = nullptr;

    unsigned int NeedsInput = 0;
    unsigned int HasOutput = 0;
};

生成样例方法:

bool GenerateSampleAsync() {

    DWORD processOutputStatus = 0;
    HRESULT mftProcessOutput = S_OK;
    bool frameSent = false;

    // Create sample
    CComPtr<IMFSample> currentVideoSample = nullptr;

    MFT_OUTPUT_STREAM_INFO StreamInfo;

    // wait for any callback to come in
    WaitForSingleObject(_pEventCallback->TickEvent, INFINITE);

    while (_pEventCallback->NeedsInput) {

        if (!currentVideoSample) {

            (pDesktopDuplication)->releaseBuffer();
            (pDesktopDuplication)->cleanUpCurrentFrameObjects();

            bool bTimeout = false;

            if (pDesktopDuplication->GetCurrentFrameAsVideoSample((void**)& currentVideoSample, waitTime, bTimeout, deviceRect, deviceRect.Width(), deviceRect.Height())) {

                prevVideoSample = currentVideoSample;
            }
            // Feed the previous sample to the encoder in case of no update in display
            else {
                currentVideoSample = prevVideoSample;
            }
        }

        if (currentVideoSample)
        {
            InterlockedDecrement(&_pEventCallback->NeedsInput);
            _frameCount++;

            CHECK_HR(currentVideoSample->SetSampleTime(mTimeStamp), "Error setting the video sample time.");
            CHECK_HR(currentVideoSample->SetSampleDuration(VIDEO_FRAME_DURATION), "Error getting video sample duration.");

            CHECK_HR(_pTransform->ProcessInput(inputStreamID, currentVideoSample, 0), "The resampler H264 ProcessInput call failed.");

            mTimeStamp += VIDEO_FRAME_DURATION;
        }
    }

    while (_pEventCallback->HasOutput) {

        CComPtr<IMFSample> mftOutSample = nullptr;
        CComPtr<IMFMediaBuffer> pOutMediaBuffer = nullptr;

        InterlockedDecrement(&_pEventCallback->HasOutput);

        CHECK_HR(_pTransform->GetOutputStreamInfo(outputStreamID, &StreamInfo), "Failed to get output stream info from H264 MFT.");

        CHECK_HR(MFCreateSample(&mftOutSample), "Failed to create MF sample.");
        CHECK_HR(MFCreateMemoryBuffer(StreamInfo.cbSize, &pOutMediaBuffer), "Failed to create memory buffer.");
        CHECK_HR(mftOutSample->AddBuffer(pOutMediaBuffer), "Failed to add sample to buffer.");

        MFT_OUTPUT_DATA_BUFFER _outputDataBuffer;
        memset(&_outputDataBuffer, 0, sizeof _outputDataBuffer);
        _outputDataBuffer.dwStreamID = outputStreamID;
        _outputDataBuffer.dwStatus = 0;
        _outputDataBuffer.pEvents = nullptr;
        _outputDataBuffer.pSample = mftOutSample;

        mftProcessOutput = _pTransform->ProcessOutput(0, 1, &_outputDataBuffer, &processOutputStatus);

        if (mftProcessOutput != MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            if (_outputDataBuffer.pSample) {

                CComPtr<IMFMediaBuffer> buf = NULL;
                DWORD bufLength;
                CHECK_HR(_outputDataBuffer.pSample->ConvertToContiguousBuffer(&buf), "ConvertToContiguousBuffer failed.");

                if (buf) {

                    CHECK_HR(buf->GetCurrentLength(&bufLength), "Get buffer length failed.");
                    BYTE* rawBuffer = NULL;

                    fFrameSize = bufLength;
                    fDurationInMicroseconds = 0;
                    gettimeofday(&fPresentationTime, NULL);

                    buf->Lock(&rawBuffer, NULL, NULL);
                    memmove(fTo, rawBuffer, fFrameSize > fMaxSize ? fMaxSize : fFrameSize);

                    bytesTransfered += bufLength;

                    FramedSource::afterGetting(this);

                    buf->Unlock();

                    frameSent = true;
                }
            }

            if (_outputDataBuffer.pEvents)
                _outputDataBuffer.pEvents->Release();
        }
        else if (MF_E_TRANSFORM_STREAM_CHANGE == mftProcessOutput) {

            // some encoders want to renegotiate the output format. 
            if (_outputDataBuffer.dwStatus & MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE)
            {
                CComPtr<IMFMediaType> pNewOutputMediaType = nullptr;
                HRESULT res = _pTransform->GetOutputAvailableType(outputStreamID, 1, &pNewOutputMediaType);

                res = _pTransform->SetOutputType(0, pNewOutputMediaType, 0);//setting the type again
                CHECK_HR(res, "Failed to set output type during stream change");
            }
        }
        else {
            HandleFailure();
        }
    }

    return frameSent;
}

创建视频示例和颜色转换:

bool GetCurrentFrameAsVideoSample(void **videoSample, int waitTime, bool &isTimeout, CRect &deviceRect, int surfaceWidth, int surfaceHeight)
{

FRAME_DATA currentFrameData;

m_LastErrorCode = m_DuplicationManager.GetFrame(&currentFrameData, waitTime, &isTimeout);

if (!isTimeout && SUCCEEDED(m_LastErrorCode)) {

    m_CurrentFrameTexture = currentFrameData.Frame;

    if (!pDstTexture) {

        D3D11_TEXTURE2D_DESC desc;
        ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));

        desc.Format = DXGI_FORMAT_NV12;
        desc.Width = surfaceWidth;
        desc.Height = surfaceHeight;
        desc.MipLevels = 1;
        desc.ArraySize = 1;
        desc.SampleDesc.Count = 1;
        desc.CPUAccessFlags = 0;
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.BindFlags = D3D11_BIND_RENDER_TARGET;

        m_LastErrorCode = m_Id3d11Device->CreateTexture2D(&desc, NULL, &pDstTexture);
    }

    if (m_CurrentFrameTexture && pDstTexture) {

        // Copy diff area texels to new temp texture
        //m_Id3d11DeviceContext->CopySubresourceRegion(pNewTexture, D3D11CalcSubresource(0, 0, 1), 0, 0, 0, m_CurrentFrameTexture, 0, NULL);

        HRESULT hr = pColorConv->Convert(m_CurrentFrameTexture, pDstTexture);

        if (SUCCEEDED(hr)) { 

            CComPtr<IMFMediaBuffer> pMediaBuffer = nullptr;

            MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), pDstTexture, 0, FALSE, (IMFMediaBuffer**)&pMediaBuffer);

            if (pMediaBuffer) {

                CComPtr<IMF2DBuffer> p2DBuffer = NULL;
                DWORD length = 0;
                (((IMFMediaBuffer*)pMediaBuffer))->QueryInterface(__uuidof(IMF2DBuffer), reinterpret_cast<void**>(&p2DBuffer));
                p2DBuffer->GetContiguousLength(&length);
                (((IMFMediaBuffer*)pMediaBuffer))->SetCurrentLength(length);

                //MFCreateVideoSampleFromSurface(NULL, (IMFSample**)videoSample);
                MFCreateSample((IMFSample * *)videoSample);

                if (videoSample) {

                    (*((IMFSample **)videoSample))->AddBuffer((((IMFMediaBuffer*)pMediaBuffer)));
                }

                return true;
            }
        }
    }
}

return false;
}

机器中的英特尔显卡驱动程序已经是最新的。

enter image description here enter image description here enter image description here

一直只有TransformNeedInput事件被触发,但编码器却抱怨无法接受更多输入。从未触发过TransformHaveOutput事件。

enter image description here

类似问题已在Intel和MSDN论坛上报告: 1) https://software.intel.com/en-us/forums/intel-media-sdk/topic/607189 2) https://social.msdn.microsoft.com/Forums/SECURITY/en-US/fe051dd5-b522-4e4b-9cbb-2c06a5450e40/imfsinkwriter-merit-validation-failed-for-mft-intel-quick-sync-video-h264-encoder-mft?forum=mediafoundationdevelopment 更新: 我尝试模拟仅输入源(通过编程方式创建一个动画矩形NV12样本)而不改变其他任何东西。这次,英特尔编码器没有任何投诉,我甚至得到了输出样本。除了英特尔编码器的输出视频失真外,Nvidia编码器完美运行。
此外,我仍然在使用英特尔编码器处理原始NV12源时遇到ProcessInput错误。我在Nvidia MFT和软件编码器中没有任何问题。

英特尔硬件MFT的输出:(请查看Nvidia编码器的输出) enter image description here

Nvidia硬件MFT的输出: enter image description here

Nvidia图形使用统计数据: enter image description here

英特尔图形使用统计数据(我不明白为什么GPU引擎显示为视频解码): enter image description here


在编程方面,将以下内容从英语翻译成中文。仅返回已翻译的文本:没有显示相关代码。很可能是在接收“需要输入”并使用ProcessInput提供它时出现了问题。 - Roman R.
@RomanR。如果是这样,那么软件和Nvidia硬件MFT也可能失败,不是吗?我没有展示与枚举MFT以及输入和输出配置相关的任何代码,因为这将是冗余的、不必要的,并且对于一个线程来说太长了,正如我所提到的,我已经按照英特尔论坛中给出的完全相同的代码进行了跟踪(https://software.intel.com/en-us/forums/intel-media-sdk/topic/681571)。我将尝试使用必要的代码块更新此线程。 - iamrameshkumar
不是这样的。来自AMD,Intel和NVIDIA的硬件MFT实现方式类似,但同时又略有不同的行为。这三种大多数都作为异步MFT工作,因此您的问题明显表明您在做某些事情时出了问题。没有代码,只能猜测具体是什么问题。据我所知,Microsoft的软件编码器是同步MFT,因此很可能在与异步MFT通信的部分存在问题。 - Roman R.
顺便说一句,那个英特尔论坛链接中的代码对我有效,并生成了视频。 - Roman R.
@RomanR。现在,我尝试通过利用那个Intel线程中的RenderImage图像方法来模拟输入源。现在,编码器不会抱怨任何事情。我甚至在持续获取输出样本。唯一的例外是Intel编码器的输出失真,而Nvidia编码器的输出完全正常。我已经在该线程中更新了截图。 - iamrameshkumar
显示剩余4条评论
2个回答

2

我看了你的代码。

根据你的帖子,我怀疑是Intel视频处理器的问题。

我的操作系统是Win7,所以我决定使用Nvidia卡上的D3D9Device测试视频处理器的行为,然后在Intel HD Graphics 4000上进行测试。

我认为对于D3D9Device和D3D11Device,视频处理器的功能将会表现出相同的方式。当然需要进行检查。

所以我制作了这个程序进行检查:https://github.com/mofo7777/DirectXVideoScreen(请参见D3D9VideoProcessor子项目)。

看起来你没有检查视频处理器功能方面的足够内容。

使用IDXVAHD_Device::GetVideoProcessorDeviceCaps,这是我检查的:

DXVAHD_VPDEVCAPS.MaxInputStreams > 0

DXVAHD_VPDEVCAPS.VideoProcessorCount > 0

DXVAHD_VPDEVCAPS.OutputFormatCount > 0

DXVAHD_VPDEVCAPS.InputFormatCount > 0

DXVAHD_VPDEVCAPS.InputPool == D3DPOOL_DEFAULT

我还使用IDXVAHD_Device::GetVideoProcessorOutputFormats和IDXVAHD_Device::GetVideoProcessorInputFormats检查支持的输入和输出格式。

这就是我在Nvidia GPU和Intel GPU之间发现差异的地方。

NVIDIA:4种输出格式

  • D3DFMT_A8R8G8B8
  • D3DFMT_X8R8G8B8
  • D3DFMT_YUY2
  • D3DFMT_NV12

INTEL:3种输出格式

  • D3DFMT_A8R8G8B8
  • D3DFMT_X8R8G8B8
  • D3DFMT_YUY2

在Intel HD Graphics 4000上,不支持NV12输出格式。

此外,为了使程序正常工作,我需要在使用VideoProcessBltHD之前设置流状态:

  • DXVAHD_STREAM_STATE_D3DFORMAT
  • DXVAHD_STREAM_STATE_FRAME_FORMAT
  • DXVAHD_STREAM_STATE_INPUT_COLOR_SPACE
  • DXVAHD_STREAM_STATE_SOURCE_RECT
  • DXVAHD_STREAM_STATE_DESTINATION_RECT

对于D3D11:

ID3D11VideoProcessorEnumerator::GetVideoProcessorCaps == IDXVAHD_Device::GetVideoProcessorDeviceCaps

(D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT) ID3D11VideoProcessorEnumerator::CheckVideoProcessorFormat == IDXVAHD_Device::GetVideoProcessorOutputFormats

(D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT) ID3D11VideoProcessorEnumerator::CheckVideoProcessorFormat == IDXVAHD_Device::GetVideoProcessorInputFormats

ID3D11VideoContext::(...) == IDXVAHD_VideoProcessor::SetVideoProcessStreamState

首先,您需要验证您的GPU的视频处理器功能。您是否看到与我所看到的相同的差异?这是我们需要了解的第一件事情,从您在GitHub项目上所见,似乎您的程序没有检查此项。


你是正确的。在英特尔图形上调用GetVideoProcessorOutputFormats方法只返回了RGB变体和YUY2。 - iamrameshkumar
我很容易地能够在英特尔处理器上将RGBA纹理转换为YUY2。但问题是,似乎英特尔图形仅支持NV12输入格式。现在颜色转换器和视频编码器不兼容。我仍然想知道英特尔为什么要这样做。是否有其他有效地将RGB转换为NV12的方法?我已经尝试过软件方法,但性能不足。 - iamrameshkumar
你有着色器或计算着色器。 - mofo77
1
我正在研究一种着色器方法。请查看 https://github.com/mofo7777/DirectXVideoScreen 获取更新。 - mofo77
太好了!谢谢分享,这真的很有帮助。 - iamrameshkumar

1
如帖子中所提到的,在英特尔硬件上,Transform的事件生成器在输入第一个样本后立即返回错误MEError(“未指定错误”),而进一步调用只返回“Transform Need more input”,但没有产生输出。然而,同样的代码在Nvidia机器上运行良好。经过大量尝试和研究,我发现我创建了太多的D3d11Device实例,在我的情况下,我为捕获、颜色转换和硬件编码器分别创建了2到3个设备。而我可以简单地重复使用一个单独的D3dDevice实例。在高端机器上创建多个D3d11Device实例可能会起作用,但这并没有记录在任何地方。我无法找到任何关于“MEError”错误原因的线索。这在任何地方都没有被提及。有许多类似于这个问题的StackOverflow线程被保留未回答,甚至Microsoft人员也无法指出问题,给出完整的源代码也无济于事。

重复使用D3D11Device实例解决了这个问题。发布这个解决方案,因为它可能对遇到与我相同问题的人有帮助。


我在你的帖子中没有看到提到E_UNEXPECTED错误的地方... - mofo77
@mofo77,抱歉,正如帖子中所提到的那样,这是我的错误MEError = 1(“未指定的错误”)。我有点迷失了自己的思路。我已经更正了我的答案。感谢您的指出。 - iamrameshkumar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接