Uso del lettore di origine per elaborare i dati multimediali

In questo argomento viene descritto come usare il lettore di origine per elaborare i dati multimediali.

Per usare il lettore di origine, seguire questa procedura di base:

  1. Creare un'istanza del lettore di origine.
  2. Enumerare i possibili formati di output.
  3. Impostare il formato di output effettivo per ogni flusso.
  4. Elaborare i dati dall'origine.

La parte restante di questo argomento descrive in dettaglio questi passaggi.

Creazione del lettore di origine

Per creare un'istanza del lettore di origine, chiamare una delle funzioni seguenti:

Funzione Descrizione
MFCreateSourceReaderFromURL
Accetta un URL come input. Questa funzione usa il resolver di origine per creare un'origine multimediale dall'URL.
MFCreateSourceReaderFromByteStream
Accetta un puntatore a un flusso di byte. Questa funzione usa anche il resolver di origine per creare l'origine multimediale.
MFCreateSourceReaderFromMediaSource
Accetta un puntatore a un'origine multimediale già creata. Questa funzione è utile per le origini multimediali che il Resolver di origine non può creare, ad esempio dispositivi di acquisizione o origini multimediali personalizzate.

 

In genere, per i file multimediali, usare MFCreateSourceReaderFromURL. Per i dispositivi, ad esempio le webcam, usare MFCreateSourceReaderFromMediaSource. Per altre informazioni sui dispositivi di acquisizione in Microsoft Media Foundation, vedere Audio/Video Capture.

Ognuna di queste funzioni accetta un puntatore FMAttributes facoltativo, che viene usato per impostare varie opzioni nel lettore di origine, come descritto negli argomenti di riferimento per queste funzioni. Per ottenere il comportamento predefinito, impostare questo parametro su NULL. Ogni funzione restituisce un puntatore IMFSourceReader come parametro di output. È necessario chiamare la funzione CoInitialize(Ex) e MFStartup prima di chiamare una di queste funzioni.

Il codice seguente crea il lettore di origine da un URL.

int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
    if (argc < 2)
    {
        return 1;
    }

    const WCHAR *pszURL = argv[1];

    // Initialize the COM runtime.
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Initialize the Media Foundation platform.
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            // Create the source reader.
            IMFSourceReader *pReader;
            hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
            if (SUCCEEDED(hr))
            {
                ReadMediaFile(pReader);
                pReader->Release();
            }
            // Shut down Media Foundation.
            MFShutdown();
        }
        CoUninitialize();
    }
}

Enumerazione dei formati di output

Ogni origine multimediale ha almeno un flusso. Ad esempio, un file video potrebbe contenere un flusso video e un flusso audio. Il formato di ogni flusso viene descritto usando un tipo di supporto, rappresentato dall'interfaccia IMFMediaType . Per altre informazioni sui tipi di supporti, vedere Tipi di supporti. È necessario esaminare il tipo di supporto per comprendere il formato dei dati ottenuti dal lettore di origine.

Inizialmente, ogni flusso ha un formato predefinito, che è possibile trovare chiamando il metodo IMFSourceReader::GetCurrentMediaType :

Per ogni flusso, l'origine multimediale offre un elenco di possibili tipi di supporti per tale flusso. Il numero di tipi dipende dall'origine. Se l'origine rappresenta un file multimediale, in genere è presente un solo tipo per flusso. Una webcam, d'altra parte, potrebbe essere in grado di trasmettere video in diversi formati diversi. In tal caso, l'applicazione può selezionare il formato da usare dall'elenco dei tipi di supporti.

Per ottenere uno dei tipi di supporti per un flusso, chiamare il metodo IMFSourceReader::GetNativeMediaType . Questo metodo accetta due parametri di indice: indice del flusso e un indice nell'elenco dei tipi multimediali per il flusso. Per enumerare tutti i tipi per un flusso, incrementare l'indice di elenco mantenendo costante l'indice di flusso. Quando l'indice di elenco non supera i limiti, GetNativeMediaType restituisce MF_E_NO_MORE_TYPES.

HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    HRESULT hr = S_OK;
    DWORD dwMediaTypeIndex = 0;

    while (SUCCEEDED(hr))
    {
        IMFMediaType *pType = NULL;
        hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
        if (hr == MF_E_NO_MORE_TYPES)
        {
            hr = S_OK;
            break;
        }
        else if (SUCCEEDED(hr))
        {
            // Examine the media type. (Not shown.)

            pType->Release();
        }
        ++dwMediaTypeIndex;
    }
    return hr;
}

