Partilhar via


Tutorial: Escrever um arquivo WMA usando objetos WMContainer

Este tutorial demonstra a gravação de um novo arquivo de áudio (.wma) extraindo conteúdo de mídia de um arquivo de áudio não compactado (.wav) e compactando-o no formato ASF. O modo de codificação usado para a conversão é CBR (Constante Decodificação de Taxa de Bits ). Nesse modo, antes da sessão de codificação, o aplicativo especifica uma taxa de bits de destino que o codificador deve alcançar.

Neste tutorial, você criará um aplicativo de console que usa os nomes de arquivo de entrada e saída como argumentos. O aplicativo obtém os exemplos de mídia descompactados de um aplicativo de análise de arquivo de onda, que é fornecido com este tutorial. Esses exemplos são enviados ao codificador para conversão no formato Windows Media Audio 9. O codificador é configurado para codificação CBR e usa a primeira taxa de bits disponível durante a negociação de tipo de mídia como a taxa de bits de destino. Os exemplos codificados são enviados para o multiplexer para empacotamento no formato de dados ASF. Esses pacotes serão gravados em um fluxo de bytes que representa o Objeto de Dados ASF. Depois que a seção de dados estiver pronta, você criará um arquivo de áudio ASF e gravará o novo Objeto de Cabeçalho ASF que consolida todas as informações de cabeçalho e acrescentará o fluxo de bytes do Objeto de Dados ASF.

Este tutorial contém as seguintes seções:

Pré-requisitos

Este tutorial pressupõe o seguinte:

  • Você está familiarizado com a estrutura de um arquivo ASF e os componentes fornecidos pelo Media Foundation para trabalhar com objetos ASF. Esses componentes incluem ContentInfo, divisor, multiplexer e objetos de perfil. Para obter mais informações, consulte Componentes do ASF WMContainer.
  • Você está familiarizado com codificadores do Windows Media e com os vários tipos de codificação, especialmente CBR. Para obter mais informações, consulte Codificadores do Windows Media .
  • Você está familiarizado com buffers de mídia e fluxos de bytes: especificamente, operações de arquivo usando um fluxo de bytes e gravando o conteúdo de um buffer de mídia em um fluxo de bytes.

Terminologia

Este tutorial usa os seguintes termos:

  • Tipo de mídia de origem: objeto de tipo de mídia expõe a interface IMFMediaType , que descreve o conteúdo do arquivo de entrada.
  • Perfil de áudio: o objeto Profile expõe a interface IMFASFProfile , que contém apenas fluxos de áudio do arquivo de saída.
  • Exemplo de fluxo: o exemplo de mídia, expõe a interface IMFSample , representa os dados de mídia do arquivo de entrada obtidos do codificador em um estado compactado.
  • Objeto ContentInfo: Objeto ContentInfo do ASF expõe a interface IMFASFContentInfo , que representa o Objeto de Cabeçalho ASF do arquivo de saída.
  • Fluxo de bytes de dados: o objeto de fluxo de bytes expõe a interface IMFByteStream , que representa toda a parte do Objeto de Dados ASF do arquivo de saída.
  • Pacote de dados: exemplo de mídia, expõe a interface IMFSample , gerada pelo Multiplexer do ASF; representa um pacote de dados ASF que será gravado no fluxo de bytes de dados.
  • Fluxo de bytes de saída: objeto de fluxo de bytes expõe a interface IMFByteStream , que contém o conteúdo do arquivo de saída.

1. Configurar o Projeto

  1. Inclua os seguintes cabeçalhos no arquivo de origem:

    #include <new>
    #include <stdio.h>       // Standard I/O
    #include <windows.h>     // Windows headers
    #include <mfapi.h>       // Media Foundation platform
    #include <wmcontainer.h> // ASF interfaces
    #include <mferror.h>     // Media Foundation error codes
    
  2. Link para os seguintes arquivos de biblioteca:

    • mfplat.lib
    • mf.lib
    • mfuuid.lib
  3. Declare a função SafeRelease .

  4. Inclua a classe CWmaEncoder em seu projeto. Para obter o código-fonte completo dessa classe, consulte Código de exemplo do codificador.

2. Declarar funções auxiliares

