Come convertire YUV in RGB con Media Foundation

· Aggiornato il: · · Media Foundation, C++, Sviluppo Windows, Elaborazione video, YUV

Vuoi estrarre un fotogramma da un video e salvarlo come PNG, passarlo a WIC o GDI o visualizzarlo nell’UI. In queste situazioni, il lato dell’applicazione richiede dati pixel RGB.

Tuttavia, i frame che escono da un decoder Media Foundation sono, abbastanza comunemente, formati della famiglia YUV come NV12 o YUY2. Se tratti il ​​flusso di byte grezzi come un’immagine così com’è, ottieni un’immagine piuttosto triste: colori interrotti, bande o una tinta stranamente verdastra.

In un post precedente, Cos’è Media Foundation - Perché inizi a vedere il volto di COM e i Windows Media API, abbiamo trattato il quadro generale e in Come estrarre un’immagine fissa da un MP4 in un momento specifico con Media Foundation: una versione a file singolo che puoi incollare direttamente in un file .cpp abbiamo trattato l’estrazione di immagini fisse. Questa volta affrontiamo il passaggio intermedio: la conversione YUV -> RGB stessa.

In questo articolo separiamo e organizziamo i due modelli seguenti.

  • Modello A: lascia che IMFSourceReader porti automaticamente i fotogrammi fino a RGB32
  • Modello B: ricevi NV12 / YUY2 e converti tu stesso in RGB

L’obiettivo non è memorizzare i nomi API. È poter immaginare, nella tua testa, dove nel Media Foundation appare il YUV e dove si trasforma in RGB.

Il codice visualizzato in questo articolo è pubblicato su GitHub come set di campioni completo (codice C++ per Pattern A / Pattern B, configurazione CMake e test per la conversione dei pixel).

modelli-di-conversione-yuv-to-rgb-media-foundation - esempi-blog-komurasoft (GitHub)

Prerequisiti per eseguire i campioni

Se vuoi portare il codice di questo articolo nel tuo progetto, ti servono solo queste cose.

Elemento Prerequisito
OS Windows 10 o successivo
Compilatore MSVC di Visual Studio 2019 / 2022 (C++17)
SDK Windows SDK (header e librerie di importazione di Media Foundation). Incluso nel workload “Sviluppo desktop con C++” di Visual Studio
Build I campioni usano CMake 3.20 o successivo. Puoi anche creare un progetto Visual Studio a mano

Le librerie da collegare sono 4. Nel codice dell’articolo le scriviamo con #pragma comment(lib, ...), ma puoi specificarle anche nelle impostazioni del progetto.

  • mfplat.lib
  • mfreadwrite.lib
  • mfuuid.lib
  • ole32.lib

Inoltre, il codice dell’articolo presuppone che CoInitializeEx e MFStartup siano già stati eseguiti. Solo la formula di conversione di 1 pixel (5.6) è indipendente dal sistema operativo, quindi nel campione GitHub è stata spostata in un header separato e può essere testata anche con g++ su Linux.

1. Prima la conclusione

Riassumendo le conclusioni in anticipo:

  • Per estrarre alcune immagini fisse o generare miniature, il percorso più semplice è abilitare MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING e richiedere MFVideoFormat_RGB32
  • Tuttavia, questa conversione automatica è un’elaborazione software e non è ottimizzata per la riproduzione in tempo reale
  • Se hai intenzione di scrivere la tua conversione, il percorso più breve è comprendere correttamente NV12 e YUY2 prima
  • YUV -> RGB non è solo “moltiplica per tre coefficienti e il gioco è fatto” — in pratica, sottocampionamento, intervallo, matrice e passo entrano tutti in gioco
  • La documentazione Media Foundation utilizza ampiamente il termine YUV, ma per i video digitali è più facile da leggere se si presuppone che effettivamente significhi Y’CbCr
  • Le cose che più spesso rompono i colori nella pratica sono non guardare MF_MT_YUV_MATRIX e MF_MT_VIDEO_NOMINAL_RANGE, e supporre che il passo sia width * bytesPerPixel

In breve: se vuoi il percorso facile, prendi l’uscita Source Reader RGB32. Se hai bisogno di un’elaborazione di grandi volumi o di un controllo sul colore, ricevi i fotogrammi come YUV e convertili tu stesso. Queste sono le due scelte.

2. Inizia con un’immagine

È più veloce osservare prima un diagramma di ciò che accade all’interno di Media Foundation.

Pattern APattern BMP4 / H.264 / HEVCdecodificatoreYUV frame come NV12 / YUY2 / YV12Source Reader elaborazione videoRGB32Il tuo codice di conversioneBGRA / RGB

Se il contenuto del file video è in un formato compresso come H.264 o HEVC, il decoder lo trasforma prima in fotogrammi non compressi. Questi frame non compressi non sono necessariamente RGB. Infatti, nel mondo dei video Windows, i formati della famiglia YUV sono la norma.

Quindi, quando l’applicazione richiede RGB, scegli una delle seguenti.

  1. Chiedi al Media Foundation di portare i fotogrammi fino al RGB32
  2. Ricevi YUV e trasformalo in RGB con il tuo codice

Questo articolo riguarda esattamente quel bivio.

3. Risolvere prima la relazione tra YUV e RGB

3.1. Dice YUV, ma in realtà si tratta di Y’CbCr

I nomi e la documentazione di Windows API utilizzano ampiamente il termine YUV. Nel contesto del video digitale, tuttavia, puoi leggere U come Cb e V come Cr praticamente senza problemi.

In parole povere:

  • Y è il componente orientato alla luminosità
  • U / V sono i componenti della differenza di colore
  • RGB ogni pixel trasporta direttamente Rosso / Verde / Blu

Questa è la relazione.

L’occhio umano è più sensibile ai dettagli più fini nella luminosità che nel colore. Quindi, per i video, un design che mantiene Y al massimo dettaglio e U / V un po’ più grossolano ripaga. Questo è il motivo per cui i formati della famiglia YUV sono così ampiamente utilizzati.

3.2. 4:4:4 / 4:2:2 / 4:2:0 è “Quanto è diluito il colore”

Questa è la chiave di lettura di YUV.

