Bagikan melalui


Tutorial: Mendekode Audio

Tutorial ini menunjukkan cara menggunakan Pembaca Sumber untuk mendekode audio dari file media dan menulis audio ke file WAVE. Tutorial ini didasarkan pada sampel Klip Audio .

Gambaran Umum

Dalam tutorial ini, Anda akan membuat aplikasi konsol yang mengambil dua argumen baris perintah: Nama file input yang berisi aliran audio, dan nama file output. Aplikasi ini membaca lima detik data audio dari file input dan menulis audio ke file output sebagai data WAVE.

Untuk mendapatkan data audio yang didekodekan, aplikasi menggunakan objek pembaca sumber. Pembaca sumber mengekspos antarmuka IMFSourceReader . Untuk menulis audio yang didekodekan ke file WAVE, aplikasi menggunakan fungsi I/O Windows. Gambar berikut mengilustrasikan proses ini.

diagram memperlihatkan pembaca sumber mendapatkan data audio dari file sumber.

Dalam bentuk yang paling sederhana, file WAVE memiliki struktur berikut:

Jenis Data Ukuran (Byte) Nilai
FOURCC 4 'RIFF'
DWORD 4 Total ukuran file, tidak termasuk 8 byte pertama
FOURCC 4 'WAVE'
FOURCC 4 'fmt '
DWORD 4 Ukuran data WAVEFORMATEX yang mengikutinya.
WAVEFORMATEX Bervariasi Header format audio.
FOURCC 4 'data'
DWORD 4 Ukuran data audio.
BYTE[] Bervariasi Data audio.

 

Catatan

FOURCC adalah DWORD yang dibentuk dengan menggabungkan empat karakter ASCII.

 

Struktur dasar ini dapat diperluas dengan menambahkan metadata file dan informasi lainnya, yang berada di luar cakupan tutorial ini.

File Header dan Pustaka

Sertakan file header berikut dalam proyek Anda:

#define WINVER _WIN32_WINNT_WIN7

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

Tautkan ke pustaka berikut ini:

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

Menerapkan wmain

Kode berikut menunjukkan fungsi titik entri untuk aplikasi.

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

Fungsi ini melakukan hal berikut:

  1. Memanggil CoInitializeEx untuk menginisialisasi pustaka COM.
  2. Memanggil MFStartup untuk menginisialisasi platform Media Foundation.
  3. Memanggil MFCreateSourceReaderFromURL untuk membuat pembaca sumber. Fungsi ini mengambil nama file input dan menerima penunjuk antarmuka IMFSourceReader .
  4. Membuat file output dengan memanggil fungsi CreateFile , yang mengembalikan handel file.
  5. Memanggil fungsi WriteWavFile yang ditentukan aplikasi. Fungsi ini mendekode audio dan menulis file WAVE.
  6. Merilis penunjuk IMFSourceReader dan handel file.
  7. Memanggil MFShutdown untuk mematikan platform Media Foundation.
  8. Memanggil CoUninitialize untuk merilis pustaka COM.

Menulis File WAVE

Sebagian besar pekerjaan terjadi dalam WriteWavFile fungsi , yang dipanggil dari 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;
}

Fungsi ini memanggil serangkaian fungsi lain yang ditentukan aplikasi:

  1. Fungsi ConfigureAudioStream menginisialisasi pembaca sumber. Fungsi ini menerima penunjuk ke antarmuka IMFMediaType , yang digunakan untuk mendapatkan deskripsi format audio yang didekodekan, termasuk laju sampel, jumlah saluran, dan kedalaman bit (bit per sampel).
  2. Fungsi WriteWaveHeader menulis bagian pertama dari file WAVE, termasuk header dan awal gugus 'data'.
  3. Fungsi CalculateMaxAudioDataSize menghitung jumlah maksimum audio untuk ditulis ke file, dalam byte.
  4. Fungsi WriteWaveData menulis data audio PCM ke file.
  5. Fungsi FixUpChunkSizes menulis informasi ukuran file yang muncul setelah nilai FOURCC 'RIFF' dan 'data' dalam file WAVE. (Nilai-nilai ini tidak diketahui sampai WriteWaveData selesai.)

Fungsi-fungsi ini ditampilkan di bagian yang tersisa dari tutorial ini.

Mengonfigurasi Pembaca Sumber

Fungsi ini ConfigureAudioStream mengonfigurasi pembaca sumber untuk mendekode aliran audio dalam file sumber. Ini juga mengembalikan informasi tentang format audio yang didekodekan.

