Partilhar via


Usando o leitor de origem para processar dados de mídia

Este tópico descreve como usar o Source Reader para processar dados de mídia.

Para usar o leitor de código-fonte, siga estas etapas básicas:

  1. Crie uma instância do Source Reader.
  2. Enumere os formatos de saída possíveis.
  3. Defina o formato de saída real para cada fluxo.
  4. Processar dados da fonte.

O restante deste tópico descreve essas etapas em detalhes.

Criando o leitor de código-fonte

Para criar uma instância do Source Reader, chame uma das seguintes funções:

Função Descrição
MFCreateSourceReaderFromURL
Usa uma URL como entrada. Esta função usa o Source Resolve para criar uma fonte de mídia a partir da URL.
MFCreateSourceReaderFromByteStream
Leva um ponteiro para um fluxo de bytes. Esta função também usa o Source Resolver para criar a fonte de mídia.
MFCreateSourceReaderFromMediaSource
Leva um ponteiro para uma fonte de mídia que já foi criada. Esta função é útil para fontes de mídia que o Source Resolver não pode criar, como dispositivos de captura ou fontes de mídia personalizadas.

 

Normalmente, para arquivos de mídia, use MFCreateSourceReaderFromURL. Para dispositivos, como webcams, use MFCreateSourceReaderFromMediaSource. (Para obter mais informações sobre dispositivos de captura no Microsoft Media Foundation, consulte Captura de áudio/vídeo.)

Cada uma dessas funções recebe um ponteiro opcional IMFAttributes, que é utilizado para definir várias opções no Source Reader, conforme descrito nos tópicos de referência dessas funções. Para obter o comportamento padrão, defina esse parâmetro como NULL. Cada função retorna um ponteiro IMFSourceReader como um parâmetro de saída. Você deve chamar a função CoInitialize(Ex) e a função MFStartup antes de chamar qualquer uma dessas funções.

O código a seguir cria o leitor de código-fonte a partir de uma 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();
    }
}

Enumerando formatos de saída

Cada fonte de mídia tem pelo menos um stream. Por exemplo, um arquivo de vídeo pode conter um fluxo de vídeo e um fluxo de áudio. O formato de cada fluxo é descrito usando um tipo de mídia, representado pelo IMFMediaType interface. Para obter mais informações sobre tipos de mídia, consulte Tipos de mídia. Você deve examinar o tipo de mídia para entender o formato dos dados que você obtém do leitor de origem.

Inicialmente, cada fluxo tem um formato padrão, que você pode encontrar chamando o IMFSourceReader::GetCurrentMediaType método:

Para cada fluxo, a fonte de mídia oferece uma lista de possíveis tipos de mídia para esse fluxo. O número de tipos depende da fonte. Se a origem representar um arquivo de mídia, normalmente há apenas um tipo por fluxo. Uma webcam, por outro lado, pode ser capaz de transmitir vídeo em vários formatos diferentes. Nesse caso, o aplicativo pode selecionar qual formato usar na lista de tipos de mídia.

Para obter um dos tipos de mídia para um fluxo, chame o IMFSourceReader::GetNativeMediaType método. Esse método usa dois parâmetros de índice: O índice do fluxo e um índice na lista de tipos de mídia para o fluxo. Para enumerar todos os tipos de um fluxo, incremente o índice de lista mantendo o índice de fluxo constante. Quando o índice de lista sai dos limites, GetNativeMediaType retorna 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;
}

Para enumerar os tipos de mídia para cada fluxo, incremente o índice de fluxo. Quando o índice de fluxo sai dos limites, GetNativeMediaType retorna 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;
}

Definindo formatos de saída

Para alterar o formato de saída, chame o IMFSourceReader::SetCurrentMediaType método. Este método usa o índice de fluxo e um tipo de mídia:

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

Para o tipo de mídia, depende se você deseja inserir um decodificador.

  • Para obter dados diretamente da fonte sem decodificá-los, use um dos tipos retornados pelo GetNativeMediaType.
  • Para decodificar o fluxo, crie um novo tipo de mídia que descreva o formato não compactado desejado.

No caso do descodificador, crie o tipo de suporte da seguinte forma:

  1. Chame MFCreateMediaType para criar um novo tipo de mídia.
  2. Defina o atributo MF_MT_MAJOR_TYPE para especificar áudio ou vídeo.
  3. Defina o atributo MF_MT_SUBTYPE para especificar o subtipo do formato de decodificação. (Consulte os GUIDs de subtipo de áudio e os GUIDs de subtipo de vídeo .)
  4. Chamar IMFSourceReader::SetCurrentMediaType.

