Руководство. Декодирование звука

В этом руководстве показано, как использовать средство чтения исходного кода для декодирования звука из файла мультимедиа и записи звука в ФАЙЛ WAVE. Руководство основано на примере аудиоклипа .

Общие сведения

В этом руководстве вы создадите консольное приложение, которое принимает два аргумента командной строки: имя входного файла, содержащего звуковой поток, и имя выходного файла. Приложение считывает пять секунд звуковых данных из входного файла и записывает звук в выходной файл в виде данных WAVE.

Чтобы получить декодированные звуковые данные, приложение использует исходный объект средства чтения. Средство чтения источника предоставляет интерфейс IMFSourceReader . Для записи декодированного звука в файл WAVE приложения используют функции ввода-вывода Windows. Этот процесс показан на следующем рисунке.

схема, показывающая, как средство чтения исходного кода получает звуковые данные из исходного файла.

В простейшей форме файл WAVE имеет следующую структуру:

Тип данных Размер (байт) Значение
FOURCC 4 'RIFF'
DWORD 4 Общий размер файла, не включая первые 8 байт
FOURCC 4 'WAVE'
FOURCC 4 'fmt '
DWORD 4 Размер следующих данных WAVEFORMATEX .
WAVEFORMATEX Различается Заголовок аудиоформата.
FOURCC 4 "data"
DWORD 4 Размер звуковых данных.
BYTE[] Различается Звуковые данные.

 

Примечание

FOURCC — это DWORD, сформированный путем объединения четырех символов ASCII.

 

Эту базовую структуру можно расширить, добавив метаданные файла и другие сведения, которые выходят за рамки область этого руководства.

Файлы заголовков и библиотек

Включите в проект следующие файлы заголовков:

#define WINVER _WIN32_WINNT_WIN7

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

Ссылка на следующие библиотеки:

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

Реализация wmain

В следующем коде показана функция точки входа для приложения.

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

Эта функция выполняет следующие действия:

  1. Вызывает CoInitializeEx для инициализации библиотеки COM.
  2. Вызывает MFStartup для инициализации платформы Media Foundation.
  3. Вызывает MFCreateSourceReaderFromURL для создания исходного средства чтения. Эта функция принимает имя входного файла и получает указатель интерфейса IMFSourceReader .
  4. Создает выходной файл, вызывая функцию CreateFile , которая возвращает дескриптор файла.
  5. Вызывает определяемую приложением функцию WriteWavFile . Эта функция декодирует звук и записывает ФАЙЛ WAVE.
  6. Освобождает указатель IMFSourceReader и дескриптор файла.
  7. Вызывает MFShutdown , чтобы завершить работу платформы Media Foundation.
  8. Вызывает CoUninitialize , чтобы освободить библиотеку COM.

Запись файла WAVE

Большая часть работы выполняется в WriteWavFile функции , которая вызывается из 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;
}

Эта функция вызывает ряд других функций, определяемых приложением:

  1. Функция ConfigureAudioStream инициализирует средство чтения исходного кода. Эта функция получает указатель на интерфейс IMFMediaType , который используется для получения описания декодированного аудиоформата, включая частоту выборки, количество каналов и битовую глубину (биты на выборку).
  2. Функция WriteWaveHeader записывает первую часть файла WAVE, включая заголовок и начало блока data.
  3. Функция CalculateMaxAudioDataSize вычисляет максимальный объем звука для записи в файл в байтах.
  4. Функция WriteWaveData записывает звуковые данные PCM в файл.
  5. Функция FixUpChunkSizes записывает сведения о размере файла, которые отображаются после значений "RIFF" и "data" FOURCC в wave-файле. (Эти значения не известны до WriteWaveData завершения.)

Эти функции показаны в остальных разделах этого руководства.

Настройка средства чтения источника

Функция ConfigureAudioStream настраивает средство чтения исходного кода для декодирования аудиопотока в исходном файле. Он также возвращает сведения о формате декодированного звука.

В Media Foundation форматы мультимедиа описываются с помощью объектов типа мультимедиа . Объект типа мультимедиа предоставляет интерфейс IMFMediaType , который наследует интерфейс IMFAttributes . По сути, тип носителя — это коллекция свойств, описывающих формат. Дополнительные сведения см. в разделе Типы мультимедиа.

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

