Tutorial: Descodificación de audio
En este tutorial se muestra cómo usar el Lector de origen para descodificar audio de un archivo multimedia y escribir el audio en un archivo WAVE. El tutorial se basa en el ejemplo de clip de audio .
- Información general
- Archivos de encabezado y biblioteca
- Implementación de wmain
- Escribir el archivo WAVE
- Configurar el lector de origen
- Escribir el encabezado de archivo WAVE
- Calcular el tamaño máximo de datos
- Descodificar el audio
- Finalizar el encabezado de archivo
- Temas relacionados
Información general
En este tutorial, creará una aplicación de consola que toma dos argumentos de línea de comandos: el nombre de un archivo de entrada que contiene una secuencia de audio y el nombre del archivo de salida. La aplicación lee cinco segundos de datos de audio del archivo de entrada y escribe el audio en el archivo de salida como datos WAVE.
Para obtener los datos de audio descodificados, la aplicación usa el objeto lector de origen. El lector de origen expone la interfaz IMFSourceReader . Para escribir el audio descodificado en el archivo WAVE, las aplicaciones usan funciones de E/S de Windows. En la imagen siguiente se muestra este proceso.
En su forma más sencilla, un archivo WAVE tiene la siguiente estructura:
Tipo de datos | Tamaño (bytes) | Valor |
---|---|---|
FOURCC | 4 | 'RIFF' |
DWORD | 4 | Tamaño total del archivo, sin incluir los primeros 8 bytes |
FOURCC | 4 | 'WAVE' |
FOURCC | 4 | 'fmt' |
DWORD | 4 | Tamaño de los datos WAVEFORMATEX siguientes. |
WAVEFORMATEX | Varía | Encabezado de formato de audio. |
FOURCC | 4 | 'data' |
DWORD | 4 | Tamaño de los datos de audio. |
BYTE[] | Varía | Datos de audio. |
Nota
Un FOURCC es un DWORD formado por la concatenación de cuatro caracteres ASCII.
Esta estructura básica se puede ampliar agregando metadatos de archivo y otra información, que está fuera del ámbito de este tutorial.
Archivos de encabezado y biblioteca
Incluya los siguientes archivos de encabezado en el proyecto:
#define WINVER _WIN32_WINNT_WIN7
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>
Vínculo a las siguientes bibliotecas:
- mfplat.lib
- mfreadwrite.lib
- mfuuid.lib
Implementación de wmain
En el código siguiente se muestra la función de punto de entrada para la aplicación.
int wmain(int argc, wchar_t* argv[])
{
HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
if (argc != 3)
{
printf("arguments: input_file output_file.wav\n");
return 1;
}
const WCHAR *wszSourceFile = argv[1];
const WCHAR *wszTargetFile = argv[2];
const LONG MAX_AUDIO_DURATION_MSEC = 5000; // 5 seconds
HRESULT hr = S_OK;
IMFSourceReader *pReader = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
// Initialize the COM library.
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
// Initialize the Media Foundation platform.
if (SUCCEEDED(hr))
{
hr = MFStartup(MF_VERSION);
}
// Create the source reader to read the input file.
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromURL(wszSourceFile, NULL, &pReader);
if (FAILED(hr))
{
printf("Error opening input file: %S\n", wszSourceFile, hr);
}
}
// Open the output file for writing.
if (SUCCEEDED(hr))
{
hFile = CreateFile(wszTargetFile, GENERIC_WRITE, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
hr = HRESULT_FROM_WIN32(GetLastError());
printf("Cannot create output file: %S\n", wszTargetFile, hr);
}
}
// Write the WAVE file.
if (SUCCEEDED(hr))
{
hr = WriteWaveFile(pReader, hFile, MAX_AUDIO_DURATION_MSEC);
}
if (FAILED(hr))
{
printf("Failed, hr = 0x%X\n", hr);
}
// Clean up.
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
}
SafeRelease(&pReader);
MFShutdown();
CoUninitialize();
return SUCCEEDED(hr) ? 0 : 1;
};
Esta función hace lo siguiente:
- Llama a CoInitializeEx para inicializar la biblioteca COM.
- Llama a MFStartup para inicializar la plataforma de Media Foundation.
- Llama a MFCreateSourceReaderFromURL para crear el lector de origen. Esta función toma el nombre del archivo de entrada y recibe un puntero de interfaz IMFSourceReader .
- Crea el archivo de salida llamando a la función CreateFile , que devuelve un identificador de archivo.
- Llama a la función WriteWavFile definida por la aplicación. Esta función descodifica el audio y escribe el archivo WAVE.
- Libera el puntero IMFSourceReader y el identificador de archivo.
- Llama a MFShutdown para apagar la plataforma de Media Foundation.
- Llama a CoUninitialize para liberar la biblioteca COM.
Escribir el archivo WAVE
La mayoría del trabajo se produce en la función , a la WriteWavFile
que se llama desde wmain
.
//-------------------------------------------------------------------
// WriteWaveFile
//
// Writes a WAVE file by getting audio data from the source reader.
//
//-------------------------------------------------------------------
HRESULT WriteWaveFile(
IMFSourceReader *pReader, // Pointer to the source reader.
HANDLE hFile, // Handle to the output file.
LONG msecAudioData // Maximum amount of audio data to write, in msec.
)
{
HRESULT hr = S_OK;
DWORD cbHeader = 0; // Size of the WAVE file header, in bytes.
DWORD cbAudioData = 0; // Total bytes of PCM audio data written to the file.
DWORD cbMaxAudioData = 0;
IMFMediaType *pAudioType = NULL; // Represents the PCM audio format.
// Configure the source reader to get uncompressed PCM audio from the source file.
hr = ConfigureAudioStream(pReader, &pAudioType);
// Write the WAVE file header.
if (SUCCEEDED(hr))
{
hr = WriteWaveHeader(hFile, pAudioType, &cbHeader);
}
// Calculate the maximum amount of audio to decode, in bytes.
if (SUCCEEDED(hr))
{
cbMaxAudioData = CalculateMaxAudioDataSize(pAudioType, cbHeader, msecAudioData);
// Decode audio data to the file.
hr = WriteWaveData(hFile, pReader, cbMaxAudioData, &cbAudioData);
}
// Fix up the RIFF headers with the correct sizes.
if (SUCCEEDED(hr))
{
hr = FixUpChunkSizes(hFile, cbHeader, cbAudioData);
}
SafeRelease(&pAudioType);
return hr;
}
Esta función llama a una serie de otras funciones definidas por la aplicación:
- La función ConfigureAudioStream inicializa el lector de origen. Esta función recibe un puntero a la interfaz IMFMediaType , que se usa para obtener una descripción del formato de audio descodificado, incluida la frecuencia de muestreo, el número de canales y la profundidad de bits (bits por muestra).
- La función WriteWaveHeader escribe la primera parte del archivo WAVE, incluido el encabezado y el inicio del fragmento "data".
- La función CalculateMaxAudioDataSize calcula la cantidad máxima de audio que se va a escribir en el archivo, en bytes.
- La función WriteWaveData escribe los datos de audio de PCM en el archivo.
- La función FixUpChunkSizes escribe la información de tamaño de archivo que aparece después de los valores "RIFF" y "data" FOURCC en el archivo WAVE. (Estos valores no se conocen hasta
WriteWaveData
que se completa).
Estas funciones se muestran en las secciones restantes de este tutorial.
Configurar el lector de origen
La ConfigureAudioStream
función configura el lector de origen para descodificar la secuencia de audio en el archivo de origen. También devuelve información sobre el formato del audio descodificado.
En Media Foundation, los formatos multimedia se describen mediante objetos de tipo multimedia . Un objeto de tipo multimedia expone la interfaz IMFMediaType , que hereda la interfaz IMFAttributes . Básicamente, un tipo de medio es una colección de propiedades que describen el formato. Para obtener más información, vea Tipos de medios.
//-------------------------------------------------------------------
// ConfigureAudioStream
//
// Selects an audio stream from the source file, and configures the
// stream to deliver decoded PCM audio.
//-------------------------------------------------------------------
HRESULT ConfigureAudioStream(
IMFSourceReader *pReader, // Pointer to the source reader.
IMFMediaType **ppPCMAudio // Receives the audio format.
)
{
IMFMediaType *pUncompressedAudioType = NULL;
IMFMediaType *pPartialType = NULL;
// Select the first audio stream, and deselect all other streams.
HRESULT hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);
if (SUCCEEDED(hr))
{
hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
}
// Create a partial media type that specifies uncompressed PCM audio.
hr = MFCreateMediaType(&pPartialType);
if (SUCCEEDED(hr))
{
hr = pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
}
if (SUCCEEDED(hr))
{
hr = pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
}
// Set this type on the source reader. The source reader will
// load the necessary decoder.
if (SUCCEEDED(hr))
{
hr = pReader->SetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
NULL, pPartialType);
}
// Get the complete uncompressed format.
if (SUCCEEDED(hr))
{
hr = pReader->GetCurrentMediaType(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
&pUncompressedAudioType);
}
// Ensure the stream is selected.
if (SUCCEEDED(hr))
{
hr = pReader->SetStreamSelection(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
TRUE);
}
// Return the PCM format to the caller.
if (SUCCEEDED(hr))
{
*ppPCMAudio = pUncompressedAudioType;
(*ppPCMAudio)->AddRef();
}
SafeRelease(&pUncompressedAudioType);
SafeRelease(&pPartialType);
return hr;
}
La ConfigureAudioStream
función hace lo siguiente:
- Llama al método IMFSourceReader::SetStreamSelection para seleccionar la secuencia de audio y anular la selección de todas las demás secuencias. Este paso puede mejorar el rendimiento, ya que impide que el lector de origen mantenga en fotogramas de vídeo que la aplicación no use.
- Crea un tipo de medio parcial que especifica audio PCM. La función crea el tipo parcial de la siguiente manera:
- Llama a MFCreateMediaType para crear un objeto de tipo multimedia vacío.
- Establece el atributo MF_MT_MAJOR_TYPEen MFMediaType_Audio.
- Establece el atributo MF_MT_SUBTYPEen MFAudioFormat_PCM.
- Llama a IMFSourceReader::SetCurrentMediaType para establecer el tipo parcial en el lector de origen. Si el archivo de origen contiene audio codificado, el lector de origen carga automáticamente el descodificador de audio necesario.
- Llama a IMFSourceReader::GetCurrentMediaType para obtener el tipo de medio PCM real. Este método devuelve un tipo de medio con todos los detalles de formato rellenados, como la frecuencia de muestreo de audio y el número de canales.
- Llama a IMFSourceReader::SetStreamSelection para habilitar la secuencia de audio.
Escribir el encabezado de archivo WAVE
La WriteWaveHeader
función escribe el encabezado de archivo WAVE.
La única API de Media Foundation llamada desde esta función es MFCreateWaveFormatExFromMFMediaType, que convierte el tipo de medio en una estructura WAVEFORMATEX .
//-------------------------------------------------------------------
// WriteWaveHeader
//
// Write the WAVE file header.
//
// Note: This function writes placeholder values for the file size
// and data size, as these values will need to be filled in later.
//-------------------------------------------------------------------
HRESULT WriteWaveHeader(
HANDLE hFile, // Output file.
IMFMediaType *pMediaType, // PCM audio format.
DWORD *pcbWritten // Receives the size of the header.
)
{
HRESULT hr = S_OK;
UINT32 cbFormat = 0;
WAVEFORMATEX *pWav = NULL;
*pcbWritten = 0;
// Convert the PCM audio format into a WAVEFORMATEX structure.
hr = MFCreateWaveFormatExFromMFMediaType(pMediaType, &pWav, &cbFormat);
// Write the 'RIFF' header and the start of the 'fmt ' chunk.
if (SUCCEEDED(hr))
{
DWORD header[] = {
// RIFF header
FCC('RIFF'),
0,
FCC('WAVE'),
// Start of 'fmt ' chunk
FCC('fmt '),
cbFormat
};
DWORD dataHeader[] = { FCC('data'), 0 };
hr = WriteToFile(hFile, header, sizeof(header));
// Write the WAVEFORMATEX structure.
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, pWav, cbFormat);
}
// Write the start of the 'data' chunk
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, dataHeader, sizeof(dataHeader));
}
if (SUCCEEDED(hr))
{
*pcbWritten = sizeof(header) + cbFormat + sizeof(dataHeader);
}
}
CoTaskMemFree(pWav);
return hr;
}
La WriteToFile
función es una función auxiliar sencilla que ajusta la función WriteFile de Windows y devuelve un valor HRESULT .
//-------------------------------------------------------------------
//
// Writes a block of data to a file
//
// hFile: Handle to the file.
// p: Pointer to the buffer to write.
// cb: Size of the buffer, in bytes.
//
//-------------------------------------------------------------------
HRESULT WriteToFile(HANDLE hFile, void* p, DWORD cb)
{
DWORD cbWritten = 0;
HRESULT hr = S_OK;
BOOL bResult = WriteFile(hFile, p, cb, &cbWritten, NULL);
if (!bResult)
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}
Calcular el tamaño máximo de datos
Dado que el tamaño del archivo se almacena como un valor de 4 bytes en el encabezado de archivo, un archivo WAVE se limita a un tamaño máximo de 0xFFFFFFFF bytes, aproximadamente 4 GB. Este valor incluye el tamaño del encabezado de archivo. El audio PCM tiene una velocidad de bits constante, por lo que puede calcular el tamaño máximo de datos a partir del formato de audio, como se indica a continuación:
//-------------------------------------------------------------------
// CalculateMaxAudioDataSize
//
// Calculates how much audio to write to the WAVE file, given the
// audio format and the maximum duration of the WAVE file.
//-------------------------------------------------------------------
DWORD CalculateMaxAudioDataSize(
IMFMediaType *pAudioType, // The PCM audio format.
DWORD cbHeader, // The size of the WAVE file header.
DWORD msecAudioData // Maximum duration, in milliseconds.
)
{
UINT32 cbBlockSize = 0; // Audio frame size, in bytes.
UINT32 cbBytesPerSecond = 0; // Bytes per second.
// Get the audio block size and number of bytes/second from the audio format.
cbBlockSize = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_BLOCK_ALIGNMENT, 0);
cbBytesPerSecond = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 0);
// Calculate the maximum amount of audio data to write.
// This value equals (duration in seconds x bytes/second), but cannot
// exceed the maximum size of the data chunk in the WAVE file.
// Size of the desired audio clip in bytes:
DWORD cbAudioClipSize = (DWORD)MulDiv(cbBytesPerSecond, msecAudioData, 1000);
// Largest possible size of the data chunk:
DWORD cbMaxSize = MAXDWORD - cbHeader;
// Maximum size altogether.
cbAudioClipSize = min(cbAudioClipSize, cbMaxSize);
// Round to the audio block size, so that we do not write a partial audio frame.
cbAudioClipSize = (cbAudioClipSize / cbBlockSize) * cbBlockSize;
return cbAudioClipSize;
}
Para evitar fotogramas de audio parciales, el tamaño se redondea a la alineación del bloque, que se almacena en el atributo MF_MT_AUDIO_BLOCK_ALIGNMENT .
Descodificar el audio
La WriteWaveData
función lee el audio descodificado del archivo de origen y escribe en el archivo WAVE.
//-------------------------------------------------------------------
// WriteWaveData
//
// Decodes PCM audio data from the source file and writes it to
// the WAVE file.
//-------------------------------------------------------------------
HRESULT WriteWaveData(
HANDLE hFile, // Output file.
IMFSourceReader *pReader, // Source reader.
DWORD cbMaxAudioData, // Maximum amount of audio data (bytes).
DWORD *pcbDataWritten // Receives the amount of data written.
)
{
HRESULT hr = S_OK;
DWORD cbAudioData = 0;
DWORD cbBuffer = 0;
BYTE *pAudioData = NULL;
IMFSample *pSample = NULL;
IMFMediaBuffer *pBuffer = NULL;
// Get audio samples from the source reader.
while (true)
{
DWORD dwFlags = 0;
// Read the next sample.
hr = pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
0, NULL, &dwFlags, NULL, &pSample );
if (FAILED(hr)) { break; }
if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
printf("Type change - not supported by WAVE file format.\n");
break;
}
if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM)
{
printf("End of input file.\n");
break;
}
if (pSample == NULL)
{
printf("No sample\n");
continue;
}
// Get a pointer to the audio data in the sample.
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (FAILED(hr)) { break; }
hr = pBuffer->Lock(&pAudioData, NULL, &cbBuffer);
if (FAILED(hr)) { break; }
// Make sure not to exceed the specified maximum size.
if (cbMaxAudioData - cbAudioData < cbBuffer)
{
cbBuffer = cbMaxAudioData - cbAudioData;
}
// Write this data to the output file.
hr = WriteToFile(hFile, pAudioData, cbBuffer);
if (FAILED(hr)) { break; }
// Unlock the buffer.
hr = pBuffer->Unlock();
pAudioData = NULL;
if (FAILED(hr)) { break; }
// Update running total of audio data.
cbAudioData += cbBuffer;
if (cbAudioData >= cbMaxAudioData)
{
break;
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
}
if (SUCCEEDED(hr))
{
printf("Wrote %d bytes of audio data.\n", cbAudioData);
*pcbDataWritten = cbAudioData;
}
if (pAudioData)
{
pBuffer->Unlock();
}
SafeRelease(&pBuffer);
SafeRelease(&pSample);
return hr;
}
La WriteWaveData
función realiza lo siguiente en un bucle:
- Llama a IMFSourceReader::ReadSample para leer audio desde el archivo de origen. El parámetro dwFlags recibe un OR bit a bit de las marcas de la enumeración MF_SOURCE_READER_FLAG . El parámetro pSample recibe un puntero a la interfaz IMFSample , que se usa para acceder a los datos de audio. En algunos casos, una llamada a ReadSample no genera datos, en cuyo caso el puntero IMFSample es NULL.
- Comprueba dwFlags para las marcas siguientes:
- MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Esta marca indica un cambio de formato en el archivo de origen. Los archivos WAVE no admiten cambios de formato.
- MF_SOURCE_READERF_ENDOFSTREAM. Esta marca indica el final de la secuencia.
- Llama a IMFSample::ConvertToContiguousBuffer para obtener un puntero a un objeto de búfer.
- Llama a IMFMediaBuffer::Lock para obtener un puntero a la memoria del búfer.
- Escribe los datos de audio en el archivo de salida.
- Llama a IMFMediaBuffer::Unlock para desbloquear el objeto de búfer.
La función se interrumpe del bucle cuando se produce alguna de las siguientes acciones:
- El formato de la secuencia cambia.
- Se llega al final de la secuencia.
- La cantidad máxima de datos de audio se escribe en el archivo de salida.
- Se produce un error.
Finalizar el encabezado de archivo
Los valores de tamaño almacenados en el encabezado WAVE no se conocen hasta que se completa la función anterior.
FixUpChunkSizes
Rellena estos valores:
//-------------------------------------------------------------------
// FixUpChunkSizes
//
// Writes the file-size information into the WAVE file header.
//
// WAVE files use the RIFF file format. Each RIFF chunk has a data
// size, and the RIFF header has a total file size.
//-------------------------------------------------------------------
HRESULT FixUpChunkSizes(
HANDLE hFile, // Output file.
DWORD cbHeader, // Size of the 'fmt ' chuck.
DWORD cbAudioData // Size of the 'data' chunk.
)
{
HRESULT hr = S_OK;
LARGE_INTEGER ll;
ll.QuadPart = cbHeader - sizeof(DWORD);
if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
// Write the data size.
if (SUCCEEDED(hr))
{
hr = WriteToFile(hFile, &cbAudioData, sizeof(cbAudioData));
}
if (SUCCEEDED(hr))
{
// Write the file size.
ll.QuadPart = sizeof(FOURCC);
if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
if (SUCCEEDED(hr))
{
DWORD cbRiffFileSize = cbHeader + cbAudioData - 8;
// NOTE: The "size" field in the RIFF header does not include
// the first 8 bytes of the file. (That is, the size of the
// data that appears after the size field.)
hr = WriteToFile(hFile, &cbRiffFileSize, sizeof(cbRiffFileSize));
}
return hr;
}
Temas relacionados