Notazione Significato Esempi tipici
4:4:4 Ogni pixel ha il proprio Y / U / V AYUV, I444
4:2:2 2 pixel condividono orizzontalmente una coppia U / V YUY2, UYVY, I422
4:2:0 Un blocco di 2x2 pixel condivide una coppia U / V NV12, YV12, I420

È di grande aiuto osservare innanzitutto la forma dei due formati che incontri maggiormente nella pratica.

NV12 (4:2:0, planar)

Y plane
Y Y Y Y
Y Y Y Y
Y Y Y Y
Y Y Y Y

UV plane
U V U V
U V U V

In NV12, i 4 pixel di un blocco 2x2 condividono una singola coppia U / V. Y esiste per ogni singolo pixel.

YUY2 (4:2:2, packed)

bytes:
Y0 U0 Y1 V0   Y2 U2 Y3 V2   ...

In YUY2, 2 pixel orizzontali condividono una coppia U / V. Y0 e Y1 sono separati, ma U0 e V0 sono condivisi.

A questo punto puoi già vedere che YUV -> RGB non è una semplice sostituzione di un pixel con un pixel. Per prima cosa devi pensare a come assegnare l’U / V condiviso a quali pixel.

3.3. YUV -> RGB è “Conversione spazio colore + conversione campionamento”

Se guardi Informazioni estese sul colore di Media Foundation, la conversione del colore rigorosamente corretta prevede diverse fasi: quantizzazione inversa, sovracampionamento della crominanza, YUV -> RGB, la funzione di trasferimento, conversione delle primarie e infine quantizzazione.

Detto questo, come codice pratico per SDR a 8 bit, è più facile da capire se lo dividi nei tre livelli seguenti.

  1. Annulla il sottocampionamento Espandi 4:2:0 o 4:2:2 U / V in modo che ogni pixel possa fare riferimento a un valore
  2. Annulla l’intervallo Il video Y normalmente utilizza 16..235 e U / V utilizza 16..240, quindi annulla il ridimensionamento
  3. Applica la matrice Converti in RGB utilizzando coefficienti come BT.601 o BT.709

In altre parole, in termini pratici, la conversione YUV -> RGB è il processo di decisione:

  • quale U / V è il colore per quel pixel
  • quali coefficienti utilizzare per riconvertire quel Y / U / V in RGB

3.4. Trattare BT.601 e BT.709 con noncuranza e i colori si disperderanno leggermente

La documentazione di Media Foundation descrive la relazione come BT.601 preferito per SDTV e precedenti e BT.709 per i video oltre SD.

Tuttavia, indovinare silenziosamente “la risoluzione è grande, quindi deve essere 709” non è una grande idea. La deriva del colore non si blocca, quindi entra facilmente in produzione senza essere notato.

Media Foundation può contenere informazioni sullo spazio colore come attributi media type. Come minimo, guarda questi due:

  • MF_MT_YUV_MATRIX
  • MF_MT_VIDEO_NOMINAL_RANGE

Guardando questi due e accettando esplicitamente solo le combinazioni supportate dal tuo codice rende molto meno probabili gli incidenti silenziosi in seguito.

3.5. La prima formula da imparare è la versione a raggio limitato BT.601

La formula canonica BT.601 a 8 bit si presenta così.

C = Y - 16
D = U - 128
E = V - 128

R = clip(1.164383 * C + 1.596027 * E)
G = clip(1.164383 * C - 0.391762 * D - 0.812968 * E)
B = clip(1.164383 * C + 2.017232 * D)

Per BT.709 i coefficienti cambiano. Lo mostreremo nel codice più tardi.

Ciò che conta qui non è memorizzare i coefficienti ma la struttura: sottrarre il livello del nero 16 da Y e visualizzare U / V centrato su 128.

4. Modello A: lascia che Media Foundation converta automaticamente

4.1. Quando questa è una buona soluzione

Questo approccio è adatto a situazioni come le seguenti.

  • Vuoi estrarre una singola immagine fissa da un MP4
  • Vuoi creare alcune miniature
  • Vuoi un’immagine RGB da consegnare a WIC
  • L’uso del batch o degli strumenti va bene; questa non è la riproduzione in tempo reale

Source Reader ha una funzione che esegue elaborazione video limitata di YUV -> RGB32 quando si utilizza MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING.

Tuttavia, come nota anche Microsoft Learn, si tratta di elaborazione software e non ottimizzata per la riproduzione. Se vuoi elaborare centinaia di fotogrammi al secondo, affidarti a questo non è proprio lo strumento giusto.

4.2. Cosa hai impostato per ottenere RGB32

Il flusso è abbastanza semplice.

  1. Negli attributi passati a MFCreateSourceReaderFromURL, impostare MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING = TRUE
  2. Selezionare il flusso video
  3. Richiesta MFMediaType_Video / MFVideoFormat_RGB32 tramite SetCurrentMediaType
  4. Leggi i campioni con ReadSample

Solo questo fa sì che l’elaborazione video limitata inserita dietro il decoder faccia il YUV -> RGB32 per te.

4.3. Codice

Il codice seguente presuppone che CoInitializeEx e MFStartup siano già stati eseguiti. Una versione minimale assomiglia più o meno a questa.

#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mferror.h>
#include <wrl/client.h>

#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "ole32.lib")

using Microsoft::WRL::ComPtr;

HRESULT CreateSourceReaderWithAutoRgb(
    const wchar_t* path,
    IMFSourceReader** ppReader)
{
    if (!path || !ppReader) return E_POINTER;
    *ppReader = nullptr;

    ComPtr<IMFAttributes> attrs;
    HRESULT hr = MFCreateAttributes(&attrs, 2);
    if (FAILED(hr)) return hr;

    hr = attrs->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE);
    if (FAILED(hr)) return hr;

    hr = MFCreateSourceReaderFromURL(path, attrs.Get(), ppReader);
    if (FAILED(hr)) return hr;

    hr = (*ppReader)->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, FALSE);
    if (FAILED(hr)) return hr;

    hr = (*ppReader)->SetStreamSelection(MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE);
    if (FAILED(hr)) return hr;

    ComPtr<IMFMediaType> outType;
    hr = MFCreateMediaType(&outType);
    if (FAILED(hr)) return hr;

    hr = outType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    if (FAILED(hr)) return hr;

    hr = outType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
    if (FAILED(hr)) return hr;

    hr = (*ppReader)->SetCurrentMediaType(
        MF_SOURCE_READER_FIRST_VIDEO_STREAM,
        nullptr,
        outType.Get());
    if (FAILED(hr)) return hr;

    return S_OK;
}

