Compartir a través de


Tutorial: Escritura de un archivo WMA mediante objetos WMContainer

En este tutorial se muestra cómo escribir un nuevo archivo de audio (.wma) extrayendo contenido multimedia de un archivo de audio sin comprimir (.wav) y comprimiéndolo en formato ASF. El modo de codificación usado para la conversión es Codificación de velocidad de bits constante (CBR). En este modo, antes de la sesión de codificación, la aplicación especifica una velocidad de bits de destino que el codificador debe lograr.

En este tutorial, creará una aplicación de consola que toma los nombres de archivo de entrada y salida como argumentos. La aplicación obtiene los ejemplos de medios sin comprimir de una aplicación de análisis de archivos de onda, que se proporciona con este tutorial. Estos ejemplos se envían al codificador para la conversión al formato Windows Media Audio 9. El codificador está configurado para la codificación CBR y usa la primera velocidad de bits disponible durante la negociación de tipo multimedia como velocidad de bits de destino. Los ejemplos codificados se envían al multiplexador para la paqueteización en formato de datos ASF. Estos paquetes se escribirán en una secuencia de bytes que representa el objeto de datos ASF. Una vez que la sección de datos esté lista, creará un archivo de audio ASF y escribirá el nuevo objeto de encabezado ASF que consolida toda la información de encabezado y, a continuación, anexará el flujo de bytes del objeto de datos ASF.

Este tutorial contiene las secciones siguientes:

Requisitos previos

En este tutorial se da por hecho lo siguiente:

  • Está familiarizado con la estructura de un archivo ASF y los componentes proporcionados por Media Foundation para trabajar con objetos ASF. Estos componentes incluyen contentInfo, divisor, multiplexador y objetos de perfil. Para obtener más información, vea Componentes de ASF de WMContainer.
  • Está familiarizado con los codificadores de Windows Media y los distintos tipos de codificación, especialmente CBR. Para obtener más información, vea Codificadores de Windows Media .
  • Está familiarizado con los búferes multimedia y las secuencias de bytes: en concreto, las operaciones de archivo que usan una secuencia de bytes y escriben el contenido de un búfer multimedia en una secuencia de bytes.

Terminología

En este tutorial se usan los términos siguientes:

  • Tipo de medio de origen: objeto de tipo de medio, expone la interfaz IMFMediaType , que describe el contenido del archivo de entrada.
  • Perfil de audio: objeto profile, expone la interfaz IMFASFProfile , que solo contiene secuencias de audio del archivo de salida.
  • Ejemplo de secuencia: el ejemplo multimedia, expone la interfaz IMFSample , representa los datos multimedia del archivo de entrada obtenidos del codificador en un estado comprimido.
  • Objeto ContentInfo: Objeto ContentInfo de ASF, expone la interfaz IMFASFContentInfo , que representa el objeto de encabezado ASF del archivo de salida.
  • Flujo de bytes de datos: objeto de secuencia de bytes, expone la interfaz IMFByteStream , que representa toda la parte del objeto de datos ASF del archivo de salida.
  • Paquete de datos: muestra multimedia, expone la interfaz IMFSample , generada por el multiplexador ASF; representa un paquete de datos ASF que se escribirá en el flujo de bytes de datos.
  • Flujo de bytes de salida: objeto de secuencia de bytes, expone la interfaz IMFByteStream , que contiene el contenido del archivo de salida.

1. Configurar el proyecto

  1. Incluya los siguientes encabezados en el archivo de origen:

    #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. Vínculo a los siguientes archivos de biblioteca:

    • mfplat.lib
    • mf.lib
    • mfuuid.lib
  3. Declare la función SafeRelease .

  4. Incluya la clase CWmaEncoder en el proyecto. Para obtener el código fuente completo de esta clase, vea Código de ejemplo del codificador.

2. Declarar funciones auxiliares

En este tutorial se usan las siguientes funciones auxiliares para leer y escribir desde una secuencia de bytes.

  • AppendToByteStream: anexa el contenido de una secuencia de bytes a otra secuencia de bytes.
  • WriteBufferToByteStream: escribe datos de un búfer multimedia en una secuencia de bytes.

Para obtener más información, vea IMFByteStream::Write. En el código siguiente se muestran estas funciones 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 un archivo de audio

En este tutorial se supone que la aplicación generará audio sin comprimir para la codificación. Para ello, se declaran dos funciones en este tutorial:

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

La implementación de estas funciones se deja al lector.

  • La OpenAudioFile función debe abrir el archivo multimedia especificado por pszURL y devolver un puntero a un tipo de medio que describe una secuencia de audio.
  • La GetNextAudioSample función debe leer el audio PCM sin comprimir del archivo abierto por OpenAudioFile. Cuando se alcanza el final del archivo, pbEOS recibe el valor TRUE. De lo contrario, ppSample recibe una muestra multimedia que contiene el búfer de audio.

4. Configurar el codificador

A continuación, cree el codificador, configúrelo para generar ejemplos de secuencias codificados en CBR y negociar los tipos de medios de entrada y salida.

En Media Foundation, los codificadores (exponer la interfaz IMFTransform ) se implementan como transformaciones de Media Foundation (MFT).

En este tutorial, el codificador se implementa en la CWmaEncoder clase que proporciona un contenedor para MFT. Para obtener el código fuente completo de esta clase, vea Código de ejemplo del codificador.

Nota

