Tutorial: Decodificação de áudio
Este tutorial mostra como usar o Leitor de Origem para decodificar áudio de um arquivo de mídia e gravar o áudio em um arquivo WAVE. O tutorial é baseado no exemplo de Clipe de Áudio .
- Visão geral
- Arquivos de cabeçalho e biblioteca
- Implementar wmain
- Gravar o arquivo WAVE
- Configurar o Leitor de Origem
- Gravar o cabeçalho do arquivo WAVE
- Calcular o tamanho máximo dos dados
- Decodificar o áudio
- Finalizar o cabeçalho do arquivo
- Tópicos relacionados
Visão geral
Neste tutorial, você criará um aplicativo de console que usa dois argumentos de linha de comando: o nome de um arquivo de entrada que contém um fluxo de áudio e o nome do arquivo de saída. O aplicativo lê cinco segundos de dados de áudio do arquivo de entrada e grava o áudio no arquivo de saída como dados WAVE.
Para obter os dados de áudio decodificados, o aplicativo usa o objeto de leitor de origem. O leitor de origem expõe a interface IMFSourceReader . Para gravar o áudio decodificado no arquivo WAVE, os aplicativos usam funções de E/S do Windows. A imagem a seguir ilustra esse processo.
Em sua forma mais simples, um arquivo WAVE tem a seguinte estrutura:
Tipo de Dados | Tamanho (Bytes) | Valor |
---|---|---|
FOURCC | 4 | 'RIFF' |
DWORD | 4 | Tamanho total do arquivo, sem incluir os primeiros 8 bytes |
FOURCC | 4 | 'WAVE' |
FOURCC | 4 | 'fmt' |
DWORD | 4 | Tamanho dos dados WAVEFORMATEX a seguir. |
WAVEFORMATEX | Varia | Cabeçalho de formato de áudio. |
FOURCC | 4 | 'data' |
DWORD | 4 | Tamanho dos dados de áudio. |
BYTE[] | Varia | Dados de áudio. |
Observação
UM FOURCC é um DWORD formado pela concatenação de quatro caracteres ASCII.
Essa estrutura básica pode ser estendida adicionando metadados de arquivo e outras informações, que estão além do escopo deste tutorial.
Arquivos de cabeçalho e biblioteca
Inclua os seguintes arquivos de cabeçalho em seu projeto:
#define WINVER _WIN32_WINNT_WIN7
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>
Link para as seguintes bibliotecas:
- mfplat.lib
- mfreadwrite.lib
- mfuuid.lib
Implementar wmain
O código a seguir mostra a função de ponto de entrada para o aplicativo.
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;
};
Essa função faz o seguinte:
- Chama CoInitializeEx para inicializar a biblioteca COM.
- Chama MFStartup para inicializar a plataforma do Media Foundation.
- Chama MFCreateSourceReaderFromURL para criar o leitor de origem. Essa função usa o nome do arquivo de entrada e recebe um ponteiro de interface IMFSourceReader .
- Cria o arquivo de saída chamando a função CreateFile , que retorna um identificador de arquivo.
- Chama a função WriteWavFile definida pelo aplicativo. Essa função decodifica o áudio e grava o arquivo WAVE.
- Libera o ponteiro IMFSourceReader e o identificador de arquivo.
- Chama o MFShutdown para desligar a plataforma do Media Foundation.
- Chama CoUninitialize para liberar a biblioteca COM.
Gravar o arquivo WAVE
A maior parte do trabalho ocorre na WriteWavFile
função , que é chamada de 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;
}
Essa função chama uma série de outras funções definidas pelo aplicativo:
- A função ConfigureAudioStream inicializa o leitor de origem. Essa função recebe um ponteiro para a interface IMFMediaType , que é usada para obter uma descrição do formato de áudio decodificado, incluindo taxa de exemplo, número de canais e profundidade de bits (bits por exemplo).
- A função WriteWaveHeader grava a primeira parte do arquivo WAVE, incluindo o cabeçalho e o início da parte "dados".
- A função CalculateMaxAudioDataSize calcula a quantidade máxima de áudio a ser gravada no arquivo, em bytes.
- A função WriteWaveData grava os dados de áudio do PCM no arquivo.
- A função FixUpChunkSizes grava as informações de tamanho do arquivo exibidas após os valores "RIFF" e "data" FOURCC no arquivo WAVE. (Esses valores não são conhecidos até
WriteWaveData
a conclusão.)
Essas funções são mostradas nas seções restantes deste tutorial.
Configurar o Leitor de Origem
A ConfigureAudioStream
função configura o leitor de origem para decodificar o fluxo de áudio no arquivo de origem. Ele também retorna informações sobre o formato do áudio decodificado.
No Media Foundation, os formatos de mídia são descritos usando objetos de tipo de mídia . Um objeto de tipo de mídia expõe a interface IMFMediaType , que herda a interface IMFAttributes . Essencialmente, um tipo de mídia é uma coleção de propriedades que descrevem o formato. Para obter mais informações, consulte Tipos de mídia.
//-------------------------------------------------------------------
// 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;
}
A ConfigureAudioStream
função faz o seguinte:
- Chama o método IMFSourceReader::SetStreamSelection para selecionar o fluxo de áudio e desmarcar todos os outros fluxos. Essa etapa pode melhorar o desempenho, pois impede que o leitor de origem a segurando quadros de vídeo que o aplicativo não usa.
- Cria um tipo de mídia parcial que especifica o áudio PCM. A função cria o tipo parcial da seguinte maneira:
- Chama MFCreateMediaType para criar um objeto de tipo de mídia vazio.
- Define o atributo MF_MT_MAJOR_TYPE como MFMediaType_Audio.
- Define o atributo MF_MT_SUBTYPE como MFAudioFormat_PCM.
- Chama IMFSourceReader::SetCurrentMediaType para definir o tipo parcial no leitor de origem. Se o arquivo de origem contiver áudio codificado, o leitor de origem carregará automaticamente o decodificador de áudio necessário.
- Chama IMFSourceReader::GetCurrentMediaType para obter o tipo de mídia PCM real. Esse método retorna um tipo de mídia com todos os detalhes de formato preenchidos, como a taxa de amostragem de áudio e o número de canais.
- Chama IMFSourceReader::SetStreamSelection para habilitar o fluxo de áudio.
Gravar o cabeçalho do arquivo WAVE
A WriteWaveHeader
função grava o cabeçalho do arquivo WAVE.
A única API do Media Foundation chamada dessa função é MFCreateWaveFormatExFromMFMediaType, que converte o tipo de mídia em uma estrutura 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;
}
A WriteToFile
função é uma função auxiliar simples que encapsula a função WriteFile do Windows e retorna um 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 o tamanho máximo dos dados
Como o tamanho do arquivo é armazenado como um valor de 4 bytes no cabeçalho do arquivo, um arquivo WAVE é limitado a um tamanho máximo de 0xFFFFFFFF bytes— aproximadamente 4 GB. Esse valor inclui o tamanho do cabeçalho do arquivo. O áudio PCM tem uma taxa de bits constante, portanto, você pode calcular o tamanho máximo dos dados do formato de áudio, da seguinte maneira:
//-------------------------------------------------------------------
// 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 quadros de áudio parciais, o tamanho é arredondado para o alinhamento do bloco, que é armazenado no atributo MF_MT_AUDIO_BLOCK_ALIGNMENT .
Decodificar o áudio
A WriteWaveData
função lê o áudio decodificado do arquivo de origem e grava no arquivo 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;
}
A WriteWaveData
função faz o seguinte em um loop:
- Chama IMFSourceReader::ReadSample para ler áudio do arquivo de origem. O parâmetro dwFlags recebe um OR bit a bit de sinalizadores da enumeração MF_SOURCE_READER_FLAG . O parâmetro pSample recebe um ponteiro para a interface IMFSample , que é usada para acessar os dados de áudio. Em alguns casos, uma chamada para ReadSample não gera dados; nesse caso, o ponteiro IMFSample é NULL.
- Verifica dwFlags para os seguintes sinalizadores:
- MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Esse sinalizador indica uma alteração de formato no arquivo de origem. Os arquivos WAVE não dão suporte a alterações de formato.
- MF_SOURCE_READERF_ENDOFSTREAM. Esse sinalizador indica o final do fluxo.
- Chama IMFSample::ConvertToContiguousBuffer para obter um ponteiro para um objeto buffer.
- Chama IMFMediaBuffer::Lock para obter um ponteiro para a memória do buffer.
- Grava os dados de áudio no arquivo de saída.
- Chama IMFMediaBuffer::Unlock para desbloquear o objeto buffer.
A função sai do loop quando qualquer um dos seguintes ocorre:
- O formato de fluxo é alterado.
- O final do fluxo foi atingido.
- A quantidade máxima de dados de áudio é gravada no arquivo de saída.
- Ocorrerá um erro.
Finalizar o cabeçalho do arquivo
Os valores de tamanho armazenados no cabeçalho WAVE não são conhecidos até que a função anterior seja concluída. O FixUpChunkSizes
preenche estes 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;
}
Tópicos relacionados