Di Media Foundation, format media dijelaskan menggunakan objek jenis media . Objek jenis media mengekspos antarmuka IMFMediaType , yang mewarisi antarmuka IMFAttributes . Pada dasarnya, jenis media adalah kumpulan properti yang menjelaskan formatnya. Untuk informasi selengkapnya, lihat Jenis Media.

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

Fungsi melakukan ConfigureAudioStream hal berikut:

  1. Memanggil metode IMFSourceReader::SetStreamSelection untuk memilih aliran audio dan membatalkan pilihan semua aliran lainnya. Langkah ini dapat meningkatkan performa, karena mencegah pembaca sumber menahan bingkai video yang tidak digunakan aplikasi.
  2. Membuat jenis media parsial yang menentukan audio PCM. Fungsi ini membuat jenis parsial sebagai berikut:
    1. Memanggil MFCreateMediaType untuk membuat objek jenis media kosong.
    2. Mengatur atribut MF_MT_MAJOR_TYPE ke MFMediaType_Audio.
    3. Mengatur atribut MF_MT_SUBTYPE ke MFAudioFormat_PCM.
  3. Memanggil IMFSourceReader::SetCurrentMediaType untuk mengatur jenis parsial pada pembaca sumber. Jika file sumber berisi audio yang dikodekan, pembaca sumber secara otomatis memuat dekoder audio yang diperlukan.
  4. Memanggil IMFSourceReader::GetCurrentMediaType untuk mendapatkan jenis media PCM yang sebenarnya. Metode ini mengembalikan jenis media dengan semua detail format yang diisi, seperti laju sampel audio dan jumlah saluran.
  5. Memanggil IMFSourceReader::SetStreamSelection untuk mengaktifkan aliran audio.

Menulis Header File WAVE

Fungsi ini WriteWaveHeader menulis header file WAVE.

Satu-satunya MEDIA Foundation API yang dipanggil dari fungsi ini adalah MFCreateWaveFormatExFromMFMediaType, yang mengonversi jenis media menjadi struktur 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;
}

Fungsi ini WriteToFile adalah fungsi pembantu sederhana yang membungkus fungsi Windows WriteFile dan mengembalikan nilai 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;
}

Menghitung Ukuran Data Maksimum

Karena ukuran file disimpan sebagai nilai 4 byte di header file, file WAVE dibatasi hingga ukuran maksimum 0xFFFFFFFF byte—sekitar 4 GB. Nilai ini mencakup ukuran header file. Audio PCM memiliki laju bit konstan, sehingga Anda dapat menghitung ukuran data maksimum dari format audio, sebagai berikut:

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

Untuk menghindari bingkai audio parsial, ukuran dibulatkan ke perataan blok, yang disimpan dalam atribut MF_MT_AUDIO_BLOCK_ALIGNMENT .

Mendekode Audio

Fungsi ini WriteWaveData membaca audio yang didekodekan dari file sumber dan menulis ke file 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;
}

Fungsi WriteWaveData melakukan hal berikut dalam perulangan:

  1. Memanggil IMFSourceReader::ReadSample untuk membaca audio dari file sumber. Parameter dwFlags menerima bitwise ATAU bendera dari enumerasi MF_SOURCE_READER_FLAG . Parameter pSample menerima penunjuk ke antarmuka IMFSample , yang digunakan untuk mengakses data audio. Dalam beberapa kasus, panggilan ke ReadSample tidak menghasilkan data, dalam hal ini penunjuk IMFSample adalah NULL.
  2. Memeriksa dwFlags untuk bendera berikut:
    • MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Bendera ini menunjukkan perubahan format dalam file sumber. File WAVE tidak mendukung perubahan format.
    • MF_SOURCE_READERF_ENDOFSTREAM. Bendera ini menunjukkan akhir aliran.
  3. Memanggil IMFSample::ConvertToContiguousBuffer untuk mendapatkan penunjuk ke objek buffer.
  4. Memanggil IMFMediaBuffer::Lock untuk mendapatkan penunjuk ke memori buffer.
  5. Menulis data audio ke file output.
  6. Memanggil IMFMediaBuffer::Unlock untuk membuka kunci objek buffer.

Fungsi berhenti dari perulangan ketika salah satu hal berikut terjadi:

  • Format aliran berubah.
  • Akhir aliran tercapai.
  • Jumlah maksimum data audio ditulis ke file output.
  • Muncul kesalahan.

Menyelesaikan Header File

Nilai ukuran yang disimpan di header WAVE tidak diketahui sampai fungsi sebelumnya selesai. Isi FixUpChunkSizes nilai-nilai ini:

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

Tipe Media Audio

Pembaca Sumber

IMFSourceReader