Per enumerare i tipi di supporti per ogni flusso, aumentare l'indice di flusso. Quando l'indice di flusso non supera i limiti, GetNativeMediaType restituisce MF_E_INVALIDSTREAMNUMBER.

HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    DWORD dwStreamIndex = 0;

    while (SUCCEEDED(hr))
    {
        hr = EnumerateTypesForStream(pReader, dwStreamIndex);
        if (hr == MF_E_INVALIDSTREAMNUMBER)
        {
            hr = S_OK;
            break;
        }
        ++dwStreamIndex;
    }
    return hr;
}

Impostazione dei formati di output

Per modificare il formato di output, chiamare il metodo IMFSourceReader::SetCurrentMediaType . Questo metodo accetta l'indice di flusso e un tipo di supporto:

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

Per il tipo di supporto, dipende dal fatto che si vuole inserire un decodificatore.

  • Per ottenere dati direttamente dall'origine senza decodificarlo, usare uno dei tipi restituiti da GetNativeMediaType.
  • Per decodificare il flusso, creare un nuovo tipo di supporto che descrive il formato non compresso desiderato.

Nel caso del decodificatore, creare il tipo di supporto come indicato di seguito:

  1. Chiamare MFCreateMediaType per creare un nuovo tipo di supporto.
  2. Impostare l'attributo MF_MT_MAJOR_TYPE per specificare audio o video.
  3. Impostare l'attributo MF_MT_SUBTYPE per specificare il sottotipo del formato di decodifica. Vedere GUID sottotipi audio e GUID sottotipivideo.
  4. Chiamare IMFSourceReader::SetCurrentMediaType.

Il lettore di origine caricherà automaticamente il decodificatore. Per ottenere i dettagli completi del formato decodificato, chiamare FMSourceReader::GetCurrentMediaType dopo la chiamata a SetCurrentMediaType

Il codice seguente configura il flusso video per RGB-32 e il flusso audio per l'audio PCM.

HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    IMFMediaType *pNativeType = NULL;
    IMFMediaType *pType = NULL;

    // Find the native format of the stream.
    HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
    if (FAILED(hr))
    {
        return hr;
    }

    GUID majorType, subtype;

    // Find the major type.
    hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Define the output type.
    hr = MFCreateMediaType(&pType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Select a subtype.
    if (majorType == MFMediaType_Video)
    {
        subtype= MFVideoFormat_RGB32;
    }
    else if (majorType == MFMediaType_Audio)
    {
        subtype = MFAudioFormat_PCM;
    }
    else
    {
        // Unrecognized type. Skip.
        goto done;
    }

    hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the uncompressed format.
    hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
    if (FAILED(hr))
    {
        goto done;
    }

done:
    SafeRelease(&pNativeType);
    SafeRelease(&pType);
    return hr;
}

Elaborazione dei dati multimediali

Per ottenere dati multimediali dall'origine, chiamare il metodo IMFSourceReader::ReadSample , come illustrato nel codice seguente.

        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

Il primo parametro è l'indice del flusso per il quale si desidera ottenere dati. È anche possibile specificare MF_SOURCE_READER_ANY_STREAM per ottenere i dati disponibili successivi da qualsiasi flusso. Il secondo parametro contiene flag facoltativi; vedere MF_SOURCE_READER_CONTROL_FLAG per un elenco di questi elementi. Il terzo parametro riceve l'indice del flusso che produce effettivamente i dati. Queste informazioni saranno necessarie se si imposta il primo parametro su MF_SOURCE_READER_ANY_STREAM. Il quarto parametro riceve i flag di stato, che indicano vari eventi che possono verificarsi durante la lettura dei dati, ad esempio le modifiche al formato nel flusso. Per un elenco di flag di stato, vedere MF_SOURCE_READER_FLAG.

Se l'origine multimediale è in grado di produrre dati per il flusso richiesto, l'ultimo parametro di ReadSample riceve un puntatore all'interfaccia FMSample di un oggetto di esempio multimediale. Usare l'esempio multimediale per:

  • Ottenere un puntatore ai dati multimediali.
  • Ottenere l'ora di presentazione e la durata dell'esempio.
  • Ottenere attributi che descrivono l'interlacciamento, la dominanza dei campi e altri aspetti dell'esempio.

Il contenuto dei dati multimediali dipende dal formato del flusso. Per un flusso video non compresso, ogni esempio multimediale contiene un singolo fotogramma video. Per un flusso audio non compresso, ogni esempio multimediale contiene una sequenza di fotogrammi audio.

