Självstudie: Avkoda ljud

Den här handledningen visar hur du använder Source Reader för att dekoda ljud från en mediefil och skriva ljudet till en WAVE-fil. Självstudien är baserad på ljudklippexemplet .

Överblick

I den här självstudien skapar du ett konsolprogram som tar två kommandoradsargument: Namnet på en indatafil som innehåller en ljudström och namnet på utdatafilen. Programmet läser fem sekunders ljuddata från indatafilen och skriver ljudet till utdatafilen som WAVE-data.

För att hämta avkodade ljuddata använder programmet källläsarobjektet. Källläsaren exponerar IMFSourceReader--gränssnittet. För att skriva det avkodade ljudet till WAVE-filen använder programmen Windows I/O-funktioner. Följande bild illustrerar den här processen.

diagram som visar källläsaren som hämtar ljuddata från källfilen.

I sin enklaste form har en WAVE-fil följande struktur:

Datatyp Storlek (byte) Värde
FOURCC- 4 "RIFF"
DWORD 4 Total filstorlek, exklusive de första 8 byteen
FOURCC- 4 "WAVE"
FOURCC- 4 "fmt"
DWORD 4 Storleken på WAVEFORMATEX data som följer.
WAVEFORMATEX Varierar Ljudformat rubrik.
FOURCC- 4 "data"
DWORD 4 Storleken på ljudinformationen.
BYTE[] Varierar Ljuddata.

 

Not

En FOURCC- är en DWORD- som bildas genom att sammanfoga fyra ASCII-tecken.

 

Den här grundläggande strukturen kan utökas genom att lägga till filmetadata och annan information, vilket ligger utanför omfånget för den här självstudien.

Rubrik- och biblioteksfiler

Inkludera följande huvudfiler i projektet:

#define WINVER _WIN32_WINNT_WIN7

#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>

Länka till följande bibliotek:

  • mfplat.lib
  • mfreadwrite.lib
  • mfuuid.lib

Använda wmain

Följande kod visar startpunktsfunktionen för programmet.

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;
};

Den här funktionen gör följande:

  1. Anropar CoInitializeEx för att initiera COM-biblioteket.
  2. Anropar MFStartup för att initiera Media Foundation-plattformen.
  3. Anropar MFCreateSourceReaderFromURL för att skapa källläsaren. Den här funktionen tar namnet på indatafilen och tar emot en IMFSourceReader gränssnittspekare.
  4. Skapar utdatafilen genom att anropa funktionen CreateFile, som returnerar ett filhandtag.
  5. Anropar den programdefinierade funktionen WriteWavFile. Den här funktionen avkodar ljudet och skriver WAVE-filen.
  6. Släpper pekaren IMFSourceReader och filhandtaget.
  7. Anropar MFShutdown för att stänga av Media Foundation-plattformen.
  8. Anropar CoUninitialize för att släppa COM-biblioteket.

Skriv WAVE-filen

Det mesta av arbetet sker i funktionen WriteWavFile, som anropas från 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;
}

Den här funktionen anropar en serie andra programdefinierade funktioner:

  1. Funktionen ConfigureAudioStream initierar källläsaren. Den här funktionen tar emot en pekare till IMFMediaType--gränssnittet, som används för att få en beskrivning av det avkodade ljudformatet, inklusive exempelfrekvens, antal kanaler och bitdjup (bitar per exempel).
  2. Funktionen WriteWaveHeader skriver den första delen av WAVE-filen, inklusive rubriken och början av datasegmentet.
  3. Funktionen CalculateMaxAudioDataSize beräknar den maximala mängden ljud som ska skrivas till filen i byte.
  4. Funktionen WriteWaveData skriver PCM-ljuddata till filen.
  5. Funktionen FixUpChunkSizes skriver filstorleksinformationen som visas efter värdena "RIFF" och "data" FOURCC i WAVE-filen. (Dessa värden är inte kända förrän WriteWaveData har slutförts.)

De här funktionerna visas i de återstående avsnitten i den här självstudien.

Konfigurera källläsaren

Funktionen ConfigureAudioStream konfigurerar källläsaren att avkoda ljudströmmen i källfilen. Den returnerar också information om formatet för det avkodade ljudet.

I Media Foundation beskrivs medieformat med hjälp av medietyp objekt. Ett medietypobjekt exponerar IMFMediaType--gränssnittet, som ärver IMFAttributes-gränssnittet. I princip är en medietyp en samling egenskaper som beskriver formatet. Mer information finns i Medietyper.