HRESULT ReadOneRgb32Sample(
    IMFSourceReader* reader,
    IMFSample** ppSample,
    LONGLONG* pTimestamp100ns)
{
    if (!reader || !ppSample) return E_POINTER;
    *ppSample = nullptr;
    if (pTimestamp100ns) *pTimestamp100ns = 0;

    DWORD streamIndex = 0;
    DWORD flags = 0;
    LONGLONG timestamp = 0;

    HRESULT hr = reader->ReadSample(
        MF_SOURCE_READER_FIRST_VIDEO_STREAM,
        0,
        &streamIndex,
        &flags,
        &timestamp,
        ppSample);

    if (FAILED(hr)) return hr;
    if (flags & MF_SOURCE_READERF_ENDOFSTREAM) return MF_E_END_OF_STREAM;
    if (*ppSample == nullptr) return MF_E_INVALID_STREAM_DATA;

    if (pTimestamp100ns) *pTimestamp100ns = timestamp;
    return S_OK;
}

Successivamente, chiamando GetCurrentMediaType è possibile verificare la dimensione e il passo effettivi dell’output.

4.4. Punti di forza di questo approccio

L’aspetto positivo di questo approccio è che ti porta rapidamente a un’immagine corretta.

  • Non è necessario scrivere tu stesso l’espansione 4:2:0 / 4:2:2
  • Nasconde gran parte del fastidio della gestione / deinterlacciamento della matrice
  • L’uscita è facile da consegnare a WIC o GDI
  • Per la lavorazione di una manciata di fotogrammi, è perfettamente pratico

Per gli strumenti di estrazione di immagini fisse, iniziare da qui è del tutto naturale.

4.5. Ma ci sono anche delle insidie

Questa conversione automatica ha le seguenti caratteristiche.

Articolo Dettagli
Obiettivo di conversione Fondamentalmente RGB32
Attuazione Elaborazione software
Adatto per Piccoli numeri di fotogrammi, miniature, elaborazione offline
Non adatto per Rendering in tempo reale basato su D3D, elaborazione di frame ad alto volume
Attributi incompatibili MF_SOURCE_READER_D3D_MANAGER, MF_READWRITE_DISABLE_CONVERTERS

E un’altra cosa importante: la gestione del 4° byte in RGB32. In memoria, Windows RGB32 è strutturato come Blu / Verde / Rosso / Alfa o Non importa. Non è ARGB32. Se lo passi a WIC come 32bppBGRA, è più sicuro riempire il quarto byte con 0xFF per renderlo opaco.

Abbiamo accennato a questo come a una cosa facile in cui inciampare anche nel precedente articolo sull’estrazione di immagini fisse.

5. Modello B: scrivi tu stesso la conversione

5.1. Quando questa è una buona soluzione

Effettuare la conversione da soli è una buona soluzione in casi come questi.

  • Elabori un gran numero di frame e desideri ottimizzare tu stesso la conversione
  • Vuoi alimentare NV12 direttamente alla GPU o al codice SIMD
  • Vuoi gestire esplicitamente BT.601 / BT.709 / range
  • Vuoi produrre formati di output diversi da RGB32
  • La conversione automatica limitata del Source Reader non è sufficiente

Potresti chiamarlo il modello in cui ti assumi la responsabilità della produttività e del colore in cambio della libertà.

5.2. Flusso complessivo della conversione manuale

I passaggi sono i seguenti.

  1. Impostare l’uscita Source Reader su NV12 o YUY2
  2. Ottieni il sottotipo e gli attributi effettivi tramite GetCurrentMediaType
  3. Controllare MF_MT_FRAME_SIZE, MF_MT_DEFAULT_STRIDE, MF_MT_YUV_MATRIX e MF_MT_VIDEO_NOMINAL_RANGE
  4. Estrarre il tampone dal campione e bloccarlo
  5. Determinare a quale Y / U / V fa riferimento ciascun pixel
  6. Applica la matrice e scrivi BGRA

Il codice in questo articolo è limitato a 8 bit SDR / progressivo / NV12 o YUY2 / intervallo limitato. Restringere le ipotesi qui non è pigrizia: in realtà è importante. Se scrivi una conversione YUV che “accetta tutto per ora”, tende a rompere silenziosamente i colori.

5.3. Innanzitutto, specificare esplicitamente l’output Media Type

Per prima cosa, dì a Source Reader “per favore, emetti il YUV così com’è.” Ancora una volta, ciò presuppone che CoInitializeEx / MFStartup siano stati completati.

#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mferror.h>
#include <wrl/client.h>

using Microsoft::WRL::ComPtr;

HRESULT ConfigureSourceReaderForSubtype(
    IMFSourceReader* reader,
    REFGUID subtype)
{
    if (!reader) return E_POINTER;

    HRESULT hr = reader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, FALSE);
    if (FAILED(hr)) return hr;

    hr = reader->SetStreamSelection(MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE);
    if (FAILED(hr)) return hr;

    ComPtr<IMFMediaType> outType;
    hr = MFCreateMediaType(&outType);
    if (FAILED(hr)) return hr;

    hr = outType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    if (FAILED(hr)) return hr;

    hr = outType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr)) return hr;

    hr = reader->SetCurrentMediaType(
        MF_SOURCE_READER_FIRST_VIDEO_STREAM,
        nullptr,
        outType.Get());
    if (FAILED(hr)) return hr;

    return S_OK;
}

Qui passi MFVideoFormat_NV12 o MFVideoFormat_YUY2 come subtype.

Tieni presente che non è garantito che il sottotipo richiesto venga accettato così com’è. Controlla cosa esce effettivamente con GetCurrentMediaType.

5.4. Prima della conversione, accetta solo le informazioni sul colore supportate

Per una conversione manuale, estrarre prima le informazioni minime dal tipo di supporto. L’esempio in questo articolo accetta solo NV12 / YUY2 e lascia passare solo BT.601 o BT.709 per la matrice e solo MFNominalRange_16_235 per l’intervallo.

#include <vector>