Il metodo ReadSample può restituire S_OK e non restituire ancora un esempio multimediale nel parametro pSample . Ad esempio, quando si raggiunge la fine del file, ReadSample imposta il flag di MF_SOURCE_READERF_ENDOFSTREAM in dwFlags e imposta pSample su NULL. In questo caso, il metodo ReadSample restituisce S_OK perché non si è verificato alcun errore, anche se il parametro pSample è impostato su NULL. Controllare quindi sempre il valore di pSample prima di dereferenziarlo.

Il codice seguente illustra come chiamare ReadSample in un ciclo e controllare le informazioni restituite dal metodo, fino alla fine del file multimediale.

HRESULT ProcessSamples(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    IMFSample *pSample = NULL;
    size_t  cSamples = 0;

    bool quit = false;
    while (!quit)
    {
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

        if (FAILED(hr))
        {
            break;
        }

        wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
        if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            wprintf(L"\tEnd of stream\n");
            quit = true;
        }
        if (flags & MF_SOURCE_READERF_NEWSTREAM)
        {
            wprintf(L"\tNew stream\n");
        }
        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            wprintf(L"\tNative type changed\n");
        }
        if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            wprintf(L"\tCurrent type changed\n");
        }
        if (flags & MF_SOURCE_READERF_STREAMTICK)
        {
            wprintf(L"\tStream tick\n");
        }

        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            // The format changed. Reconfigure the decoder.
            hr = ConfigureDecoder(pReader, streamIndex);
            if (FAILED(hr))
            {
                break;
            }
        }

        if (pSample)
        {
            ++cSamples;
        }

        SafeRelease(&pSample);
    }

    if (FAILED(hr))
    {
        wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
    }
    else
    {
        wprintf(L"Processed %d samples\n", cSamples);
    }
    SafeRelease(&pSample);
    return hr;
}

Svuotamento della pipeline di dati

Durante l'elaborazione dei dati, un decodificatore o un'altra trasformazione potrebbe eseguire il buffer degli esempi di input. Nel diagramma seguente l'applicazione chiama ReadSample e riceve un esempio con un tempo di presentazione uguale a t1. Il decodificatore contiene campioni per t2 e t3.

figura che mostra il buffering in un decodificatore.

Nella chiamata successiva a ReadSample, il lettore di origine potrebbe dare t4 al decodificatore e restituire t2 all'applicazione.

Se si desidera decodificare tutti gli esempi attualmente memorizzati nel buffer nel decodificatore, senza passare nuovi campioni al decodificatore, impostare il flag MF_SOURCE_READER_CONTROLF_DRAIN nel parametro dwControlFlags di ReadSample. Continuare a eseguire questa operazione in un ciclo finché ReadSample non restituisce un puntatore di esempio NULL . A seconda del modo in cui i campioni del decodificatore vengono memorizzati nel buffer, che possono verificarsi immediatamente o dopo diverse chiamate a ReadSample.

Recupero della durata del file

Per ottenere la durata di un file multimediale, chiamare il metodo IMFSourceReader::GetPresentationAttribute e richiedere l'attributo MF_PD_DURATION , come illustrato nel codice seguente.

HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
    PROPVARIANT var;
    HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, 
        MF_PD_DURATION, &var);
    if (SUCCEEDED(hr))
    {
        hr = PropVariantToInt64(var, phnsDuration);
        PropVariantClear(&var);
    }
    return hr;
}

La funzione illustrata qui ottiene la durata in unità di 100 nanosecondi. Dividere per 10.000.000 per ottenere la durata in secondi.

Riercare

Un'origine multimediale che ottiene dati da un file locale può in genere cercare posizioni arbitrarie nel file. I dispositivi di acquisizione, ad esempio le webcam, in genere non possono cercare, perché i dati sono in tempo reale. Un'origine che trasmette i dati in una rete potrebbe essere in grado di cercare, a seconda del protocollo di streaming di rete.

Per verificare se un'origine multimediale può cercare, chiamare IMFSourceReader::GetPresentationAttribute e richiedere l'attributo MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS , come illustrato nel codice seguente:

HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
    ULONG flags = 0;

    PROPVARIANT var;
    PropVariantInit(&var);

    HRESULT hr = pReader->GetPresentationAttribute(
        MF_SOURCE_READER_MEDIASOURCE, 
        MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, 
        &var);

    if (SUCCEEDED(hr))
    {
        hr = PropVariantToUInt32(var, &flags);
    }
    if (SUCCEEDED(hr))
    {
        *pulFlags = flags;
    }

    PropVariantClear(&var);
    return hr;
}

Questa funzione ottiene un set di flag di funzionalità dall'origine. Questi flag sono definiti nell'enumerazione MFMEDIASOURCE_CHARACTERISTICS . Due flag sono correlati alla ricerca:

Flag Descrizione
MFMEDIASOURCE_CAN_SEEK
L'origine può cercare.
MFMEDIASOURCE_HAS_SLOW_SEEK
Il completamento della ricerca potrebbe richiedere molto tempo. Ad esempio, l'origine potrebbe dover scaricare l'intero file prima di poter eseguire la ricerca. Non esistono criteri rigorosi per restituire questo flag da parte di un'origine.

 

Il codice seguente verifica il flag di MFMEDIASOURCE_CAN_SEEK .

BOOL SourceCanSeek(IMFSourceReader *pReader)
{
    BOOL bCanSeek = FALSE;
    ULONG flags;
    if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
    {
        bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
    }
    return bCanSeek;
}

Per eseguire la ricerca, chiamare il metodo IMFSourceReader::SetCurrentPosition , come illustrato nel codice seguente.

HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
    PROPVARIANT var;
    HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetCurrentPosition(GUID_NULL, var);
        PropVariantClear(&var);
    }
    return hr;
}

Il primo parametro fornisce il formato dell'ora in uso per specificare la posizione di ricerca. Tutte le origini multimediali in Media Foundation sono necessarie per supportare unità da 100 nanosecondi, indicate dal valore GUID_NULL. Il secondo parametro è un PROPVARIANT che contiene la posizione di ricerca. Per le unità di tempo di 100 nanosecondi, il tipo di dati è LONGLONG.

Tenere presente che non tutte le origini multimediali forniscono una ricerca accurata dei fotogrammi. L'accuratezza della ricerca dipende da diversi fattori, ad esempio l'intervallo dei fotogrammi chiave, se il file multimediale contiene un indice e se i dati hanno una frequenza di bit costante o variabile. Pertanto, dopo aver cercato una posizione in un file, non esiste alcuna garanzia che il timestamp del campione successivo corrisponda esattamente alla posizione richiesta. In genere, la posizione effettiva non sarà successiva alla posizione richiesta, quindi è possibile eliminare i campioni fino a raggiungere il punto desiderato nel flusso.

Velocità di riproduzione

Anche se è possibile impostare la frequenza di riproduzione usando il lettore di origine, in genere non è molto utile, per i motivi seguenti:

  • Il lettore di origine non supporta la riproduzione inversa, anche se l'origine multimediale lo esegue.
  • L'applicazione controlla i tempi di presentazione, in modo che l'applicazione possa implementare la riproduzione veloce o lenta senza impostare la frequenza nell'origine.
  • Alcune origini multimediali supportano la modalità di thinning , in cui l'origine fornisce meno campioni, in genere solo i fotogrammi chiave. Tuttavia, se si desidera eliminare fotogrammi non chiave, è possibile controllare ogni esempio per l'attributo MFSampleExtension_CleanPoint .

Per impostare la frequenza di riproduzione usando source Reader, chiamare il metodo IMFSourceReader::GetServiceForStream per ottenere le interfacce IMFRateSupport e IMFRateControl dall'origine multimediale.

Accelerazione hardware

Il lettore di origine è compatibile con Microsoft DirectX Video Acceleration (DXVA) 2.0 per la decodifica video accelerata hardware. Per usare DXVA con il lettore di origine, seguire questa procedura.

  1. Creare un dispositivo Microsoft Direct3D.
  2. Chiamare la funzione DXVA2CreateDirect3DDeviceManager9 per creare la gestione dispositivi Direct3D. Questa funzione ottiene un puntatore all'interfaccia IDirect3DDeviceManager9 .
  3. Chiamare il metodo IDirect3DDeviceManager9::ResetDevice con un puntatore al dispositivo Direct3D.
  4. Creare un archivio attributi chiamando la funzione MFCreateAttributes .
  5. Creare il lettore di origine. Passare l'archivio attributi nel parametro pAttributes della funzione di creazione.

Quando si fornisce un dispositivo Direct3D, il lettore di origine alloca esempi video compatibili con l'API del processore video DXVA. È possibile usare l'elaborazione video DXVA per eseguire la disincantazione hardware o la combinazione di video. Per altre informazioni, vedere Elaborazione video DXVA. Inoltre, se il decodificatore supporta DXVA 2.0, userà il dispositivo Direct3D per eseguire la decodifica accelerata hardware.

Importante

A partire da Windows 8, IMFDXGIDeviceManager può essere usato anziché IDirect3DDeviceManager9. Per le app di Windows Store, devi usare IMFDXGIDeviceManager. Per altre info, vedi le API video Direct3D 11.

 

Lettore di origine