Opcionalmente, puede especificar el tipo de codificación como CBR. De forma predeterminada, el codificador está configurado para usar la codificación CBR. Para obtener más información, consulte Codificación de velocidad de bits constante. Puede establecer propiedades adicionales según el tipo de codificación, para obtener información sobre las propiedades específicas de un modo de codificación, vea Codificación de velocidad de bits variable basada en calidad, Codificación de velocidad de bits variable sin restricciones, Codificación de velocidad de bits variable sin restricciones y Codificación de velocidad de bits variable restringida 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. Cree el objeto ContentInfo de ASF.

El objeto ContentInfo de ASF contiene información sobre los distintos objetos de encabezado del archivo de salida.

En primer lugar, cree un perfil de ASF para la secuencia de audio:

  1. Llame a MFCreateASFProfile para crear un objeto de perfil ASF vacío. El perfil de ASF expone la interfaz IMFASFProfile . Para obtener más información, consulte Creación y configuración de secuencias asf.
  2. Obtenga el formato de audio codificado del CWmaEncoder objeto .
  3. Llame a IMFASFProfile::CreateStream para crear una nueva secuencia para el perfil de ASF. Pase un puntero a la interfaz IMFMediaType , que representa el formato de secuencia.
  4. Llame a IMFASFStreamConfig::SetStreamNumber para asignar un identificador de secuencia.
  5. Establezca los parámetros "bucket de fuga" estableciendo el atributo MF_ASFSTREAMCONFIG_LEAKYBUCKET1 en el objeto stream.
  6. Llame a IMFASFProfile::SetStream para agregar la nueva secuencia al perfil.

Ahora cree el objeto ContentInfo de ASF de la siguiente manera:

  1. Llame a MFCreateASFContentInfo para crear un objeto ContentInfo vacío.
  2. Llame a IMFASFContentInfo::SetProfile para establecer el perfil de ASF.

El siguiente código muestra estos pasos:

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. Creación del multiplexador ASF

El multiplexador ASF genera paquetes de datos asf.

  1. Llame a MFCreateASFMultiplexer para crear el multiplexador ASF.
  2. Llame a IMFASFMultiplexer::Initialize para inicializar el multiplexador. Pase un puntero al objeto de información de contenido de ASF, que se creó en la sección anterior.
  3. Llame a IMFASFMultiplexer::SetFlags para establecer la marca MFASF_MULTIPLEXER_AUTOADJUST_BITRATE . Cuando se usa esta configuración, el multiplexador ajusta automáticamente la velocidad de bits del contenido de ASF para que coincida con las características de las secuencias que se multiplexan.
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. Generar nuevos paquetes de datos asf

A continuación, genere paquetes de datos ASF para el nuevo archivo. Estos paquetes de datos constituyen el objeto de datos ASF final para el nuevo archivo. Para generar nuevos paquetes de datos de ASF:

  1. Llame a MFCreateTempFile para crear una secuencia de bytes temporal que contenga los paquetes de datos de ASF.
  2. Llame a la función definida por GetNextAudioSample la aplicación para obtener datos de audio sin comprimir para el codificador.
  3. Pase el audio sin comprimir al codificador para la compresión. Para obtener más información, consulte Procesamiento de datos en el codificador.
  4. Llame a IMFASFMultiplexer::P rocessSample para enviar las muestras de audio comprimidos al multiplexador ASF para la paqueteización.
  5. Obtenga los paquetes ASF del multiplexador y escríbalos en la secuencia de bytes temporal. Para obtener más información, vea Generar nuevos paquetes de datos ASF.
  6. Cuando llegue al final de la secuencia de origen, desagüe el codificador y extraiga las muestras comprimidas restantes del codificador. Para obtener más información sobre cómo purgar un MFT, vea Modelo de procesamiento de MFT básico.
  7. Después de enviar todas las muestras al multiplexador, llame a IMFASFMultiplexer::Flush y extraiga los paquetes ASF restantes del multiplexador.
  8. Llame a IMFASFMultiplexer::End.

El código siguiente genera paquetes de datos ASF. La función devuelve un puntero a una secuencia de bytes que contiene el objeto de datos 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;
}

El código de la GenerateASFDataPackets función se muestra en el tema Generar nuevos paquetes de datos asf.

8. Escribir el archivo ASF

A continuación, escriba el encabezado ASF en un búfer multimedia llamando a IMFASFContentInfo::GenerateHeader. Este método convierte los datos almacenados en el objeto ContentInfo en datos binarios en formato de objeto de encabezado ASF. Para obtener más información, vea Generar un nuevo objeto de encabezado ASF.

Una vez generado el nuevo objeto de encabezado ASF, cree un flujo de bytes para el archivo de salida. En primer lugar, escriba el objeto header en el flujo de bytes de salida. Siga el objeto header con el objeto data contenido en el flujo de bytes de datos.

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 la función Entry-Point

Ahora puede reunir los pasos anteriores en una aplicación completa. Antes de usar cualquiera de los objetos de Media Foundation, inicialice la plataforma Media Foundation mediante una llamada a MFStartup. Cuando haya terminado, llame a MFShutdown. Para obtener más información, vea Inicializar Media Foundation.

El código siguiente muestra la aplicación de consola completa. El argumento de línea de comandos especifica el nombre del archivo que se va a convertir y el nombre del nuevo archivo de audio.

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 de ASF de WMContainer

Compatibilidad con ASF en Media Foundation