struct DecodedFrameInfo
{
    GUID subtype = GUID_NULL;
    UINT32 width = 0;
    UINT32 height = 0;
    LONG defaultStride = 0;
    MFVideoTransferMatrix matrix = MFVideoTransferMatrix_Unknown;
    MFNominalRange nominalRange = MFNominalRange_Unknown;
};

HRESULT GetDefaultStride(
    IMFMediaType* pType,
    LONG* plStride)
{
    if (!pType || !plStride) return E_POINTER;

    LONG stride = 0;
    HRESULT hr = pType->GetUINT32(
        MF_MT_DEFAULT_STRIDE,
        reinterpret_cast<UINT32*>(&stride));

    if (FAILED(hr))
    {
        GUID subtype = GUID_NULL;
        UINT32 width = 0;
        UINT32 height = 0;

        hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
        if (FAILED(hr)) return hr;

        hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
        if (FAILED(hr)) return hr;

        hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &stride);
        if (FAILED(hr)) return hr;

        hr = pType->SetUINT32(MF_MT_DEFAULT_STRIDE, static_cast<UINT32>(stride));
        if (FAILED(hr)) return hr;
    }

    *plStride = stride;
    return S_OK;
}

HRESULT GetStrictDecodedFrameInfo(
    IMFMediaType* pType,
    DecodedFrameInfo* pInfo)
{
    if (!pType || !pInfo) return E_POINTER;

    HRESULT hr = pType->GetGUID(MF_MT_SUBTYPE, &pInfo->subtype);
    if (FAILED(hr)) return hr;

    if (pInfo->subtype != MFVideoFormat_NV12 &&
        pInfo->subtype != MFVideoFormat_YUY2)
    {
        return MF_E_INVALIDMEDIATYPE;
    }

    hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &pInfo->width, &pInfo->height);
    if (FAILED(hr)) return hr;

    hr = GetDefaultStride(pType, &pInfo->defaultStride);
    if (FAILED(hr)) return hr;

    UINT32 value = 0;

    hr = pType->GetUINT32(MF_MT_YUV_MATRIX, &value);
    if (FAILED(hr)) return hr;

    pInfo->matrix = static_cast<MFVideoTransferMatrix>(value);
    if (pInfo->matrix != MFVideoTransferMatrix_BT601 &&
        pInfo->matrix != MFVideoTransferMatrix_BT709)
    {
        return MF_E_INVALIDMEDIATYPE;
    }

    hr = pType->GetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, &value);
    if (FAILED(hr)) return hr;

    pInfo->nominalRange = static_cast<MFNominalRange>(value);
    if (pInfo->nominalRange != MFNominalRange_16_235)
    {
        return MF_E_INVALIDMEDIATYPE;
    }

    return S_OK;
}

Qui siamo deliberatamente severi. La documentazione dell’enumerazione Media Foundation dice cose come “tratta Unknown come BT.709”, ma in pratica, arrotondarlo silenziosamente rende più difficile notare la deriva del colore. Almeno in una prima implementazione, è più sicuro restituire un errore per combinazioni non supportate.

Con le fotocamere e le sorgenti derivate da JPEG, potresti voler gestire separatamente i percorsi a gamma completa. Qui deliberatamente non li raggruppiamo silenziosamente insieme: la politica è di restringere esplicitamente le ipotesi accettate da questo codice.

5.5. Leggi il Buffer Fiducia nel passo

Anche questa parte è abbastanza importante.

  • MF_MT_DEFAULT_STRIDE è la falcata minima
  • Il buffer del campione effettivo potrebbe avere un passo effettivo che include il riempimento
  • Se IMF2DBuffer::Lock2D è disponibile, preferiscilo

Prendere il pattern helper da Uncompressed Video Buffers di Microsoft Learn e renderlo direttamente utilizzabile ci dà questo.

class BufferLock
{
public:
    explicit BufferLock(IMFMediaBuffer* buffer)
        : m_buffer(buffer),
          m_2dBuffer(nullptr),
          m_locked(false)
    {
        if (m_buffer)
        {
            m_buffer->AddRef();
            m_buffer->QueryInterface(IID_PPV_ARGS(&m_2dBuffer));
        }
    }

    ~BufferLock()
    {
        Unlock();

        if (m_2dBuffer)
        {
            m_2dBuffer->Release();
            m_2dBuffer = nullptr;
        }

        if (m_buffer)
        {
            m_buffer->Release();
            m_buffer = nullptr;
        }
    }

    HRESULT Lock(
        LONG defaultStride,
        DWORD heightInPixels,
        BYTE** ppScanline0,
        LONG* pActualStride)
    {
        if (!m_buffer || !ppScanline0 || !pActualStride) return E_POINTER;
        if (m_locked) return MF_E_INVALIDREQUEST;

        if (m_2dBuffer)
        {
            HRESULT hr = m_2dBuffer->Lock2D(ppScanline0, pActualStride);
            if (FAILED(hr)) return hr;

            m_locked = true;
            return S_OK;
        }

        BYTE* pData = nullptr;
        HRESULT hr = m_buffer->Lock(&pData, nullptr, nullptr);
        if (FAILED(hr)) return hr;

        *pActualStride = defaultStride;
        if (defaultStride < 0)
        {
            *ppScanline0 =
                pData + static_cast<size_t>(-defaultStride) * (heightInPixels - 1);
        }
        else
        {
            *ppScanline0 = pData;
        }

        m_locked = true;
        return S_OK;
    }

    void Unlock()
    {
        if (!m_locked) return;

        if (m_2dBuffer)
        {
            m_2dBuffer->Unlock2D();
        }
        else
        {
            m_buffer->Unlock();
        }

        m_locked = false;
    }

private:
    IMFMediaBuffer* m_buffer;
    IMF2DBuffer* m_2dBuffer;
    bool m_locked;
};

Le definizioni di superficie YUV consigliate sono in alto a sinistra / falcata positiva, ma per l’accesso effettivo al buffer è più sicuro utilizzare il passo restituito dall’API, così com’è. Se codifichi qualcosa in base a width qui, le cose si interrompono silenziosamente più tardi.

5.6. Trasformare la formula di conversione per pixel in codice

Qui trattiamo solo le versioni a raggio limitato di BT.601 e BT.709. L’output è BGRA32, che è facile da trasferire a WIC o GDI.

