Tutorial: Decodieren von Audio
In diesem Tutorial wird gezeigt, wie Sie den Quellleser verwenden, um Audio aus einer Mediendatei zu decodieren und das Audio in eine WAVE-Datei zu schreiben. Das Tutorial basiert auf dem Audioclipbeispiel .
- Übersicht
- Header- und Bibliotheksdateien
- Implementieren von wmain
- Schreiben der WAVE-Datei
- Konfigurieren des Quelllesers
- Schreiben des WAVE-Dateiheaders
- Berechnen der maximalen Datengröße
- Decodieren des Audios
- Abschließen des Dateiheaders
- Zugehörige Themen
Übersicht
In diesem Tutorial erstellen Sie eine Konsolenanwendung, die zwei Befehlszeilenargumente verwendet: Den Namen einer Eingabedatei, die einen Audiostream enthält, und den Namen der Ausgabedatei. Die Anwendung liest fünf Sekunden Audiodaten aus der Eingabedatei und schreibt das Audio als WAVE-Daten in die Ausgabedatei.
Zum Abrufen der decodierten Audiodaten verwendet die Anwendung das Quellleseobjekt. Der Quellleser macht die IMFSourceReader-Schnittstelle verfügbar. Um das decodierte Audio in die WAVE-Datei zu schreiben, verwenden die Anwendungen Windows-E/A-Funktionen. Die folgende Abbildung veranschaulicht diesen Vorgang.
In ihrer einfachsten Form hat eine WAVE-Datei die folgende Struktur:
Datentyp | Größe (Byte) | Wert |
---|---|---|
FOURCC | 4 | "RIFF" |
DWORD | 4 | Gesamtdateigröße, ohne die ersten 8 Bytes |
FOURCC | 4 | "WAVE" |
FOURCC | 4 | 'fmt ' |
DWORD | 4 | Größe der folgenden WAVEFORMATEX-Daten . |
WAVEFORMATEX | Varies | Audioformatheader. |
FOURCC | 4 | "Daten" |
DWORD | 4 | Größe der Audiodaten. |
BYTE[] | Varies | Audiodaten. |
Hinweis
Ein FOURCC ist ein DWORD , das durch Verkettung von vier ASCII-Zeichen gebildet wird.
Diese grundlegende Struktur kann durch Hinzufügen von Dateimetadaten und anderen Informationen erweitert werden, was den Umfang dieses Tutorials sprengt.
Header- und Bibliotheksdateien
Fügen Sie die folgenden Headerdateien in Ihr Projekt ein:
#define WINVER _WIN32_WINNT_WIN7
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>
Link zu den folgenden Bibliotheken:
- mfplat.lib
- mfreadwrite.lib
- mfuuid.lib
Implementieren von wmain
Der folgende Code zeigt die Einstiegspunktfunktion für die Anwendung.
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;
};
Diese Funktion führt Folgendes aus:
- Ruft CoInitializeEx auf, um die COM-Bibliothek zu initialisieren.
- Ruft MFStartup auf , um die Media Foundation-Plattform zu initialisieren.
- Ruft MFCreateSourceReaderFromURL auf, um den Quellleser zu erstellen. Diese Funktion übernimmt den Namen der Eingabedatei und empfängt einen IMFSourceReader-Schnittstellenzeiger .
- Erstellt die Ausgabedatei durch Aufrufen der CreateFile-Funktion , die ein Dateihandle zurückgibt.
- Ruft die anwendungsdefinierte WriteWavFile-Funktion auf. Diese Funktion decodiert das Audio und schreibt die WAVE-Datei.
- Gibt den IMFSourceReader-Zeiger und das Dateihandle frei.
- Ruft MFShutdown auf, um die Media Foundation-Plattform herunterzufahren.
- Ruft CoUninitialize auf , um die COM-Bibliothek freizugeben.
Schreiben der WAVE-Datei
Die meiste Arbeit erfolgt in der WriteWavFile
Funktion, die von wmain
aufgerufen wird.
//-------------------------------------------------------------------
// 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;
}
Diese Funktion ruft eine Reihe anderer anwendungsdefinierter Funktionen auf:
- Die Funktion ConfigureAudioStream initialisiert den Quellleser. Diese Funktion empfängt einen Zeiger auf die IMFMediaType-Schnittstelle , die verwendet wird, um eine Beschreibung des decodierten Audioformats abzurufen, einschließlich Samplerate, Anzahl der Kanäle und Bittiefe (Bits pro Beispiel).
- Die WriteWaveHeader-Funktion schreibt den ersten Teil der WAVE-Datei, einschließlich des Headers und des Anfangs des Datenblocks.
- Die CalculateMaxAudioDataSize-Funktion berechnet die maximale Audiomenge, die in die Datei geschrieben werden soll, in Bytes.
- Die WriteWaveData-Funktion schreibt die PCM-Audiodaten in die Datei.
- Die FixUpChunkSizes-Funktion schreibt die Dateigrößeninformationen, die nach den FOURCC-Werten "RIFF" und "data" in der WAVE-Datei angezeigt werden. (Diese Werte sind erst bekannt, wenn
WriteWaveData
sie abgeschlossen sind.)
Diese Funktionen werden in den restlichen Abschnitten dieses Tutorials gezeigt.
Konfigurieren des Quelllesers
Die ConfigureAudioStream
Funktion konfiguriert den Quellleser, um den Audiostream in der Quelldatei zu decodieren. Außerdem werden Informationen zum Format des decodierten Audios zurückgegeben.
In Media Foundation werden Medienformate mithilfe von Medientypobjekten beschrieben. Ein Medientypobjekt macht die IMFMediaType-Schnittstelle verfügbar, die die IMFAttributes-Schnittstelle erbt . Im Wesentlichen ist ein Medientyp eine Auflistung von Eigenschaften, die das Format beschreiben. Weitere Informationen finden Sie unter Medientypen.
//-------------------------------------------------------------------
// 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;
}
Die ConfigureAudioStream
Funktion führt Folgendes aus:
- Ruft die IMFSourceReader::SetStreamSelection-Methode auf, um den Audiodatenstrom auszuwählen und die Auswahl aller anderen Datenströme aufzuheben. Dieser Schritt kann die Leistung verbessern, da er verhindert, dass sich der Quellleser an Videoframes hält, die die Anwendung nicht verwendet.
- Erstellt einen partiellen Medientyp, der PCM-Audio angibt. Die Funktion erstellt den Teiltyp wie folgt:
- Ruft MFCreateMediaType auf, um ein leeres Medientypobjekt zu erstellen.
- Legt das MF_MT_MAJOR_TYPE-Attribut auf MFMediaType_Audio fest.
- Legt das MF_MT_SUBTYPE-Attribut auf MFAudioFormat_PCM fest.
- Ruft IMFSourceReader::SetCurrentMediaType auf, um den Teiltyp für den Quellleser festzulegen. Wenn die Quelldatei codiertes Audio enthält, lädt der Quellleser automatisch den erforderlichen Audiodecoder.
- Ruft IMFSourceReader::GetCurrentMediaType auf, um den tatsächlichen PCM-Medientyp abzurufen. Diese Methode gibt einen Medientyp zurück, in dem alle Formatdetails ausgefüllt sind, z. B. die Audio-Samplerate und die Anzahl der Kanäle.
- Ruft IMFSourceReader::SetStreamSelection auf, um den Audiostream zu aktivieren.
Schreiben des WAVE-Dateiheaders
Die WriteWaveHeader
Funktion schreibt den WAVE-Dateiheader.
Die einzige Media Foundation-API, die von dieser Funktion aufgerufen wird, ist MFCreateWaveFormatExFromMFMediaType, die den Medientyp in eine WAVEFORMATEX-Struktur konvertiert.
//-------------------------------------------------------------------
// 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;
}
Die WriteToFile
Funktion ist eine einfache Hilfsfunktion, die die Windows WriteFile-Funktion umschließt und einen HRESULT-Wert zurückgibt.
//-------------------------------------------------------------------
//
// 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;
}
Berechnen der maximalen Datengröße
Da die Dateigröße als 4-Byte-Wert im Dateiheader gespeichert wird, ist eine WAVE-Datei auf eine maximale Größe von 0xFFFFFFFF Bytes beschränkt – etwa 4 GB. Dieser Wert enthält die Größe des Dateiheaders. PCM-Audio hat eine konstante Bitrate, sodass Sie die maximale Datengröße aus dem Audioformat wie folgt berechnen können:
//-------------------------------------------------------------------
// 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;
}
Um partielle Audioframes zu vermeiden, wird die Größe auf die Blockausrichtung gerundet, die im attribut MF_MT_AUDIO_BLOCK_ALIGNMENT gespeichert wird.
Decodieren des Audios
Die WriteWaveData
Funktion liest decodierte Audiodaten aus der Quelldatei und schreibt in die WAVE-Datei.
//-------------------------------------------------------------------
// 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;
}
Die WriteWaveData
Funktion führt folgendes in einer Schleife aus:
- Ruft IMFSourceReader::ReadSample auf, um Audio aus der Quelldatei zu lesen. Der dwFlags-Parameter empfängt ein bitweises OR von Flags von der MF_SOURCE_READER_FLAG-Enumeration . Der pSample-Parameter empfängt einen Zeiger auf die IMFSample-Schnittstelle , die für den Zugriff auf die Audiodaten verwendet wird. In einigen Fällen generiert ein Aufruf von ReadSample keine Daten. In diesem Fall ist der IMFSample-ZeigerNULL.
- Überprüft dwFlags auf die folgenden Flags:
- MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Dieses Flag gibt eine Formatänderung in der Quelldatei an. WAVE-Dateien unterstützen keine Formatänderungen.
- MF_SOURCE_READERF_ENDOFSTREAM. Dieses Flag gibt das Ende des Datenstroms an.
- Ruft IMFSample::ConvertToContiguousBuffer auf, um einen Zeiger auf ein Pufferobjekt abzurufen.
- Ruft IMFMediaBuffer::Lock auf, um einen Zeiger auf den Pufferspeicher abzurufen.
- Schreibt die Audiodaten in die Ausgabedatei.
- Ruft IMFMediaBuffer::Unlock auf, um das Pufferobjekt zu entsperren.
Die Funktion bricht aus der Schleife aus, wenn eine der folgenden Aktionen auftritt:
- Das Streamformat ändert sich.
- Das Ende des Streams ist erreicht.
- Die maximale Menge an Audiodaten wird in die Ausgabedatei geschrieben.
- Ein Fehler tritt auf.
Abschließen des Dateiheaders
Die Größenwerte, die im WAVE-Header gespeichert sind, sind erst bekannt, wenn die vorherige Funktion abgeschlossen ist. Die FixUpChunkSizes
folgenden Werte werden ausgefüllt:
//-------------------------------------------------------------------
// 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;
}
Zugehörige Themen