O leitor de código-fonte carregará automaticamente o descodificador. Para obter os detalhes completos do formato decodificado, chame IMFSourceReader::GetCurrentMediaType após a chamada para SetCurrentMediaType

O código a seguir configura o fluxo de vídeo para RGB-32 e o fluxo de áudio para áudio 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;
}

Processamento de dados de mídia

Para obter dados de mídia da fonte, chame o IMFSourceReader::ReadSample método, conforme mostrado no código a seguir.

        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.
            );

O primeiro parâmetro é o índice do fluxo para o qual você deseja obter dados. Você também pode especificar MF_SOURCE_READER_ANY_STREAM para obter os próximos dados disponíveis de qualquer fluxo. O segundo parâmetro contém sinalizadores opcionais; consulte MF_SOURCE_READER_CONTROL_FLAG para obter uma lista destes. O terceiro parâmetro recebe o índice do fluxo que realmente produz os dados. Você precisará dessas informações se definir o primeiro parâmetro como MF_SOURCE_READER_ANY_STREAM. O quarto parâmetro recebe sinalizadores de status, indicando vários eventos que podem ocorrer durante a leitura dos dados, como alterações de formato no fluxo. Para obter uma lista de sinalizadores de status, consulte MF_SOURCE_READER_FLAG.

Se a fonte de mídia for capaz de produzir dados para o fluxo solicitado, o último parâmetro de ReadSample receberá um ponteiro para a interface IMFSample de um objeto de amostra de mídia. Use o exemplo de mídia para:

  • Obtenha um ponteiro para os dados de mídia.
  • Obtenha o tempo de apresentação e a duração da amostra.
  • Obtenha atributos que descrevem entrelaçamento, dominância de campo e outros aspetos da amostra.

O conteúdo dos dados de mídia depende do formato do fluxo. Para um fluxo de vídeo não compactado, cada amostra de mídia contém um único quadro de vídeo. Para um fluxo de áudio não comprimido, cada amostra de mídia contém uma sequência de quadros de áudio.

O método ReadSample pode retornar S_OK e ainda não retornar uma amostra de mídia no parâmetro pSample. Por exemplo, quando você chega ao final do arquivo, ReadSample define o sinalizador MF_SOURCE_READERF_ENDOFSTREAM em dwFlags e define pSample como NULL . Nesse caso, o método ReadSample retorna S_OK porque nenhum erro ocorreu, mesmo que o parâmetro pSample esteja definido como NULL. Portanto, sempre verifique o valor de pSample antes de desreferenciá-lo.

O código a seguir mostra como chamar ReadSample em um loop e verificar as informações retornadas pelo método, até que o final do arquivo de mídia seja alcançado.

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

Drenando o fluxo de dados

Durante o processamento de dados, um decodificador ou outra transformação pode armazenar amostras de entrada em buffer. No diagrama a seguir, o aplicativo chama ReadSample e recebe uma amostra com um tempo de apresentação igual a t1. O descodificador contém amostras para t2 e t3.

uma ilustração que mostra o buffer em um decodificador.

Na próxima chamada para ReadSample, o Source Reader pode dar t4 para o decodificador e retornar t2 para a aplicação.

Se você quiser decodificar todas as amostras que estão atualmente armazenadas em buffer no decodificador, sem passar novas amostras para o decodificador, defina o sinalizador de MF_SOURCE_READER_CONTROLF_DRAIN no parâmetro dwControlFlags de ReadSample. Continue a fazer isso em um loop até que ReadSample retorne um ponteiro de exemplo NULL. Dependendo de como o decodificador armazena amostras em buffer, isso pode acontecer imediatamente ou após várias chamadas para ReadSample.

Obtendo a duração do arquivo

Para obter a duração de um arquivo de mídia, chame o IMFSourceReader::GetPresentationAttribute método e solicite o atributo MF_PD_DURATION, conforme mostrado no código a seguir.

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

A função mostrada aqui obtém a duração em unidades de 100 nanossegundos. Divida por 10.000.000 para obter a duração em segundos.

Procura

Uma fonte de mídia que obtém dados de um arquivo local geralmente pode procurar posições arbitrárias no arquivo. Dispositivos de captura, como webcams, geralmente não podem procurar, porque os dados estão ao vivo. Uma fonte que transmite dados através de uma rede pode ser capaz de procurar, dependendo do protocolo de streaming de rede.

