Поделиться через


Руководство по декодированию звука

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

Обзор

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

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

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

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

Тип данных Размер (байты) Ценность
FOURCC 4 RIFF
DWORD 4 Общий размер файла, не включая первые 8 байт
FOURCC 4 'WAVE'
FOURCC 4 'fmt'
DWORD 4 Размер следующих данных WAVEFORMATEX.
WAVEFORMATEX Меняется Заголовок формата звука.
FOURCC 4 данные
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. Для инициализации платформы Media Foundation вызывается MFStartup.
  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, включая заголовок и начало блока данных.
  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. Вызывает МВФSourceReader::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 является простой вспомогательной функцией, которая упаковывает функцию WriteFile Windows и возвращает значение 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 получает побитовое OR флагов из перечисления 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