Este tutorial usa as seguintes funções auxiliares para ler e gravar de um fluxo de bytes.

  • AppendToByteStream: acrescenta o conteúdo de um fluxo de bytes a outro fluxo de bytes.
  • WriteBufferToByteStream: grava dados de um buffer de mídia em um fluxo de bytes.

Para obter mais informações, consulte IMFByteStream::Write. O código a seguir mostra estas funções auxiliares:

//-------------------------------------------------------------------
// AppendToByteStream
//
// Reads the contents of pSrc and writes them to pDest.
//-------------------------------------------------------------------

HRESULT AppendToByteStream(IMFByteStream *pSrc, IMFByteStream *pDest)
{
    HRESULT hr = S_OK;

    const DWORD READ_SIZE = 1024;

    BYTE buffer[READ_SIZE];

    while (1)
    {
        ULONG cbRead;

        hr = pSrc->Read(buffer, READ_SIZE, &cbRead);

        if (FAILED(hr)) { break; }

        if (cbRead == 0)
        {
            break;
        }

        hr = pDest->Write(buffer, cbRead, &cbRead);

        if (FAILED(hr)) { break; }
    }

    return hr;
}
//-------------------------------------------------------------------
// WriteBufferToByteStream
//
// Writes data from a media buffer to a byte stream.
//-------------------------------------------------------------------

HRESULT WriteBufferToByteStream(
    IMFByteStream *pStream,   // Pointer to the byte stream.
    IMFMediaBuffer *pBuffer,  // Pointer to the media buffer.
    DWORD *pcbWritten         // Receives the number of bytes written.
    )
{
    HRESULT hr = S_OK;
    DWORD cbData = 0;
    DWORD cbWritten = 0;
    BYTE *pMem = NULL;

    hr = pBuffer->Lock(&pMem, NULL, &cbData);

    if (SUCCEEDED(hr))
    {
        hr = pStream->Write(pMem, cbData, &cbWritten);
    }

    if (SUCCEEDED(hr))
    {
        if (pcbWritten)
        {
            *pcbWritten = cbWritten;
        }
    }

    if (pMem)
    {
        pBuffer->Unlock();
    }
    return hr;
}

3. Abrir um arquivo de áudio

Este tutorial pressupõe que seu aplicativo gerará áudio não compactado para codificação. Para essa finalidade, duas funções são declaradas neste tutorial:

HRESULT OpenAudioFile(PCWSTR pszURL, IMFMediaType **ppAudioFormat);
HRESULT GetNextAudioSample(BOOL *pbEOS, IMFSample **ppSample);

A implementação dessas funções é deixada para o leitor.

  • A OpenAudioFile função deve abrir o arquivo de mídia especificado por pszURL e retornar um ponteiro para um tipo de mídia que descreve um fluxo de áudio.
  • A GetNextAudioSample função deve ler áudio PCM descompactado do arquivo que foi aberto por OpenAudioFile. Quando o final do arquivo é atingido, o pbEOS recebe o valor TRUE. Caso contrário, ppSample receberá um exemplo de mídia que contém o buffer de áudio.

4. Configurar o codificador

Em seguida, crie o codificador, configure-o para produzir exemplos de fluxo codificados em CBR e negocie a entrada e os tipos de mídia de saída.

No Media Foundation, os codificadores (expõem a interface IMFTransform) são implementados como MFT ( Media Foundation Transforms ).

Neste tutorial, o codificador é implementado na CWmaEncoder classe que fornece um wrapper para o MFT. Para obter o código-fonte completo dessa classe, consulte Código de exemplo do codificador.

Observação

Opcionalmente, você pode especificar o tipo de codificação como CBR. Por padrão, o codificador é configurado para usar a codificação CBR. Para obter mais informações, consulte Codificação de taxa de bits constante. Você pode definir propriedades adicionais dependendo do tipo de codificação, para obter informações sobre as propriedades específicas de um modo de codificação, consulte Codificação de taxa de bits variável baseada em qualidade, Codificação de taxa de bit variável irrestrita e Codificação de taxa de bit variável restrita de pico.

 

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

5. Crie o objeto ContentInfo do ASF.

O objeto ContentInfo do ASF contém informações sobre os vários objetos de cabeçalho do arquivo de saída.