inline BYTE ClampToByte(double value)
{
    if (value <= 0.0) return 0;
    if (value >= 255.0) return 255;
    return static_cast<BYTE>(value + 0.5);
}

HRESULT ConvertLimitedYuvPixelToBgra(
    BYTE y,
    BYTE u,
    BYTE v,
    MFVideoTransferMatrix matrix,
    BYTE* dstPixel)
{
    if (!dstPixel) return E_POINTER;

    const double c = static_cast<double>(y) - 16.0;
    const double d = static_cast<double>(u) - 128.0;
    const double e = static_cast<double>(v) - 128.0;

    double r = 0.0;
    double g = 0.0;
    double b = 0.0;

    switch (matrix)
    {
    case MFVideoTransferMatrix_BT601:
        r = 1.164383 * c + 1.596027 * e;
        g = 1.164383 * c - 0.391762 * d - 0.812968 * e;
        b = 1.164383 * c + 2.017232 * d;
        break;

    case MFVideoTransferMatrix_BT709:
        r = 1.164383 * c + 1.792741 * e;
        g = 1.164383 * c - 0.213249 * d - 0.532909 * e;
        b = 1.164383 * c + 2.112402 * d;
        break;

    default:
        return MF_E_INVALIDMEDIATYPE;
    }

    dstPixel[0] = ClampToByte(b);
    dstPixel[1] = ClampToByte(g);
    dstPixel[2] = ClampToByte(r);
    dstPixel[3] = 255;

    return S_OK;
}

Ciò che sta accadendo qui è semplice.

  • Sottrai 16 da Y
  • Sottrai 128 da U / V
  • Moltiplicare per i coefficienti della matrice data
  • Ritaglia il risultato a 0..255
  • Imposta il 4° byte BGRA su 255

5.7. Conversione di NV12 in BGRA32

NV12 è 4:2:0, quindi i 4 pixel di un blocco 2x2 condividono lo stesso U / V. Come implementazione minima, l’approccio più comprensibile è utilizzare la crominanza condivisa direttamente per tutti e 4 i pixel.

HRESULT ConvertNv12ToBgra32(
    IMFMediaBuffer* buffer,
    const DecodedFrameInfo& info,
    std::vector<BYTE>& dstBgra)
{
    if (!buffer) return E_POINTER;
    if (info.subtype != MFVideoFormat_NV12) return MF_E_INVALIDMEDIATYPE;
    if ((info.width & 1u) != 0 || (info.height & 1u) != 0)
    {
        return MF_E_INVALIDMEDIATYPE;
    }

    dstBgra.resize(static_cast<size_t>(info.width) * info.height * 4);

    BufferLock lock(buffer);

    BYTE* scanline0 = nullptr;
    LONG actualStride = 0;
    HRESULT hr = lock.Lock(
        info.defaultStride,
        info.height,
        &scanline0,
        &actualStride);
    if (FAILED(hr)) return hr;

    if (actualStride <= 0)
    {
        lock.Unlock();
        return MF_E_INVALIDMEDIATYPE;
    }

    const BYTE* yPlane = scanline0;
    const BYTE* uvPlane =
        scanline0 + static_cast<size_t>(actualStride) * info.height;

    for (UINT32 y = 0; y < info.height; ++y)
    {
        const BYTE* yRow = yPlane + static_cast<size_t>(actualStride) * y;
        const BYTE* uvRow = uvPlane + static_cast<size_t>(actualStride) * (y / 2);
        BYTE* dstRow =
            dstBgra.data() + static_cast<size_t>(info.width) * 4 * y;

        for (UINT32 x = 0; x < info.width; ++x)
        {
            const BYTE Y = yRow[x];
            const BYTE U = uvRow[(x / 2) * 2 + 0];
            const BYTE V = uvRow[(x / 2) * 2 + 1];

            hr = ConvertLimitedYuvPixelToBgra(
                Y,
                U,
                V,
                info.matrix,
                dstRow + static_cast<size_t>(x) * 4);
            if (FAILED(hr))
            {
                lock.Unlock();
                return hr;
            }
        }
    }

    lock.Unlock();
    return S_OK;
}

Questo codice interpreta il sovracampionamento della crominanza in un modo del vicino più vicino. Visivamente è spesso perfettamente funzionante, ma se miri alla massima qualità, un design che esegue prima la conversione 4:2:0 -> 4:2:2 -> 4:4:4, come descritto nell’articolo Microsoft Learn YUV, è teoricamente più pulito.

5.8. Conversione di YUY2 in BGRA32

YUY2 è compresso 4:2:2. Due pixel condividono semplicemente una coppia U / V, quindi è un po’ più facile da leggere rispetto a NV12.

#include <cstddef>

HRESULT ConvertYuy2ToBgra32(
    IMFMediaBuffer* buffer,
    const DecodedFrameInfo& info,
    std::vector<BYTE>& dstBgra)
{
    if (!buffer) return E_POINTER;
    if (info.subtype != MFVideoFormat_YUY2) return MF_E_INVALIDMEDIATYPE;
    if ((info.width & 1u) != 0) return MF_E_INVALIDMEDIATYPE;

    dstBgra.resize(static_cast<size_t>(info.width) * info.height * 4);

    BufferLock lock(buffer);

    BYTE* scanline0 = nullptr;
    LONG actualStride = 0;
    HRESULT hr = lock.Lock(
        info.defaultStride,
        info.height,
        &scanline0,
        &actualStride);
    if (FAILED(hr)) return hr;

    for (UINT32 y = 0; y < info.height; ++y)
    {
        const BYTE* src =
            scanline0 +
            static_cast<ptrdiff_t>(actualStride) * static_cast<ptrdiff_t>(y);

        BYTE* dstRow =
            dstBgra.data() + static_cast<size_t>(info.width) * 4 * y;

        for (UINT32 x = 0; x < info.width; x += 2)
        {
            const BYTE Y0 = src[0];
            const BYTE U  = src[1];
            const BYTE Y1 = src[2];
            const BYTE V  = src[3];

            hr = ConvertLimitedYuvPixelToBgra(
                Y0,
                U,
                V,
                info.matrix,
                dstRow + static_cast<size_t>(x) * 4);
            if (FAILED(hr))
            {
                lock.Unlock();
                return hr;
            }

            hr = ConvertLimitedYuvPixelToBgra(
                Y1,
                U,
                V,
                info.matrix,
                dstRow + static_cast<size_t>(x + 1) * 4);
            if (FAILED(hr))
            {
                lock.Unlock();
                return hr;
            }

            src += 4;
        }
    }

    lock.Unlock();
    return S_OK;
}

