Share via


Usando o Leitor de Origem para processar dados de mídia

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

Para usar o Leitor de Origem, siga estas etapas básicas:

  1. Crie uma instância do Leitor de Origem.
  2. Enumerar os possíveis formatos de saída.
  3. Defina o formato de saída real para cada fluxo.
  4. Processar dados da origem.

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

Criando o leitor de origem

Para criar uma instância do Leitor de Origem, chame uma das seguintes funções:

Função Descrição
MFCreateSourceReaderFromURL
Usa uma URL como entrada. Essa função usa o Resolvedor de Origem para criar uma fonte de mídia a partir da URL.
MFCreateSourceReaderFromByteStream
Usa um ponteiro para um fluxo de bytes. Essa função também usa o Resolvedor de Origem para criar a fonte de mídia.
MFCreateSourceReaderFromMediaSource
Usa um ponteiro para uma fonte de mídia que já foi criada. Essa função é útil para fontes de mídia que o Resolvedor de Origem 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 usa um ponteiro IMFAttributes opcional, que é usado para definir várias opções no Leitor de Origem, conforme descrito nos tópicos de referência para essas 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 MFStartup antes de chamar qualquer uma dessas funções.

O código a seguir cria o Leitor de Origem 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 fluxo. 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 pela interface IMFMediaType . 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 obtidos do Leitor de Origem.

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

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 origem. Se a origem representa 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 de um fluxo, chame o método IMFSourceReader::GetNativeMediaType . 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 a constante de índice de fluxo. 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 método IMFSourceReader::SetCurrentMediaType . Esse 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 origem sem decodificar, use um dos tipos retornados por GetNativeMediaType.
  • Para decodificar o fluxo, crie um novo tipo de mídia que descreva o formato descompactado desejado.

No caso do decodificador, crie o tipo de mídia da seguinte maneira:

  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 GUIDs de subtipo de áudio e GUIDs de subtipo de vídeo.)
  4. Chame IMFSourceReader::SetCurrentMediaType.

O Leitor de Origem carregará automaticamente o decodificador. 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;
}

Processando dados de mídia

Para obter dados de mídia da origem, chame o método IMFSourceReader::ReadSample , 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 desses. 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 status sinalizadores, 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 exemplo 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 interlacing, domínio de campo e outros aspectos 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 exemplo de mídia contém um único quadro de vídeo. Para um fluxo de áudio não compactado, cada exemplo 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 um exemplo 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 seja definido como NULL. Portanto, sempre marcar o valor de pSample antes de desreferenciar.

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

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

Esvaziando o pipeline 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 um exemplo com um tempo de apresentação igual a t1. O decodificador está mantendo amostras para t2 e t3.

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

Na próxima chamada para ReadSample, o Leitor de Origem pode dar t4 ao decodificador e retornar t2 ao aplicativo.

Se você quiser decodificar todos os exemplos atualmente armazenados em buffer no decodificador, sem passar novos exemplos para o decodificador, defina o sinalizador MF_SOURCE_READER_CONTROLF_DRAIN no parâmetro dwControlFlags de ReadSample. Continue a fazer isso em um loop até readSample retornar um ponteiro de exemplo NULL . Dependendo de como o decodificador armazena em buffer exemplos, 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 método IMFSourceReader::GetPresentationAttribute 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.

Procurando

Uma fonte de mídia que obtém dados de um arquivo local geralmente pode buscar posições arbitrárias no arquivo. Os dispositivos de captura, como webcams, geralmente não podem ser buscados, pois os dados estão ativos. Uma fonte que transmite dados por uma rede pode ser capaz de buscar, 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 seguinte código:

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

Essa função obtém um conjunto de sinalizadores de funcionalidades da origem. Esses sinalizadores são definidos na enumeração MFMEDIASOURCE_CHARACTERISTICS . Dois sinalizadores estão relacionados à busca:

Sinalizador Descrição
MFMEDIASOURCE_CAN_SEEK
A origem pode buscar.
MFMEDIASOURCE_HAS_SLOW_SEEK
A busca pode levar muito tempo para ser concluída. Por exemplo, a origem pode precisar baixar todo o arquivo antes que ele possa buscar. (Não há critérios estritos para uma fonte retornar 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 buscar, 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 hora 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 dar suporte a 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.

Lembre-se 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 de 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 em um arquivo, não há garantia de que o carimbo de data/hora no próximo exemplo corresponderá exatamente à posição solicitada. Em geral, a posição real não será posterior à posição solicitada, portanto, 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 Leitor de Origem, fazer normalmente não é muito útil, pelos seguintes motivos:

  • O Leitor de Origem não dá suporte à reprodução inversa, mesmo que a fonte de mídia o faça.
  • O aplicativo controla os horários de apresentação, para que o aplicativo possa implementar uma reprodução rápida ou lenta sem definir a taxa na origem.
  • Algumas fontes de mídia dão suporte ao modo de afinamento , em que a origem fornece menos amostras, normalmente apenas os quadros-chave. No entanto, se você quiser remover quadros não chave, poderá marcar cada amostra para o atributo MFSampleExtension_CleanPoint.

Para definir a taxa de reprodução usando o Leitor de Origem, 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 a DXVA (Aceleração de Vídeo) 2.0 do Microsoft DirectX para decodificação de vídeo acelerada por hardware. Para usar o DXVA com o Leitor de Origem, execute as etapas a seguir.

  1. Crie um dispositivo Microsoft Direct3D.
  2. Chame a função DXVA2CreateDirect3DDeviceManager9 para criar o gerenciador de dispositivos Direct3D. Essa função obtém um ponteiro para a interface IDirect3DDeviceManager9 .
  3. Chame o método IDirect3DDeviceManager9::ResetDevice com um ponteiro para o dispositivo Direct3D.
  4. Crie um repositório de atributos chamando a função MFCreateAttributes .
  5. Crie o Leitor de Origem. 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 Origem aloca exemplos de vídeo compatíveis com a API do processador de vídeo DXVA. Você pode usar o processamento de vídeo DXVA para executar a desinterlacagem de hardware ou a combinação de vídeos. Para obter mais informações, consulte Processamento de vídeo DXVA. Além disso, se o decodificador der suporte à DXVA 2.0, ele usará o dispositivo Direct3D para executar a decodificação acelerada por hardware.

Importante

A partir de Windows 8, IMFDXGIDeviceManager pode ser usado em vez de 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 Origem