Функция ConfigureAudioStream выполняет следующие действия:

  1. Вызывает метод IMFSourceReader::SetStreamSelection для выбора аудиопотока и отмены выбора всех остальных потоков. Этот шаг может повысить производительность, так как он не позволяет средству чтения-источника удерживать видеокадры, которые приложение не использует.
  2. Создает частичный тип носителя, указывающий звук PCM. Функция создает разделяемый тип следующим образом:
    1. Вызывает MFCreateMediaType для создания пустого объекта типа мультимедиа.
    2. Задает атрибуту MF_MT_MAJOR_TYPEзначение MFMediaType_Audio.
    3. Задает атрибуту MF_MT_SUBTYPEзначение MFAudioFormat_PCM.
  3. Вызывает IMFSourceReader::SetCurrentMediaType , чтобы задать разделяемый тип в исходном средстве чтения. Если исходный файл содержит закодированный звук, средство чтения исходного кода автоматически загружает необходимый декодер звука.
  4. Вызывает IMFSourceReader::GetCurrentMediaType , чтобы получить фактический тип носителя PCM. Этот метод возвращает тип мультимедиа со всеми заполненными сведениями о формате, такими как частота дискретизации звука и количество каналов.
  5. Вызывает IMFSourceReader::SetStreamSelection , чтобы включить аудиопоток.

Запись заголовка файла WAVE

Функция WriteWaveHeader записывает заголовок файла WAVE.

Единственным API Media Foundation, вызываемым из этой функции, является MFCreateWaveFormatExFromMFMediaType, который преобразует тип мультимедиа в структуру 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;
}

Функция WriteToFile — это простая вспомогающая функция, которая создает оболочку для функции Windows WriteFile и возвращает значение 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;
}

Вычисление максимального размера данных

Так как размер файла хранится в заголовке файла в виде 4-байтового значения, размер ФАЙЛА WAVE ограничен максимальным размером 0xFFFFFFFF байтов — примерно 4 ГБ. Это значение включает размер заголовка файла. Звук PCM имеет постоянную скорость передачи, поэтому вы можете вычислить максимальный размер данных из аудиоформата следующим образом:

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

Чтобы избежать частичных звуковых кадров, размер округляется до выравнивания блока, который хранится в атрибуте MF_MT_AUDIO_BLOCK_ALIGNMENT .

Декодирование звука

Функция WriteWaveData считывает декодированные звуки из исходного файла и записывает данные в файл 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;
}

Функция WriteWaveData выполняет следующие действия в цикле:

  1. Вызывает IMFSourceReader::ReadSample для чтения звука из исходного файла. Параметр dwFlags получает побитовое ИЛИ флагов из перечисления MF_SOURCE_READER_FLAG . Параметр pSample получает указатель на интерфейс IMFSample , который используется для доступа к звуковым данным. В некоторых случаях вызов ReadSample не создает данные. В этом случае указатель IMFSample имеет значение NULL.
  2. Проверяет dwFlags на наличие следующих флагов:
    • MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED. Этот флаг указывает на изменение формата в исходном файле. Файлы WAVE не поддерживают изменения формата.
    • MF_SOURCE_READERF_ENDOFSTREAM. Этот флаг указывает конец потока.
  3. Вызывает IMFSample::ConvertToContiguousBuffer , чтобы получить указатель на объект буфера.
  4. Вызывает функцию IMFMediaBuffer::Lock , чтобы получить указатель на буферную память.
  5. Записывает звуковые данные в выходной файл.
  6. Вызывает IMFMediaBuffer::Unlock , чтобы разблокировать объект буфера.

Функция выходит из цикла, когда происходит любое из следующих действий:

  • Формат потока изменяется.
  • Достигнут конец потока.
  • Максимальный объем звуковых данных записывается в выходной файл.
  • Происходит ошибка.

Завершение заголовка файла

Значения размера, хранящиеся в заголовке WAVE, неизвестны до завершения предыдущей функции. Заполняет FixUpChunkSizes следующие значения:

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

Типы аудиоданных

Средство чтения исходного кода

IMFSourceReader