In YUY2, i byte sono disposti come Y0 U Y1 V, quindi la struttura di “riutilizzare l’U / V per ogni 2 pixel” è direttamente visibile. Ciò rende il modello mentale più facile da costruire rispetto a NV12.

5.9. Il punto di ingresso quando si chiama da un campione

Infine, se si estrae un buffer contiguo da IMFSample e lo si ramifica per sottotipo, diventa facile da usare.

HRESULT ConvertSampleToBgra32(
    IMFSample* sample,
    const DecodedFrameInfo& info,
    std::vector<BYTE>& dstBgra)
{
    if (!sample) return E_POINTER;

    ComPtr<IMFMediaBuffer> buffer;
    HRESULT hr = sample->ConvertToContiguousBuffer(&buffer);
    if (FAILED(hr)) return hr;

    if (info.subtype == MFVideoFormat_NV12)
    {
        return ConvertNv12ToBgra32(buffer.Get(), info, dstBgra);
    }

    if (info.subtype == MFVideoFormat_YUY2)
    {
        return ConvertYuy2ToBgra32(buffer.Get(), info, dstBgra);
    }

    return MF_E_INVALIDMEDIATYPE;
}

Con questo, i passaggi precedenti diventano:

  • creare il lettore
  • richiesta NV12 o YUY2
  • costruisci un DecodedFrameInfo da GetCurrentMediaType
  • ReadSample
  • ConvertSampleToBgra32

Il codice di chiamata effettivo è simile a questo.

ComPtr<IMFMediaType> currentType;
HRESULT hr = reader->GetCurrentMediaType(
    MF_SOURCE_READER_FIRST_VIDEO_STREAM,
    &currentType);
if (FAILED(hr)) return hr;

DecodedFrameInfo info;
hr = GetStrictDecodedFrameInfo(currentType.Get(), &info);
if (FAILED(hr)) return hr;

DWORD flags = 0;
LONGLONG timestamp = 0;
ComPtr<IMFSample> sample;

hr = reader->ReadSample(
    MF_SOURCE_READER_FIRST_VIDEO_STREAM,
    0,
    nullptr,
    &flags,
    &timestamp,
    &sample);
if (FAILED(hr)) return hr;
if (flags & MF_SOURCE_READERF_ENDOFSTREAM) return MF_E_END_OF_STREAM;
if (!sample) return MF_E_INVALID_STREAM_DATA;

std::vector<BYTE> bgra;
hr = ConvertSampleToBgra32(sample.Get(), info, bgra);
if (FAILED(hr)) return hr;

// bgra can be treated as top-down / 32bpp BGRA

5.10. Dove inserire la “Conversione manuale”

Il codice finora assume la forma di l’applicazione che si converte dopo Source Reader. Questa è la cosa più facile da capire.

Tuttavia, se desideri inserire la conversione all’interno della pipeline Media Foundation, sono disponibili altri progetti.

  • Scrivi il tuo MFT
  • Utilizza Video Processor MFT / XVP
  • Scrivi uno shader NV12 -> RGB sul lato GPU

Andare così lontano cambia in qualche modo l’argomento, quindi questo articolo si concentra sul codice lato applicazione. Tuttavia, è utile sapere che tra “lascia che se ne occupi Media Foundation” e “fai tutto nell’app” c’è una via di mezzo: il Video Processor MFT.

5.11. Verificare che la conversione sia corretta

Gli errori di colore sono difficili da notare ad occhio, quindi separiamo “funziona” da “è corretto”. La verifica procede in due fasi.

Fase 1: confrontare con il calcolo manuale usando valori noti

Prima di far scorrere un video, passare a ConvertLimitedYuvPixelToBgra valori Y/U/V noti. Non servono né file video né Media Foundation.

Per il limited range BT.601, i valori Y/U/V rappresentativi e i valori RGB attesi secondo le formule di 5.6 sono i seguenti.

Colore Y U V R atteso G atteso B atteso
Nero 16 128 128 0 0 0
Bianco 235 128 128 255 255 255
Rosso 81 90 240 254 0 0
Blu 41 240 110 0 0 255

Per esempio, per il rosso: C = 81 - 16 = 65, D = 90 - 128 = -38, E = 240 - 128 = 112. Sostituendo:

R = 1.164383 * 65 + 1.596027 * 112       = 254.44  -> 254
G = 1.164383 * 65 - 0.391762 * (-38)
                  - 0.812968 * 112       =  -0.48  ->   0
B = 1.164383 * 65 + 2.017232 * (-38)     =  -0.97  ->   0

L’output è in ordine BGRA, quindi in byte 00 00 FE FF.

Il punto importante è perché il rosso non torna 255 ma 254: non è un problema di precisione dei coefficienti, ma del fatto che gli input Y/U/V sono già interi arrotondati.

Se si calcola il rosso (255, 0, 0) in limited range BT.601, si ottiene Y = 16 + 219 × 0.299 = 81.481, U = 90.203, V = 240. Quando si salvano come 8 bit, la parte decimale 0.481 viene persa e Y diventa 81. Quella 0.481 persa, quando si torna indietro, diventa 0.481 × 1.164383 ≈ 0.56 di perdita. 255 − 0.56 = 254.44 — da qui proviene il 254.44 sopra. Anche usando coefficienti a precisione infinita si otterrebbe 254.44; l’arrotondamento a 6 cifre decimali agisce oltre la quarta cifra decimale e non influenza l’output a 8 bit.

Anche come si converte infine in intero cambia il risultato. La ClampToByte di 5.6. limita a [0, 255] e tronca value + 0.5, ovvero arrotonda per eccesso. Con un semplice troncamento (static_cast<BYTE>(value)) il rosso rimane 254, ma valori al bordo come B = 255.04 o R = 0.38 variano di 1. Prima di confrontare due implementazioni, accertati di quale regola usa l’altra parte.