//-------------------------------------------------------------------
// 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;
}

Funktionen ConfigureAudioStream gör följande:

  1. Anropar metoden IMFSourceReader::SetStreamSelection för att välja ljudströmmen och avmarkera alla andra strömmar. Det här steget kan förbättra prestandan eftersom det hindrar källläsaren från att hålla fast vid videorutor som programmet inte använder.
  2. Skapar en delvis medietyp som anger PCM-ljud. Funktionen skapar den partiella typen enligt följande:
    1. Anropar MFCreateMediaType för att skapa ett tomt medietypsobjekt.
    2. Anger attributet MF_MT_MAJOR_TYPE till MFMediaType_Audio.
    3. Anger attributet MF_MT_SUBTYPE till MFAudioFormat_PCM.
  3. Anropar IMFSourceReader::SetCurrentMediaType för att ange den partiella typen på källläsaren. Om källfilen innehåller kodat ljud läser källläsaren automatiskt in den nödvändiga ljudkodaren.
  4. Anropar IMFSourceReader::GetCurrentMediaType för att hämta den faktiska PCM-medietypen. Den här metoden returnerar en medietyp med all formatinformation ifylld, till exempel ljudexempelfrekvensen och antalet kanaler.
  5. Anropar IMFSourceReader::SetStreamSelection för att aktivera ljudströmmen.

Skriv WAVE-filhuvudet

Funktionen WriteWaveHeader skriver WAVE-filhuvudet.

Det enda Media Foundation-API som anropas från den här funktionen är MFCreateWaveFormatExFromMFMediaType, som konverterar medietypen till en WAVEFORMATEX- struktur.

//-------------------------------------------------------------------
// 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;
}

Funktionen WriteToFile är en enkel hjälpfunktion som omsluter funktionen Windows WriteFile och returnerar ett HRESULT- värde.

//-------------------------------------------------------------------
//
// 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;
}

Beräkna den maximala datastorleken

Eftersom filstorleken lagras som ett värde på 4 byte i filhuvudet är en WAVE-fil begränsad till en maximal storlek på 0xFFFFFFFF byte – cirka 4 GB. Det här värdet innehåller filhuvudets storlek. PCM-ljud har en konstant bithastighet, så du kan beräkna den maximala datastorleken från ljudformatet enligt följande:

//-------------------------------------------------------------------
// 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;
}

För att undvika partiella ljudramar avrundas storleken till blockjusteringen, vilken lagras i attributet MF_MT_AUDIO_BLOCK_ALIGNMENT.

Avkoda ljudet

Funktionen WriteWaveData läser avkodat ljud från källfilen och skriver till WAVE-filen.

//-------------------------------------------------------------------
// 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;
}

Funktionen WriteWaveData gör följande i en loop:

  1. Anropar IMFSourceReader::ReadSample för att läsa ljud från källfilen. Parametern dwFlags tar emot ett bitvis ELLER av flaggor från uppräkningen MF_SOURCE_READER_FLAG. Parametern pSample tar emot en pekare till IMFSample--gränssnittet, som används för att komma åt ljuddata. I vissa fall genererar ett anrop till ReadSample inte data, i sådant fall är IMFSample pekare NULL.
  2. Kontrollerar dwFlags efter följande flaggor:
    • MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Den här flaggan anger en formatändring i källfilen. WAVE-filer stöder inte formatändringar.
    • MF_SOURCE_READERF_ENDOFSTREAM. Den här flaggan anger slutet på strömmen.
  3. Anropar IMFSample::ConvertToContiguousBuffer för att hämta en pekare till ett buffertobjekt.
  4. Anropar IMFMediaBuffer::Lås för att få en pekare till buffertminnet.
  5. Skriver ljuddata till utdatafilen.
  6. Anropar IMFMediaBuffer::Lås upp för att låsa upp buffertobjektet.

Funktionen bryter ut ur loopen när något av följande inträffar:

  • Flödesformatet ändras.
  • Vi har nått slutet på flödet.
  • Den maximala mängden ljuddata skrivs till utdatafilen.
  • Ett fel inträffar.

Slutför filrubriken

De storleksvärden som lagras i WAVE-huvudet är inte kända förrän den tidigare funktionen har slutförts. FixUpChunkSizes fyller i följande värden:

//-------------------------------------------------------------------
// 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;
}

ljudmedietyper

källläsare

IMFSourceReader