Para descobrir se uma fonte de mídia pode buscar, chame IMFSourceReader::GetPresentationAttribute e solicite o atributo MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, conforme mostrado no código a seguir:

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

Esta função obtém um conjunto de sinalizadores de capacidades a partir da fonte. Esses sinalizadores são definidos na enumeração MFMEDIASOURCE_CHARACTERISTICS. Duas bandeiras se relacionam com a busca:

Bandeira Descrição
MFMEDIASOURCE_CAN_SEEK
A fonte pode procurar.
MFMEDIASOURCE_HAS_SLOW_SEEK
A busca pode levar muito tempo para ser concluída. Por exemplo, a fonte pode precisar baixar o arquivo inteiro antes de poder procurar. (Não há critérios rigorosos para que uma fonte retorne esse sinalizador.)

 

O código a seguir testa o sinalizador 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;
}

Para procurar, chame o método IMFSourceReader::SetCurrentPosition, conforme mostrado no código a seguir.

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

O primeiro parâmetro fornece o formato de tempo que você está usando para especificar a posição de busca. Todas as fontes de mídia no Media Foundation são necessárias para suportar unidades de 100 nanossegundos, indicadas pelo valor GUID_NULL. O segundo parâmetro é um PROPVARIANT que contém a posição de busca. Para unidades de tempo de 100 nanossegundos, o tipo de dados é LONGLONG.

Esteja ciente de que nem todas as fontes de mídia fornecem uma busca precisa de quadros. A precisão da busca depende de vários fatores, como o intervalo do quadro-chave, se o arquivo de mídia contém um índice e se os dados têm uma taxa de bits constante ou variável. Portanto, depois de buscar uma posição num ficheiro, não há garantia de que o carimbo de data e hora na próxima amostra corresponda exatamente à posição solicitada. Geralmente, a posição real não será posterior à posição solicitada, então você pode descartar amostras até chegar ao ponto desejado no fluxo.

Taxa de reprodução

Embora você possa definir a taxa de reprodução usando o Source Reader, fazê-lo normalmente não é muito útil pelos seguintes motivos:

  • O leitor de origem não suporta reprodução inversa, mesmo que a fonte de mídia o faça.
  • O aplicativo controla os tempos de apresentação, para que o aplicativo possa implementar uma reprodução rápida ou lenta sem definir a taxa na fonte.
  • Algumas fontes de conteúdos multimédia suportam o modo de diluição , onde a fonte fornece menos amostras, normalmente apenas os quadros-chave. No entanto, se pretender descartar quadros não-chave, pode verificar cada amostra para o atributo MFSampleExtension_CleanPoint.

Para definir a taxa de reprodução usando o Source Reader, chame o método IMFSourceReader::GetServiceForStream para obter as interfaces IMFRateSupport e IMFRateControl da fonte de mídia.

Aceleração de hardware

O leitor de origem é compatível com o Microsoft DirectX Video Acceleration (DXVA) 2.0 para decodificação de vídeo acelerada por hardware. Para usar o DXVA com o leitor de código-fonte, execute as etapas a seguir.

  1. Crie um dispositivo Microsoft Direct3D.
  2. Chame a função DXVA2CreateDirect3DDeviceManager9 para criar o gestor de dispositivos Direct3D. Esta função obtém um ponteiro para a IDirect3DDeviceManager9 interface.
  3. Chame o IDirect3DDeviceManager9::ResetDevice método com um ponteiro para o dispositivo Direct3D.
  4. Crie um repositório de atributos chamando a funçãoMFCreateAttributes.
  5. Crie o Leitor de Fonte. Passe o repositório de atributos no parâmetro pAttributes da função de criação.

Quando você fornece um dispositivo Direct3D, o leitor de código-fonte aloca amostras de vídeo que são compatíveis com a API do processador de vídeo DXVA. Você pode usar o processamento de vídeo DXVA para executar desentrelaçamento de hardware ou mixagem de vídeo. Para obter mais informações, consulte DXVA Video Processing. Além disso, se o decodificador suportar DXVA 2.0, ele usará o dispositivo Direct3D para executar a decodificação acelerada por hardware.

Importante

A partir do Windows 8, IMFDXGIDeviceManager pode ser usado em vez do IDirect3DDeviceManager9. Para aplicativos da Windows Store, você deve usar IMFDXGIDeviceManager. Para obter mais informações, consulte as APIs de vídeo do Direct3D 11.

 

Leitor de código-fonte