Quindi, i motivi per cui bisogna tollerare ±1–2 non sono “i coefficienti hanno precisioni diverse”, ma “la quantizzazione perde la parte decimale” e “la regola di arrotondamento/intero varia tra le implementazioni”. Al contrario, una differenza che non si spiega con questi due motivi è un vero bug. Se il rosso diventa 250, rosso e blu si scambiano, o le ombre scure si alzano, non è la precisione dei coefficienti: sospetta le premesse della conversione (BT.601 vs BT.709, limited vs full range, scambio U/V, stride letto male). Scartare questi casi come “problema di precisione” fa perdere bug aggiustabili.

Se vuoi scrivere un test, questo modello è sufficiente.

#include <cstdlib>  // std::abs

// Verifica che la differenza rispetto al valore atteso rientri nella tolleranza.
// Il valore atteso è il "colore teorico" (rosso -> 255, 0, 0). La tolleranza
// assorbe le perdite per quantizzazione e le diverse regole di arrotondamento;
// la precisione dei coefficienti non è una scusante.
static bool CheckPixel(
    BYTE y, BYTE u, BYTE v,
    MFVideoTransferMatrix matrix,
    int expectedR, int expectedG, int expectedB,
    int tolerance = 2)
{
    BYTE bgra[4] = {};
    if (FAILED(ConvertLimitedYuvPixelToBgra(y, u, v, matrix, bgra)))
    {
        return false;
    }

    return std::abs(static_cast<int>(bgra[2]) - expectedR) <= tolerance
        && std::abs(static_cast<int>(bgra[1]) - expectedG) <= tolerance
        && std::abs(static_cast<int>(bgra[0]) - expectedB) <= tolerance
        && bgra[3] == 255;  // alpha deve essere opaco
}

// Uso (BT.601 limited range)
// CheckPixel(16, 128, 128, MFVideoTransferMatrix_BT601, 0, 0, 0);      // nero
// CheckPixel(235, 128, 128, MFVideoTransferMatrix_BT601, 255, 255, 255); // bianco
// CheckPixel(81, 90, 240, MFVideoTransferMatrix_BT601, 255, 0, 0);     // rosso (la formula dà R=254)
// CheckPixel(41, 240, 110, MFVideoTransferMatrix_BT601, 0, 0, 255);    // blu

Lo stesso vale per BT.709, ma i coefficienti cambiano e quindi cambiano i valori Y/U/V. Per BT.709 il rosso è Y=63, U=102, V=240. Non usare i valori del 601 nel ramo 709, o i colori si sposteranno. Separare le righe di test per ogni matrix permette di catturare subito uno scambio di matrix.

Il campione su GitHub isola questa conversione di un singolo pixel in un header indipendente dal sistema operativo, quindi il test gira anche senza Windows.

Fase 2: confrontare i percorsi A e B

Una volta che il singolo pixel torna, passare al confronto fra due intere immagini dello stesso fotogramma.

  1. Percorso A: MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING + RGB32
  2. Percorso B: ricevi NV12 / YUY2 e converti tu

Estrai un fotogramma dalla stessa posizione dello stesso video in entrambi i modi e confronta pixel per pixel.

Come leggere la differenza:
  Per ogni pixel calcola |A.R - B.R|, |A.G - B.G|, |A.B - B.B|
  Trova il massimo e la percentuale di pixel che superano la soglia

Non aspettarti una corrispondenza perfetta. I motivi sono due.

  • L’elaborazione video del Source Reader potrebbe effettuare il chroma upsampling con metodi diversi dal nearest-neighbor. La nostra implementazione di riferimento in 5.7. usa la chroma condivisa così com’è su 4 pixel, quindi le differenze emergono soprattutto sui bordi
  • Gestione diversa di arrotondamenti e precisione intermedia

Quindi bisogna guardare il pattern della differenza, non il semplice “coincidono o no”.

Differenza osservata Cosa sospettare
Le aree uniformi coincidono, differenze solo ai bordi di colore Differenza nel chroma upsampling — nel range atteso
Tutta l’immagine è spostata uniformemente Scambio di matrix (601 / 709) o range (16..235 / 0..255)
Si formano strisce o si sposta in diagonale Stride fissato a priori. Vedi 7.2. e 7.5.
Rosso e blu si scambiano Confusione tra BGRA e RGBA
Tutto appare trasparente o completamente nero Il quarto byte non è riempito con 0xFF. Vedi 7.1.

Dal “modulo” della differenza si restringe molto il campo: un offset uniforme indica formula o informazioni colore errate, una differenza locale indice o stride.

6. Quale dovresti scegliere?

In caso di dubbio, la tabella seguente risolve abbastanza bene le cose.

Aspetto Conversione automatica (MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING) Conversione manuale
Velocità di attuazione Eccellente Giusto
Estrazione di alcune foto Eccellente Buono
Volume frame elevato / tempo reale Giusto Eccellente
Controllo esplicito di matrice / intervallo Giusto Eccellente
Combinazione con GPU / D3D Giusto Da buono a eccellente
Formati di output diversi da RGB32 Giusto Eccellente
Comprendere i fondamenti Buono Eccellente

Per la tua prima implementazione, questa inquadratura semplifica il compito.

  • Voglio che funzioni prima -> conversione automatica
  • Vuoi possedere colore e prestazioni -> conversione manuale

In pratica, anche la sequenza “confermare prima l’immagine corretta con la conversione automatica, quindi sostituirla con il percorso manuale” è abbastanza efficace. Se si affronta tutto dall’inizio, diventa difficile dire dove si è interrotta l’immagine.

7. Insidie facili da individuare nella pratica

7.1. Supponendo che RGB32 sia RGBA con Alpha

In memoria, RGB32 è B, G, R, Alpha or Don't Care. Se lo scrivi su un PNG come BGRA così com’è, il quarto byte potrebbe essere 0, rendendo l’immagine trasparente. È più sicuro impostarlo su 0xFF prima di salvare.

7.2. Codificare il passo come width * bytesPerPixel

Un incidente molto comune. Il buffer di esempio effettivo può contenere riempimento, quindi la regola è utilizzare il passo effettivo per spostarsi tra le righe.

7.3. Confondere MF_MT_DEFAULT_STRIDE con il tono effettivo