Primeiro, crie um perfil ASF para o fluxo de áudio:

  1. Chame MFCreateASFProfile para criar um objeto de perfil ASF vazio. O perfil do ASF expõe a interface IMFASFProfile . Para obter mais informações, consulte Criando e configurando fluxos ASF.
  2. Obtenha o formato de áudio codificado do CWmaEncoder objeto .
  3. Chame IMFASFProfile::CreateStream para criar um novo fluxo para o perfil do ASF. Passe um ponteiro para a interface IMFMediaType , que representa o formato de fluxo.
  4. Chame IMFASFStreamConfig::SetStreamNumber para atribuir um identificador de fluxo.
  5. Defina os parâmetros de "bucket com vazamento" definindo o atributo MF_ASFSTREAMCONFIG_LEAKYBUCKET1 no objeto de fluxo.
  6. Chame IMFASFProfile::SetStream para adicionar o novo fluxo ao perfil.

Agora, crie o objeto ContentInfo do ASF da seguinte maneira:

  1. Chame MFCreateASFContentInfo para criar um objeto ContentInfo vazio.
  2. Chame IMFASFContentInfo::SetProfile para definir o perfil do ASF.

O código a seguir mostra essas etapas:

HRESULT CreateASFContentInfo(
    CWmaEncoder* pEncoder,
    IMFASFContentInfo** ppContentInfo
    )
{
    HRESULT hr = S_OK;
    
    IMFASFProfile* pProfile = NULL;
    IMFMediaType* pMediaType = NULL;
    IMFASFStreamConfig* pStream = NULL;
    IMFASFContentInfo* pContentInfo = NULL;

    // Create the ASF profile object.

    hr = MFCreateASFProfile(&pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a stream description for the encoded audio.

    hr = pEncoder->GetOutputType(&pMediaType); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pProfile->CreateStream(pMediaType, &pStream); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetStreamNumber(DEFAULT_STREAM_NUMBER); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Set "leaky bucket" values.

    LeakyBucket bucket;

    hr = pEncoder->GetLeakyBucket1(&bucket);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetBlob(
        MF_ASFSTREAMCONFIG_LEAKYBUCKET1, 
        (UINT8*)&bucket, 
        sizeof(bucket)
        );

    if (FAILED(hr))
    {
        goto done;
    }

    //Add the stream to the profile

    hr = pProfile->SetStream(pStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the ASF ContentInfo object.

    hr = MFCreateASFContentInfo(&pContentInfo); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pContentInfo->SetProfile(pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the pointer to the caller.

    *ppContentInfo = pContentInfo;
    (*ppContentInfo)->AddRef();

done:
    SafeRelease(&pProfile);
    SafeRelease(&pStream);
    SafeRelease(&pMediaType);
    SafeRelease(&pContentInfo);
    return hr;
}

6. Criar o Multiplexer ASF

O Multiplexer do ASF gera pacotes de dados ASF.

  1. Chame MFCreateASFMultiplexer para criar o multiplexador ASF.
  2. Chame IMFASFMultiplexer::Initialize para inicializar o multiplexer. Passe um ponteiro para o objeto Informações de Conteúdo do ASF, que foi criado na seção anterior.
  3. Chame IMFASFMultiplexer::SetFlags para definir o sinalizador MFASF_MULTIPLEXER_AUTOADJUST_BITRATE . Quando essa configuração é usada, o multiplexer ajusta automaticamente a taxa de bits do conteúdo ASF para corresponder às características dos fluxos que estão sendo multiplexados.
HRESULT CreateASFMux( 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer** ppMultiplexer
    )
{
    HRESULT hr = S_OK;
    
    IMFMediaType* pMediaType = NULL;
    IMFASFMultiplexer *pMultiplexer = NULL;

    // Create and initialize the ASF Multiplexer object.

    hr = MFCreateASFMultiplexer(&pMultiplexer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pMultiplexer->Initialize(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    // Enable automatic bit-rate adjustment.

    hr = pMultiplexer->SetFlags(MFASF_MULTIPLEXER_AUTOADJUST_BITRATE);
    if (FAILED(hr))
    {
        goto done;
    }

    *ppMultiplexer = pMultiplexer;
    (*ppMultiplexer)->AddRef();

done:
    SafeRelease(&pMultiplexer);
    return hr;
}

7. Gerar novos pacotes de dados ASF

Em seguida, gere pacotes de dados ASF para o novo arquivo. Esses pacotes de dados constituirão o objeto de dados ASF final para o novo arquivo. Para gerar novos pacotes de dados ASF:

  1. Chame MFCreateTempFile para criar um fluxo de bytes temporário para manter os pacotes de dados ASF.
  2. Chame a função definida GetNextAudioSample pelo aplicativo para obter dados de áudio descompactados para o codificador.
  3. Passe o áudio descompactado para o codificador para compactação. Para obter mais informações, consulte Processando dados no codificador
  4. Chame IMFASFMultiplexer::P rocessSample para enviar os exemplos de áudio compactados para o multiplexador ASF para pacotes.
  5. Obtenha os pacotes ASF do multiplexador e escreva-os no fluxo de bytes temporário. Para obter mais informações, consulte Gerando novos pacotes de dados ASF.
  6. Quando você chegar ao final do fluxo de origem, escorra o codificador e efetue pull das amostras compactadas restantes do codificador. Para obter mais informações sobre como esvaziar um MFT, consulte Modelo de processamento MFT básico.
  7. Depois que todos os exemplos forem enviados para o multiplexador, chame IMFASFMultiplexer::Flush e efetue pull dos pacotes ASF restantes do multiplexador.
  8. Chame IMFASFMultiplexer::End.

O código a seguir gera pacotes de dados ASF. A função retorna um ponteiro para um fluxo de bytes que contém o objeto de dados ASF.

HRESULT EncodeData(
    CWmaEncoder* pEncoder, 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer* pMux, 
    IMFByteStream** ppDataStream) 
{
    HRESULT hr = S_OK;

    IMFByteStream* pStream = NULL;
    IMFSample* pInputSample = NULL;
    IMFSample* pWmaSample = NULL;

    BOOL bEOF = FALSE;

   // Create a temporary file to hold the data stream.
   hr = MFCreateTempFile(
        MF_ACCESSMODE_READWRITE, 
        MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE,
        &pStream
        );

   if (FAILED(hr))
   {
       goto done;
   }

    BOOL bNeedInput = TRUE;

    while (TRUE)
    {
        if (bNeedInput)
        {
            hr = GetNextAudioSample(&bEOF, &pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            if (bEOF)
            {
                // Reached the end of the input file.
                break;
            }

            // Encode the uncompressed audio sample.
            hr = pEncoder->ProcessInput(pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            bNeedInput = FALSE;
        }

        if (bNeedInput == FALSE)
        {
            // Get data from the encoder.

            hr = pEncoder->ProcessOutput(&pWmaSample);
            if (FAILED(hr))
            {
                goto done;
            }

            // pWmaSample can be NULL if the encoder needs more input.

            if (pWmaSample)
            {
                hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
                if (FAILED(hr))
                {
                    goto done;
                }

                //Collect the data packets and write them to a stream
                hr = GenerateASFDataPackets(pMux, pStream);
                if (FAILED(hr))
                {
                    goto done;
                }
            }
            else
            {
                bNeedInput = TRUE;
            }
        }
        
        SafeRelease(&pInputSample);
        SafeRelease(&pWmaSample);
    }

    // Drain the MFT and pull any remaining samples from the encoder.

    hr = pEncoder->Drain();
    if (FAILED(hr))
    {
        goto done;
    }

    while (TRUE)
    {
        hr = pEncoder->ProcessOutput(&pWmaSample);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pWmaSample == NULL)
        {
            break;
        }

        hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
        if (FAILED(hr))
        {
            goto done;
        }

        //Collect the data packets and write them to a stream
        hr = GenerateASFDataPackets(pMux, pStream);
        if (FAILED(hr))
        {
            goto done;
        }

        SafeRelease(&pWmaSample);
    }

    // Flush the mux and get any pending ASF data packets.
    hr = pMux->Flush();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = GenerateASFDataPackets(pMux, pStream);
    if (FAILED(hr))
    {
        goto done;
    }
    
    // Update the ContentInfo object
    hr = pMux->End(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    //Return stream to the caller that contains the ASF encoded data.
    *ppDataStream = pStream;
    (*ppDataStream)->AddRef();

done:
    SafeRelease(&pStream);
    SafeRelease(&pInputSample);
    SafeRelease(&pWmaSample);
    return hr;
}

O código para a GenerateASFDataPackets função é mostrado no tópico Gerando novos pacotes de dados ASF.

8. Gravar o arquivo ASF

Em seguida, escreva o cabeçalho ASF em um buffer de mídia chamando IMFASFContentInfo::GenerateHeader. Esse método converte os dados armazenados no objeto ContentInfo em dados binários no formato objeto de cabeçalho ASF. Para obter mais informações, consulte Gerando um novo objeto de cabeçalho ASF.

Depois que o novo Objeto de Cabeçalho ASF tiver sido gerado, crie um fluxo de bytes para o arquivo de saída. Primeiro, escreva o Objeto de Cabeçalho no fluxo de bytes de saída. Siga o Objeto de Cabeçalho com o Objeto de Dados contido no fluxo de bytes de dados.

HRESULT WriteASFFile( 
     IMFASFContentInfo *pContentInfo, 
     IMFByteStream *pDataStream,
     PCWSTR pszFile
     )
{
    HRESULT hr = S_OK;
    
    IMFMediaBuffer* pHeaderBuffer = NULL;
    IMFByteStream* pWmaStream = NULL;

    DWORD cbHeaderSize = 0;
    DWORD cbWritten = 0;

    //Create output file
    hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE, pszFile, &pWmaStream);

    if (FAILED(hr))
    {
        goto done;
    }


    // Get the size of the ASF Header Object.
    hr = pContentInfo->GenerateHeader (NULL, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a media buffer.
    hr = MFCreateMemoryBuffer(cbHeaderSize, &pHeaderBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Populate the media buffer with the ASF Header Object.
    hr = pContentInfo->GenerateHeader(pHeaderBuffer, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF header to the output file.
    hr = WriteBufferToByteStream(pWmaStream, pHeaderBuffer, &cbWritten);
    if (FAILED(hr))
    {
        goto done;
    }

    // Append the data stream to the file.

    hr = pDataStream->SetCurrentPosition(0);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = AppendToByteStream(pDataStream, pWmaStream);

done:
    SafeRelease(&pHeaderBuffer);
    SafeRelease(&pWmaStream);
    return hr;
}

9. Definir a função Entry-Point

Agora você pode colocar as etapas anteriores juntas em um aplicativo completo. Antes de usar qualquer um dos objetos do Media Foundation, inicialize a plataforma Media Foundation chamando MFStartup. Quando terminar, chame MFShutdown. Para obter mais informações, confira Inicializando o Media Foundation.

O código a seguir mostra o aplicativo de console completo. O argumento de linha de comando especifica o nome do arquivo a ser convertido e o nome do novo arquivo de áudio.

int wmain(int argc, WCHAR* argv[])
{
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    if (argc != 3)
    {
        wprintf_s(L"Usage: %s input.wmv, %s output.wma");
        return 0;
    }

    const WCHAR* sInputFileName = argv[1];    // Source file name
    const WCHAR* sOutputFileName = argv[2];  // Output file name
    
    IMFMediaType* pInputType = NULL;
    IMFASFContentInfo* pContentInfo = NULL;
    IMFASFMultiplexer* pMux = NULL;
    IMFByteStream* pDataStream = NULL;

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    HRESULT hr = CoInitializeEx(
        NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFStartup(MF_VERSION);
    if (FAILED(hr))
    {
        goto done;
    }

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the WMContainer objects.
    hr = CreateASFContentInfo(pEncoder, &pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = CreateASFMux(pContentInfo, &pMux);
    if (FAILED(hr))
    {
        goto done;
    }

    // Convert uncompressed data to ASF format.
    hr = EncodeData(pEncoder, pContentInfo, pMux, &pDataStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF objects to the output file.
    hr = WriteASFFile(pContentInfo, pDataStream, sOutputFileName);

done:
    SafeRelease(&pInputType);
    SafeRelease(&pContentInfo);
    SafeRelease(&pMux);
    SafeRelease(&pDataStream);
    delete pEncoder;

    MFShutdown();
    CoUninitialize();

    if (FAILED(hr))
    {
        wprintf_s(L"Error: 0x%X\n", hr);
    }

    return 0;
} 

Componentes do ASF WMContainer

Suporte do ASF no Media Foundation