Obtendo a tela verde no ffplay: Streaming da área de trabalho (superfície DirectX) como vídeo H264 sobre fluxo RTP usando o Live555

9

Estou tentando transmitir a área de trabalho (superfície DirectX no formato NV12) como vídeo H264 sobre fluxo RTP usando o codificador de hardware Live555 e Windows Media Foundation no Windows10 e esperando que seja renderizado por ffplay (ffmpeg 4.2). Mas apenas obtendo uma tela verde como abaixo,

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Referi-me mediafoundation amostra MFWebCamToRTP & Encoding DirectX superfície usando MFT hardware para implementar FramedSource das live555 e mudar a fonte de entrada para a superfície DirectX em vez de Webcam.

Aqui está um trecho da minha implementação do retorno de chamada doGetNextFrame do Live555 para alimentar amostras de entrada da superfície directX:

virtual void doGetNextFrame()
{
    if (!_isInitialised)
    {
        if (!initialise()) {
            printf("Video device initialisation failed, stopping.");
            return;
        }
        else {
            _isInitialised = true;
        }
    }

    //if (!isCurrentlyAwaitingData()) return;

    DWORD processOutputStatus = 0;
    HRESULT mftProcessOutput = S_OK;
    MFT_OUTPUT_STREAM_INFO StreamInfo;
    IMFMediaBuffer *pBuffer = NULL;
    IMFSample *mftOutSample = NULL;
    DWORD mftOutFlags;
    bool frameSent = false;
    bool bTimeout = false;

    // Create sample
    CComPtr<IMFSample> videoSample = NULL;

    // Create buffer
    CComPtr<IMFMediaBuffer> inputBuffer;
    // Get next event
    CComPtr<IMFMediaEvent> event;
    HRESULT hr = eventGen->GetEvent(0, &event);
    CHECK_HR(hr, "Failed to get next event");

    MediaEventType eventType;
    hr = event->GetType(&eventType);
    CHECK_HR(hr, "Failed to get event type");


    switch (eventType)
    {
    case METransformNeedInput:
        {
            hr = MFCreateDXGISurfaceBuffer(__uuidof(ID3D11Texture2D), surface, 0, FALSE, &inputBuffer);
            CHECK_HR(hr, "Failed to create IMFMediaBuffer");

            hr = MFCreateSample(&videoSample);
            CHECK_HR(hr, "Failed to create IMFSample");
            hr = videoSample->AddBuffer(inputBuffer);
            CHECK_HR(hr, "Failed to add buffer to IMFSample");

            if (videoSample)
            {
                _frameCount++;

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

                // Pass the video sample to the H.264 transform.

                hr = _pTransform->ProcessInput(inputStreamID, videoSample, 0);
                CHECK_HR(hr, "The resampler H264 ProcessInput call failed.\n");

                mTimeStamp += VIDEO_FRAME_DURATION;
            }
        }

        break;

    case METransformHaveOutput:

        {
            CHECK_HR(_pTransform->GetOutputStatus(&mftOutFlags), "H264 MFT GetOutputStatus failed.\n");

            if (mftOutFlags == MFT_OUTPUT_STATUS_SAMPLE_READY)
            {
                MFT_OUTPUT_DATA_BUFFER _outputDataBuffer;
                memset(&_outputDataBuffer, 0, sizeof _outputDataBuffer);
                _outputDataBuffer.dwStreamID = outputStreamID;
                _outputDataBuffer.dwStatus = 0;
                _outputDataBuffer.pEvents = NULL;
                _outputDataBuffer.pSample = nullptr;

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

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

                        //CHECK_HR(_outputDataBuffer.pSample->SetSampleTime(mTimeStamp), "Error setting MFT sample time.\n");
                        //CHECK_HR(_outputDataBuffer.pSample->SetSampleDuration(VIDEO_FRAME_DURATION), "Error setting MFT sample duration.\n");

                        IMFMediaBuffer *buf = NULL;
                        DWORD bufLength;
                        CHECK_HR(_outputDataBuffer.pSample->ConvertToContiguousBuffer(&buf), "ConvertToContiguousBuffer failed.\n");
                        CHECK_HR(buf->GetCurrentLength(&bufLength), "Get buffer length failed.\n");
                        BYTE * rawBuffer = NULL;

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

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

                        FramedSource::afterGetting(this);

                        buf->Unlock();
                        SafeRelease(&buf);

                        frameSent = true;
                        _lastSendAt = GetTickCount();

                        _outputDataBuffer.pSample->Release();
                    }

                    if (_outputDataBuffer.pEvents)
                        _outputDataBuffer.pEvents->Release();
                }

                //SafeRelease(&pBuffer);
                //SafeRelease(&mftOutSample);

                break;
            }
        }

        break;
    }

    if (!frameSent)
    {
        envir().taskScheduler().triggerEvent(eventTriggerId, this);
    }

    return;

done:

    printf("MediaFoundationH264LiveSource doGetNextFrame failed.\n");
    envir().taskScheduler().triggerEvent(eventTriggerId, this);
}

Inicialize o método:

bool initialise()
{
    HRESULT hr;
    D3D11_TEXTURE2D_DESC desc = { 0 };

    HDESK CurrentDesktop = nullptr;
    CurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
    if (!CurrentDesktop)
    {
        // We do not have access to the desktop so request a retry
        return false;
    }

    // Attach desktop to this thread
    bool DesktopAttached = SetThreadDesktop(CurrentDesktop) != 0;
    CloseDesktop(CurrentDesktop);
    CurrentDesktop = nullptr;
    if (!DesktopAttached)
    {
        printf("SetThreadDesktop failed\n");
    }

    UINT32 activateCount = 0;

    // h264 output
    MFT_REGISTER_TYPE_INFO info = { MFMediaType_Video, MFVideoFormat_H264 };

    UINT32 flags =
        MFT_ENUM_FLAG_HARDWARE |
        MFT_ENUM_FLAG_SORTANDFILTER;

    // ------------------------------------------------------------------------
    // Initialize D3D11
    // ------------------------------------------------------------------------

    // Driver types supported
    D3D_DRIVER_TYPE DriverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT NumDriverTypes = ARRAYSIZE(DriverTypes);

    // Feature levels supported
    D3D_FEATURE_LEVEL FeatureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_1
    };
    UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);

    D3D_FEATURE_LEVEL FeatureLevel;

    // Create device
    for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
    {
        hr = D3D11CreateDevice(nullptr, DriverTypes[DriverTypeIndex], nullptr,
            D3D11_CREATE_DEVICE_VIDEO_SUPPORT,
            FeatureLevels, NumFeatureLevels, D3D11_SDK_VERSION, &device, &FeatureLevel, &context);
        if (SUCCEEDED(hr))
        {
            // Device creation success, no need to loop anymore
            break;
        }
    }

    CHECK_HR(hr, "Failed to create device");

    // Create device manager
    UINT resetToken;
    hr = MFCreateDXGIDeviceManager(&resetToken, &deviceManager);
    CHECK_HR(hr, "Failed to create DXGIDeviceManager");

    hr = deviceManager->ResetDevice(device, resetToken);
    CHECK_HR(hr, "Failed to assign D3D device to device manager");


    // ------------------------------------------------------------------------
    // Create surface
    // ------------------------------------------------------------------------
    desc.Format = DXGI_FORMAT_NV12;
    desc.Width = surfaceWidth;
    desc.Height = surfaceHeight;
    desc.MipLevels = 1;
    desc.ArraySize = 1;
    desc.SampleDesc.Count = 1;

    hr = device->CreateTexture2D(&desc, NULL, &surface);
    CHECK_HR(hr, "Could not create surface");

    hr = MFTEnumEx(
        MFT_CATEGORY_VIDEO_ENCODER,
        flags,
        NULL,
        &info,
        &activateRaw,
        &activateCount
    );
    CHECK_HR(hr, "Failed to enumerate MFTs");

    CHECK(activateCount, "No MFTs found");

    // Choose the first available encoder
    activate = activateRaw[0];

    for (UINT32 i = 0; i < activateCount; i++)
        activateRaw[i]->Release();

    // Activate
    hr = activate->ActivateObject(IID_PPV_ARGS(&_pTransform));
    CHECK_HR(hr, "Failed to activate MFT");

    // Get attributes
    hr = _pTransform->GetAttributes(&attributes);
    CHECK_HR(hr, "Failed to get MFT attributes");

    // Unlock the transform for async use and get event generator
    hr = attributes->SetUINT32(MF_TRANSFORM_ASYNC_UNLOCK, TRUE);
    CHECK_HR(hr, "Failed to unlock MFT");

    eventGen = _pTransform;
    CHECK(eventGen, "Failed to QI for event generator");

    // Get stream IDs (expect 1 input and 1 output stream)
    hr = _pTransform->GetStreamIDs(1, &inputStreamID, 1, &outputStreamID);
    if (hr == E_NOTIMPL)
    {
        inputStreamID = 0;
        outputStreamID = 0;
        hr = S_OK;
    }
    CHECK_HR(hr, "Failed to get stream IDs");

     // ------------------------------------------------------------------------
    // Configure hardware encoder MFT
   // ------------------------------------------------------------------------
    CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, reinterpret_cast<ULONG_PTR>(deviceManager.p)), "Failed to set device manager.\n");

    // Set low latency hint
    hr = attributes->SetUINT32(MF_LOW_LATENCY, TRUE);
    CHECK_HR(hr, "Failed to set MF_LOW_LATENCY");

    hr = MFCreateMediaType(&outputType);
    CHECK_HR(hr, "Failed to create media type");

    hr = outputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    CHECK_HR(hr, "Failed to set MF_MT_MAJOR_TYPE on H264 output media type");

    hr = outputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264);
    CHECK_HR(hr, "Failed to set MF_MT_SUBTYPE on H264 output media type");

    hr = outputType->SetUINT32(MF_MT_AVG_BITRATE, TARGET_AVERAGE_BIT_RATE);
    CHECK_HR(hr, "Failed to set average bit rate on H264 output media type");

    hr = MFSetAttributeSize(outputType, MF_MT_FRAME_SIZE, desc.Width, desc.Height);
    CHECK_HR(hr, "Failed to set frame size on H264 MFT out type");

    hr = MFSetAttributeRatio(outputType, MF_MT_FRAME_RATE, TARGET_FRAME_RATE, 1);
    CHECK_HR(hr, "Failed to set frame rate on H264 MFT out type");

    hr = outputType->SetUINT32(MF_MT_INTERLACE_MODE, 2);
    CHECK_HR(hr, "Failed to set MF_MT_INTERLACE_MODE on H.264 encoder MFT");

    hr = outputType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE);
    CHECK_HR(hr, "Failed to set MF_MT_ALL_SAMPLES_INDEPENDENT on H.264 encoder MFT");

    hr = _pTransform->SetOutputType(outputStreamID, outputType, 0);
    CHECK_HR(hr, "Failed to set output media type on H.264 encoder MFT");

    hr = MFCreateMediaType(&inputType);
    CHECK_HR(hr, "Failed to create media type");

    for (DWORD i = 0;; i++)
    {
        inputType = nullptr;
        hr = _pTransform->GetInputAvailableType(inputStreamID, i, &inputType);
        CHECK_HR(hr, "Failed to get input type");

        hr = inputType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
        CHECK_HR(hr, "Failed to set MF_MT_MAJOR_TYPE on H264 MFT input type");

        hr = inputType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
        CHECK_HR(hr, "Failed to set MF_MT_SUBTYPE on H264 MFT input type");

        hr = MFSetAttributeSize(inputType, MF_MT_FRAME_SIZE, desc.Width, desc.Height);
        CHECK_HR(hr, "Failed to set MF_MT_FRAME_SIZE on H264 MFT input type");

        hr = MFSetAttributeRatio(inputType, MF_MT_FRAME_RATE, TARGET_FRAME_RATE, 1);
        CHECK_HR(hr, "Failed to set MF_MT_FRAME_RATE on H264 MFT input type");

        hr = _pTransform->SetInputType(inputStreamID, inputType, 0);
        CHECK_HR(hr, "Failed to set input type");

        break;
    }

    CheckHardwareSupport();

    CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, NULL), "Failed to process FLUSH command on H.264 MFT.\n");
    CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL), "Failed to process BEGIN_STREAMING command on H.264 MFT.\n");
    CHECK_HR(_pTransform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL), "Failed to process START_OF_STREAM command on H.264 MFT.\n");

    return true;

done:

    printf("MediaFoundationH264LiveSource initialisation failed.\n");
    return false;
}


    HRESULT CheckHardwareSupport()
    {
        IMFAttributes *attributes;
        HRESULT hr = _pTransform->GetAttributes(&attributes);
        UINT32 dxva = 0;

        if (SUCCEEDED(hr))
        {
            hr = attributes->GetUINT32(MF_SA_D3D11_AWARE, &dxva);
        }

        if (SUCCEEDED(hr))
        {
            hr = attributes->SetUINT32(CODECAPI_AVDecVideoAcceleration_H264, TRUE);
        }

#if defined(CODECAPI_AVLowLatencyMode) // Win8 only

        hr = _pTransform->QueryInterface(IID_PPV_ARGS(&mpCodecAPI));

        if (SUCCEEDED(hr))
        {
            VARIANT var = { 0 };

            // FIXME: encoder only
            var.vt = VT_UI4;
            var.ulVal = 0;

            hr = mpCodecAPI->SetValue(&CODECAPI_AVEncMPVDefaultBPictureCount, &var);

            var.vt = VT_BOOL;
            var.boolVal = VARIANT_TRUE;
            hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonLowLatency, &var);
            hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRealTime, &var);

            hr = attributes->SetUINT32(CODECAPI_AVLowLatencyMode, TRUE);

            if (SUCCEEDED(hr))
            {
                var.vt = VT_UI4;
                var.ulVal = eAVEncCommonRateControlMode_Quality;
                hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var);

                // This property controls the quality level when the encoder is not using a constrained bit rate. The AVEncCommonRateControlMode property determines whether the bit rate is constrained.
                VARIANT quality;
                InitVariantFromUInt32(50, &quality);
                hr = mpCodecAPI->SetValue(&CODECAPI_AVEncCommonQuality, &quality);
            }
        }
#endif

        return hr;
    }

Comando ffplay:

ffplay -protocol_whitelist file,udp,rtp -i test.sdp -x 800 -y 600 -profile:v baseline

SDP:

v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
c=IN IP4 127.0.0.1
m=video 1234 RTP/AVP 96
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1

Não sei o que estou perdendo, estou tentando consertar isso há quase uma semana sem nenhum progresso e tentei quase tudo o que pude. Além disso, os recursos online para codificar uma superfície DirectX como vídeo são muito limitados.

Qualquer ajuda seria apreciada.

RAM
fonte
11
Eu acho que você incorretamente espera que o doGetNextFrame seja chamado novamente após METransformNeedInput. Talvez você deva fazer um loop dentro dele até receber uma chamada ProcessOutput válida.
VuVirt 23/10/19
hr = evento-> GetType (& eventType); switch (eventType) {....} if (! frameSent) {envir (). taskScheduler (). triggerEvent (eventTriggerId, this); } Os 2 blocos acima cuidam muito bem de chamar ProcessInput até obtermos uma saída do codificador. Eu verifiquei o mesmo. @VuVirt
Ram
Então, o que acontece quando o frameSent é verdadeiro? Você aciona um novo evento neste caso? Você tem uma declaração de "retorno" depois disso.
VuVirt
@VuVirt É automaticamente chamado pela biblioteca live555 subjacente em um loop. Alternativamente, "ProcessInput" e "ProcessOutput" são chamados com base no evento na instrução switch. Estou recebendo um fluxo contínuo do ProcessOut, mas não apenas posso visualizá-lo. Tenho certeza de que estou definindo corretamente o tempo e a duração da amostra.
Ram
11
Pode ser necessário verificar se você recebe MF_E_TRANSFORM_STREAM_CHANGE do ProcessOutput e manipular as alterações de formato de acordo.
VuVirt 24/10/19

Respostas:

6

É mais difícil do que parece.

Se você deseja usar o codificador como está fazendo, chamando a interface IMFTransform diretamente, é necessário converter os quadros RGB em NV12. Se você quer um bom desempenho, deve fazê-lo na GPU. É possível fazer isso com pixel shaders, renderizar 2 quadros, tamanho grande no DXGI_FORMAT_R8_UNORM renderizar alvo com brilho, meio tamanho no DXGI_FORMAT_R8G8_UNORM com cores e gravar dois pixel shaders para produzir valores NV12. Os dois destinos de renderização podem renderizar em dois planos da mesma textura NV12, mas somente desde o Windows 8.

Outro método é usar o escritor de coletor . Ele pode hospedar várias MFTs ao mesmo tempo, para que você possa fornecer texturas RGB na VRAM, o gravador de coletor as converterá primeiro em NV12 com uma MFT (que provavelmente será o hardware proprietário implementado pelo driver da GPU, assim como o codificador) e, em seguida, passar para o codificador MFT. É relativamente fácil codificar em um arquivo mp4, use a API MFCreateSinkWriterFromURL para criar o gravador. No entanto, é muito mais difícil obter amostras brutas do gravador de coletor, é necessário implementar um coletor de mídia personalizado, coletor de fluxo personalizado para o fluxo de vídeo e chamar MFCreateSinkWriterFromMediaSink para criar o gravador.

Tem mais.

Independentemente dos métodos de codificação, você não pode reutilizar texturas de quadros. Cada quadro obtido do DD, você deve criar uma nova textura e passá-la para o MF.

Os codificadores de vídeo esperam uma taxa de quadros constante. O DD não fornece isso a você, ele fornece um quadro sempre que algo muda na tela. Pode ser 144 FPS se você tiver um monitor de jogos, pode ser 2 FPS se a única alteração for o cursor piscando. Idealmente, você deve enviar quadros à MF a uma taxa de quadros constante, especificada no seu tipo de mídia de vídeo.

Se você deseja transmitir para a rede, na maioria das vezes você também precisa fornecer conjuntos de parâmetros. A menos que você esteja usando o codificador h265 de hardware Intel que não possui comentários da Intel , a MF fornece esses dados no atributo MF_MT_MPEG_SEQUENCE_HEADER do tipo de mídia, chamando SetCurrentMediaType na interface IMFMediaTypeHandler. Você pode implementar essa interface para ser notificado. Você só obterá esses dados depois de começar a codificar. Ou seja, se você usar um gravador de coletor, para o IMFTransformmétodo é mais fácil, você deve obter o MF_E_TRANSFORM_STREAM_CHANGEcódigo do ProcessOutputmétodo e, em seguida, chamar GetOutputAvailableTypepara obter o tipo de mídia atualizado com esse blob mágico.

Soonts
fonte
você quer dizer que o DirectX (duplicação de desktop) não fornece quadros no formato NV12, mesmo quando o dispositivo é inicializado com D3D11_CREATE_DEVICE_VIDEO_SUPPORT e descritor de superfície como DXGI_FORMAT_NV12 e definindo MFT_MESSAGE_SET_D3D_MANAGER na transformação? Eu também pensei que precisamos converter explicitamente o buffer RGB para NV12 ou qualquer formato de entrada suportado (principalmente variantes de YUV) ou usar um SinkWriter. Mas, essa pessoa conseguiu isso de alguma maneira com a minha própria abordagem. stackoverflow.com/questions/43432670/...
Ram
11
A duplicação do @Ram Desktop sempre fornece quadros RGB em DXGI_FORMAT_B8G8R8A8_UNORMformato. Os MFTs de codificador H264 e h265 suportam apenas NV12 e outros dois, igualmente estranhos. Alguém tem que se converter. Você usa duplicação de área de trabalho; você já não pode suportar o Windows 7 com ele. Use um escritor de pia. Tenho certeza de que as MFTs de hardware da nVidia / Intel para converter RGB em NV12 são mais eficientes em termos de energia do que as ALUs de pixel shader, provavelmente implementadas puramente em hardware.
Soonts 02/11/19
Você está certo. A conversão de cores deve ser feita explicitamente. github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/92 . Eu estou seguindo nessa direção.
Ram
11
@ Ram Deve funcionar, eu fiz isso antes. Quando o DD se recusa a fornecer um novo quadro porque não houve atualizações, você pode economizar bastante VRAM enviando a mesma textura ao codificador novamente. Crie apenas novas texturas quando o DD tiver um novo quadro para você. Mas o código para detectar quando você deve enviar quadros e quanto tempo de espera não é trivial. Eu usei o QueryPerformanceCounter para medir o tempo e algum tipo de média móvel nos últimos quadros para descobrir se devo capturar ou devo dormir. BTW, o caminho certo para dormir é o método IDXGIOutput :: WaitForVBlank.
Soonts 5/11/19
1

Como ffplayestá reclamando dos parâmetros do fluxo, eu diria que ele não pode pegar o SPS / PPS. Você não os definiu no seu SDP codificado - consulte o RFC-3984 e procure sprop-parameter-sets. Um exemplo da RFC:

m = video 49170 RTP / AVP 98
a = rtpmap: 98 H264 / 90000
a = fmtp: 98-ID do nível do perfil = 42A01E; conjuntos de parâmetros-sprop = Z0IACpZTBYmI, aMljiA ==

Eu suponho fortemente que ffplayestá esperando isso no SDP. Não me lembro de cor de como obter SPS / PPS do codificador de base de mídia, mas eles estão na carga útil de amostra e você precisa extraí-los consultando as unidades NAL adequadas ou no google como extrair os dados extras do codificador - o primeiro hit que obtive parecia promissor.

Rudolfs Bundulis
fonte
É um ponto válido. Eu também tenho um suspeito no SPS / PPS. Ainda estou para verificar isso. Obrigado por me direcionar para o thread do MSDN, o que me dá alguma esperança.
Ram
@ Ram Há uma boa chance de que o SPS / PPS esteja na carga útil da amostra, então eu verificaria isso primeiro.
Rudolfs Bundulis 31/10/19
Sim, eu entendo isso. Eu tenho algum conhecimento de recuperação e análise de SPS / PPS diretamente de codificadores de fundação de mídia quando tentei gravar amostras em um arquivo através do Mpeg4MediaSink. Vou avançar nessa direção.
Ram
1

Soonts fornece todas as coisas necessárias para resolver seu problema.

A primeira coisa que você precisa fazer é a conversão de formato entre DXGI_FORMAT_B8G8R8A8_UNORM e MFVideoFormat_NV12:

Conversão de formato

informações de conversão de formato

Eu acho que é melhor usar o shader para fazer a conversão de formato, porque todas as texturas permanecerão na GPU (melhor para o desempenho).

É o primeiro passo que você precisa fazer. Você terá outras pessoas para melhorar seu programa.

mofo77
fonte
11
A imagem 2x4 leva 12 bytes no NV12 e não os valores de brilho 24: 8 que você possui, mas a imagem colorida é duas vezes menor, 1x2 pixels, portanto, apenas 4 bytes no total para as informações de cores dessa imagem 2x4, 2 bytes para U e 2 bytes para V.
Soonts 05/11/19
Sim, você está certo, omiti a downsampling para 4.2.0 do formato NV12. Vou tentar fazer um diagrama mais adequado.
mofo77