MF_MT_DEFAULT_STRIDE è “il passo minimo quando quel formato è rappresentato nella memoria contigua”. Per l’altezza effettiva del buffer del campione, preferisci il valore restituito da IMF2DBuffer::Lock2D.

7.4. Indovina silenziosamente 601 / 709 senza guardare i metadati dei colori

Gli incidenti di colore sono difficili da vedere. Nemmeno loro si schiantano. Questo è ciò che li rende fastidiosi.

  • MF_MT_YUV_MATRIX
  • MF_MT_VIDEO_NOMINAL_RANGE

Per lo meno, guarda questi. E l’atteggiamento giusto è più o meno: i valori che il tuo codice non supporta dovrebbero essere errori.

7.5. Individuazione del piano NV12 UV con width * height

L’offset del piano è determinato dalla falcata e dall’altezza effettive. Non entro width * height. Se lo fai in modo sciatto, otterrai colori spostati o immagini corrotte.

7.6. Elaborazione di video interlacciati presupponendo che sia progressivo

Gli esempi manuali in questo articolo presuppongono video progressivo. Leggere il contenuto interlacciato come se ogni fotogramma fosse un singolo campo può produrre artefatti a pettine. Se hai bisogno del deinterlacciamento, è più naturale considerare l’elaborazione video automatica del Source Reader o del Video Processor MFT.

7.7. Ignorare la qualità dell’upsampling cromatico 4:2:0

Per chiarezza, la conversione NV12 in questo articolo utilizza la crominanza condivisa direttamente per ogni pixel. Ciò è sufficiente per molti usi, ma se la priorità è la qualità dell’immagine, vale la pena studiare l’approccio di conversione descritto nella documentazione dei formati YUV consigliati.

Quando si converte YUV in RGB con Media Foundation, tenere presente il seguente quadro rende molto più difficile perdersi.

  • Dietro il decoder, NV12 o YUY2 — non RGB — è quello che normalmente esce
  • Se desideri il percorso facile, richiedi RGB32 tramite MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING
  • Se vuoi il controllo, ricevi NV12 / YUY2 e converti tu stesso in BGRA
  • Nel percorso manuale, ottieni campionamento / intervallo / matrice / falcata subito prima di preoccuparti della formula
  • Essere vaghi su BT.601 / BT.709, 16..235 e 4:2:0 / 4:2:2 porta a una deriva dei colori o a immagini interrotte

YUV -> RGB all’inizio è un po’ inavvicinabile. Ma una volta che l’immagine di

  • NV12 condivide U / V su 2x2 blocchi
  • YUY2 condivide U / V su 2 pixel orizzontali
  • applicare la matrice a quell’U / V insieme a Y

ti si sistema in testa, diventa piuttosto docile. Quelle misteriose sequenze di byte di colore cosmico iniziano a sembrare pixel propriamente significativi.

9. Riferimenti

Codice di esempio per questo articolo

Articoli KomuraSoft correlati

Microsoft Learn

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Sviluppo di applicazioni Windows

Questo argomento tratta Media Foundation, Source Reader, salvataggio di immagini e conversione di fotogrammi video: un tema di implementazione dell'elaborazione multimediale Windows che si adatta bene al nostro servizio di sviluppo di applicazioni Windows.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Perché i frame di un decoder Media Foundation sono in YUV anziché in RGB?
Nel mondo dei video Windows, l'output del decodificatore non compresso è in genere un formato della famiglia YUV come NV12 o YUY2, non RGB. L'occhio umano è più sensibile ai dettagli più fini in termini di luminosità che a colori, quindi i formati video mantengono la componente Y (luminosità) al massimo dettaglio e assottigliano le componenti U / V (differenza di colore): un blocco di 2x2 pixel condivide una coppia U / V in formati 4:2:0 come NV12 e 2 pixel orizzontali condividono una coppia in formati 4:2:2 come YUY2. Quando la tua app necessita di RGB, puoi lasciare che Source Reader venga convertito in RGB32 o convertire tu stesso YUV.
Qual è il modo più semplice per ottenere RGB fotogrammi da un video in Media Foundation?
Abilita MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING durante la creazione di Source Reader e richiedi MFVideoFormat_RGB32 tramite SetCurrentMediaType: il lettore eseguirà quindi la conversione da YUV aRGB32 per te. Questo è particolarmente adatto per estrarre alcune immagini fisse, miniature e strumenti offline. Tuttavia, si tratta dell'elaborazione software che non è ottimizzata per la riproduzione, quindi è lo strumento sbagliato per il rendering in tempo reale basato su D3D o l'elaborazione di fotogrammi a volume elevato ed è incompatibile con MF_SOURCE_READER_D3D_MANAGER e MF_READWRITE_DISABLE_CONVERTERS.
Perché i colori convertiti appaiono leggermente errati o verdastri?
Le cause più comuni sono l'ignoranza della matrice dei colori e gli attributi dell'intervallo nominale e il presupposto che il passo sia uguale alla larghezza per i byte per pixel. BT.601 e BT.709 utilizzano coefficienti di conversione diversi e il video Y normalmente utilizza l'intervallo limitato 16-235 con U / V a 16-240, quindi indovinando '709 perché la risoluzione è ampia' i colori vengono spostati silenziosamente senza arresti anomali. Controlla MF_MT_YUV_MATRIX e MF_MT_VIDEO_NOMINAL_RANGE sul media type effettivo e accetta esplicitamente solo le combinazioni supportate dal tuo codice di conversione.
Quando dovrei scrivere la mia conversione da YUV a RGB invece di utilizzare quella di Source Reader?
Scrivi la tua conversione quando elabori un numero elevato di fotogrammi e desideri ottimizzarlo, vuoi alimentare NV12 direttamente alla GPU o al codice SIMD, hai bisogno di un controllo esplicito su BT.601 / BT.709 e sulla gestione dell'intervallo o hai bisogno di formati di output diversi da RGB32. I passaggi consistono nell'impostare l'output Source Reader su NV12 o YUY2, verificare cosa è effettivamente uscito con GetCurrentMediaType, controllare gli attributi dimensione frame, passo, matrice e intervallo, quindi bloccare il buffer e applicare la matrice per pixel. Restringere i presupposti accettati è importante: un convertitore che accetta tutto tende a rompere i colori silenziosamente.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog