Получение зеленого экрана в ffplay: потоковый рабочий стол (поверхность DirectX) в виде видео H264 через RTP-поток с использованием Live555


9

Я пытаюсь транслировать рабочий стол (поверхность DirectX в формате NV12) в виде видео H264 по потоку RTP, используя аппаратный кодировщик Live555 и Windows Media Foundation на Windows10, и ожидаю, что он будет отображаться с помощью ffplay (ffmpeg 4.2). Но только получить зеленый экран, как показано ниже,

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

Я упомянул MFWebCamToRTP mediafoundation-sample & Encoding поверхность DirectX с использованием аппаратного MFT для реализации FramedSource в live555 и изменения источника ввода на поверхность DirectX вместо webCam.

Вот отрывок из моей реализации обратного вызова doGetNextFrame в Live555 для подачи входных выборок с поверхности 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);
}

Метод инициализации:

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;
    }

Команда 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

Я не знаю, что мне не хватает, я пытался исправить это почти неделю без какого-либо прогресса и перепробовал почти все, что мог. Кроме того, онлайн-ресурсы для кодирования поверхности DirectX в виде видео очень ограничены.

Любая помощь будет оценена.


1
Я думаю, что вы неправильно ожидаете, что doGetNextFrame будет вызываться снова после METransformNeedInput. Возможно, вам следует зацикливаться внутри него, пока не получите действительный вызов ProcessOutput.
VuVirt

hr = event-> GetType (& eventType); switch (eventType) {....} if (! frameSent) {envir (). taskScheduler (). triggerEvent (eventTriggerId, this); } Вышеупомянутые 2 блока прекрасно заботятся о вызове ProcessInput, пока мы не получим вывод из кодировщика. Я подтвердил то же самое. @VuVirt
Ram

Так что же происходит, когда frameSent имеет значение true? Вы запускаете новое событие в этом случае? После этого у вас есть «возврат».
VuVirt

@VuVirt Он автоматически вызывается базовой библиотекой live555 в цикле. «ProcessInput» и «ProcessOutput» альтернативно вызываются на основе события в операторе switch. Я получаю непрерывный поток от ProcessOut, но не просто могу просматривать его. Я уверен, что правильно устанавливаю время и продолжительность семпла.
Рам

1
Вам может потребоваться проверить, получаете ли вы MF_E_TRANSFORM_STREAM_CHANGE от ProcessOutput и соответственно обрабатываете изменение формата.
VuVirt

Ответы:


6

Это сложнее, чем кажется.

Если вы хотите использовать кодировщик так, как вы это делаете, напрямую вызывая интерфейс IMFTransform , вы должны преобразовать кадры RGB в NV12. Если вы хотите хорошую производительность, вы должны сделать это на GPU. Возможно сделать с пиксельными шейдерами, визуализировать 2 кадра, один полный размер в DXGI_FORMAT_R8_UNORM, визуализировать цель с яркостью, наполовину увеличить в цель DXGI_FORMAT_R8G8_UNORM с цветом, и написать два пиксельных шейдера для получения значений NV12. Обе цели рендеринга могут рендериться в 2 плоскости с одинаковой текстурой NV12, но только начиная с Windows 8.

Другой метод - использование приемника записи . Он может одновременно содержать несколько MFT, так что вы можете предоставлять RGB-текстуры в VRAM, модуль записи приемника сначала конвертирует их в NV12 с одним MFT (это, вероятно, будет проприетарное аппаратное обеспечение, реализованное драйвером графического процессора, как кодировщик), затем перейти на кодировщик MFT. Кодировать в файл mp4 относительно легко, используйте API MFCreateSinkWriterFromURL для создания модуля записи . Однако гораздо труднее получить необработанные образцы из модуля записи приемника, необходимо создать собственный приемник мультимедиа, специальный приемник потока для его видеопотока и вызвать MFCreateSinkWriterFromMediaSink для создания модуля записи .

Есть больше.

Независимо от методов кодирования, вы не можете повторно использовать текстуры кадра. Каждый кадр, который вы получаете от DD, вы должны создать новую текстуру и передать ее в MF.

Видеокодеры ожидают постоянной частоты кадров. DD не дает вам этого, он дает вам кадр каждый раз, когда что-то меняется на экране. Может быть 144 FPS, если у вас игровой монитор, может быть 2 FPS, если единственным изменением является мигающий курсор. В идеале вы должны отправлять кадры в MF с постоянной частотой кадров, указанной в вашем типе видео.

Если вы хотите выполнять потоковую передачу в сеть, чаще всего вам также необходимо указывать наборы параметров. Если вы не используете аппаратный кодер Intel h265, который не работает без комментариев от Intel , MF предоставляет эти данные в атрибуте MF_MT_MPEG_SEQUENCE_HEADER типа носителя, вызывая SetCurrentMediaType в интерфейсе IMFMediaTypeHandler. Вы можете реализовать этот интерфейс, чтобы получать уведомления. Вы получите эти данные только после того, как начнете кодировать. Вот если вы используете модуль записи приемника, для IMFTransformметода это проще, вы должны получить MF_E_TRANSFORM_STREAM_CHANGEкод из ProcessOutputметода, а затем вызвать, GetOutputAvailableTypeчтобы получить обновленный тип носителя с этим волшебным BLOB-объектом.


Вы имеете в виду, что DirectX (дублирование рабочего стола) не доставляет кадры в формате NV12, даже если устройство инициализировано с D3D11_CREATE_DEVICE_VIDEO_SUPPORT и дескриптором поверхности для DXGI_FORMAT_NV12 и установкой MFT_MESSAGE_SET_D3D_MANAGER в преобразовании? Я также подумал, что мы должны явно преобразовать буфер RGB в NV12 или любой другой поддерживаемый формат ввода (в основном варианты YUV) или использовать SinkWriter. Но этот человек смог как-то добиться этого с помощью моего подхода. stackoverflow.com/questions/43432670/…
Ram


1
Дублирование @Ram Desktop всегда обеспечивает получение кадров RGB в DXGI_FORMAT_B8G8R8A8_UNORMформате. МФУ с кодировщиками H264 и h265 поддерживают только NV12 и пару других, одинаково странных. Кто-то должен конвертировать. Вы используете дублирование рабочего стола; вы уже не можете поддерживать Windows 7 с ним. Используйте писателя раковины. Я почти уверен, что эти аппаратные MFT от nVidia / Intel для преобразования RGB в NV12 более энергоэффективны, чем ALU пиксельных шейдеров, они, вероятно, реализованы исключительно аппаратно.
Скоро

Вы правы. Преобразование цветов должно быть сделано явно. github.com/GPUOpen-LibrariesAndSDKs/AMF/issues/92 . Я иду в этом направлении.
Рам

1
@ Ram Это должно работать, я делал это раньше. Когда DD отказывается выдавать вам новый кадр из-за отсутствия обновлений, вы можете сохранить большое количество VRAM, повторно отправив ту же текстуру кодеру. Создавайте новые текстуры только тогда, когда у DD есть новый фрейм для вас. Но код для определения того, когда нужно отправлять кадры и сколько времени ждать, не является тривиальным. Я использовал QueryPerformanceCounter для измерения времени и какое-то скользящее среднее за последние несколько кадров, чтобы выяснить, должен ли я снимать или спать. Кстати, правильный путь для сна - это метод IDXGIOutput :: WaitForVBlank.
Скоро

1

Так ffplayкак жалуется на параметры потока, я бы предположил, что он не может получить SPS / PPS. Вы не установили их в своем жестком коде SDP - посмотрите RFC-3984 и поищите sprop-parameter-sets. Пример из RFC:

m = видео 49170 RTP / AVP 98
a = rtpmap: 98 H264 / 90000
a = fmtp: 98 идентификатор уровня профиля = 42A01E; наборы параметров sprop = Z0IACpZTBYmI, aMljiA ==

Я настоятельно предполагаю, ffplayчто ожидает их в ДПС. Я не помню наизусть, как получить SPS / PPS из кодировщика мультимедийных данных, но либо они находятся в образце полезной нагрузки, и вам нужно извлечь их, просмотрев правильные блоки NAL, или Google, как извлечь дополнительные данные из кодер - первый хит, который я получил, выглядел многообещающе.


Это верный момент. У меня тоже есть подозреваемый на SPS / PPS. Я все еще должен проверить это все же. Спасибо за то, что направили меня в ветку MSDN, которая дает мне надежду.
Рам

@ Рэм, есть хороший шанс, что SPS / PPS входят в пример полезной нагрузки, поэтому я проверю это в первую очередь.
Рудольф Бундулис

Да, я понимаю это. Я получил некоторые знания по извлечению и синтаксическому анализу SPS / PPS непосредственно из кодировщиков Media Foundation, когда пытался записать сэмплы в файл через Mpeg4MediaSink. Я буду двигаться вперед в этом направлении.
Рам

1

Скоро вам предоставят все необходимое для решения вашей проблемы.

Первое, что вам нужно сделать, это преобразование формата между DXGI_FORMAT_B8G8R8A8_UNORM и MFVideoFormat_NV12:

Преобразование формата

информация о преобразовании формата

Я думаю, что лучше использовать шейдер для преобразования формата, потому что все текстуры останутся в GPU (лучше для производительности).

Это первый шаг, который вам нужно сделать. У вас будут другие, чтобы улучшить вашу программу.


1
Изображение 2x4 занимает в NV12 12 байтов, а не значения яркости 24: 8, которые у вас есть, но цветное изображение в два раза меньше, 1x2 пикселя, поэтому всего 4 байта для информации о цвете этого изображения 2x4, 2 байта для U и 2 байта для V.
Скоро

Да, вы правы, я опустил понижающую дискретизацию до 4.2.0 формата NV12. Я постараюсь составить более подходящую схему.
mofo77
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.