Воспроизведение WAV файлов на С++ DX DirectSound

Загрузить пример ЗДЕСЬ. Для компиляции вам потребуется Visual Studio C++ и DirectX SDK June 2010, проект для VS2005, но вы можете попробовать скомпилировать данный проект на VS2019 и Win10 SDK где есть DirectSound.

У WAV файла есть заголовок с инофрмацией о звуковых данных, которые храняться в файле. Сначала надо прочитать заголовок, потом сами данные. В заголовке WAV-файла типично содержится следующие данные (первые 44 байта для стандартного PCM-аудио), в этом заголовке data размещено по смещению 36 байт:

WAV file header

Если по смещению 36 байт в заголовке размещено fact - то содержимое после 36 байта другое чем в таблице.

Начиная с offset 36 в этих 4 байтах может храниться либо MAKEFOURCC('d','a','t','a') либо MAKEFOURCC('f','a','c','t'). В примере кода есть условие if с переходом на нужный блок.

Блок fmt идет начиная с 20 байта.

Подробная структура до блока fmt:

Блок fmt:

С offset 20 начинается блок данных fmt, который содержит параметры аудио.

fmt block of wav file

Где начинается блок data?

После блока fmt, начиная с offset 36 (или позже, если fmt больше 16 байт), начинается следующий блок, обычно data.

fmt block of wav file

Если у вас расширенный заголовок WAV (например, для форматов с компрессией или дополнительными метаданными), размер блока fmt может быть больше 16 байт. Тогда блок data будет сдвинут дальше. Всегда читайте размер блока fmt (offset 16) и используйте его для определения начала следующего блока.

Назначение блока fmt:

Блок fmt описывает формат аудио, например:

Если вместо fmt стоит другой идентификатор, например LIST, это указывает на блок метаданных, а не формат аудио.

WAVEFORMATEX — это структура, используемая в Windows для описания формата звука в WAV-файлах или для взаимодействия с аудиодрайверами, например в DirectSound или WinMM. Эта структура предоставляет полную информацию о формате аудиоданных.


	typedef struct {
	    WORD  wFormatTag;         // Формат аудио (например, PCM = 1)
	    WORD  nChannels;          // Количество каналов (1 = моно, 2 = стерео)
	    DWORD nSamplesPerSec;     // Частота дискретизации (например, 44100 Гц)
	    DWORD nAvgBytesPerSec;    // Средняя скорость передачи данных (байт/сек)
	    WORD  nBlockAlign;        // Выравнивание блока (байт на семпл для всех каналов)
	    WORD  wBitsPerSample;     // Количество бит на семпл (например, 8 или 16)
	    WORD  cbSize;             // Размер дополнительных данных (0 для PCM)
	} WAVEFORMATEX;

Поля структуры:

  1. wFormatTag - Указывает аудиоформат.Наиболее часто используется значение: WAVE_FORMAT_PCM (1) — линейное некомпрессированное аудио (PCM). Для других форматов (например, сжатого звука), значение будет отличаться.
  2. nChannels - Количество каналов: 1 — моно. 2 — стерео.
  3. nSamplesPerSec - Частота дискретизации в герцах (Гц), например: 44100 для стандартного качества CD. 22050 для среднего качества. 11025 для низкого качества.
  4. nAvgBytesPerSec - Средняя скорость передачи данных, рассчитывается как:
  5. 
    	nAvgBytesPerSec = nSamplesPerSec × nChannels × (wBitsPerSample / 8)
    
    

    Пример: для стерео, 16 бит, 44100 Гц

    
    	nAvgBytesPerSec = 44100 × 2 × (16 / 8) = 176400 байт/сек.
    
    
  6. nBlockAlign - Количество байтов на один аудиосемпл для всех каналов, рассчитывается как:
  7. 
    	nBlockAlign = nChannels × (wBitsPerSample / 8)
    
    

    Пример: для стерео, 16 бит:

    
    	nBlockAlign = 2 × (16 / 8) = 4 байта.
    
    

    Все аудиоданные должны быть кратны этому значению.

  8. wBitsPerSample - Количество бит на семпл для каждого канала. Пример:
  9. 
    	8 для 8-битного аудио (неподписанный целый).
    
    	16 для 16-битного аудио (подписанный целый).
    
    
  10. cbSize - Размер дополнительных данных (только для расширенных форматов, например, компрессированного аудио). Для PCM-аудио это значение равно 0

Пример заполнения структуры:

Для стандартного WAV-файла (моно, 16 бит, 44100 Гц):


	WAVEFORMATEX waveFormat;
	waveFormat.wFormatTag = WAVE_FORMAT_PCM;       // Формат PCM
	waveFormat.nChannels = 1;                      // Моно
	waveFormat.nSamplesPerSec = 44100;             // Частота дискретизации 44.1 кГц
	waveFormat.nAvgBytesPerSec = 44100 * 1 * 2;    // Байтовая скорость = 88200 байт/сек
	waveFormat.nBlockAlign = 1 * (16 / 8);         // Выравнивание блока = 2 байта
	waveFormat.wBitsPerSample = 16;                // 16 бит на семпл
	waveFormat.cbSize = 0;                         // Нет дополнительных данных

Применение:

Пример использования с DirectSound:


	LPDIRECTSOUNDBUFFER pBuffer;
	DSBUFFERDESC desc = {0};
	WAVEFORMATEX waveFormat = {0};

	waveFormat.wFormatTag = WAVE_FORMAT_PCM;
	waveFormat.nChannels = 2;                   // Стерео
	waveFormat.nSamplesPerSec = 44100;          // 44.1 кГц
	waveFormat.nAvgBytesPerSec = 44100 * 2 * 2; // 176400 байт/сек
	waveFormat.nBlockAlign = 2 * 2;             // 4 байта на блок
	waveFormat.wBitsPerSample = 16;             // 16 бит
	waveFormat.cbSize = 0;

	desc.dwSize = sizeof(DSBUFFERDESC);
	desc.dwFlags = DSBCAPS_CTRLVOLUME;
	desc.dwBufferBytes = 176400;                // 1 секунда звука
	desc.lpwfxFormat = &waveFormat;

	HRESULT hr = g_DirectSound->CreateSoundBuffer(&desc, &pBuffer, NULL);
	if (FAILED(hr)) {
	    std::cerr << "Ошибка создания звукового буфера!" << std::endl;
	}


Для нестандартных форматов (например, сжатого звука) используется расширенная версия WAVEFORMATEX, которая включает дополнительные данные в поле cbSize. Если работаете с нестандартными форматами, лучше использовать WAVEFORMATEXTENSIBLE.

Теперь по поводу смещения 36 байт от начала файла. Если на 36 байте вместо data в заголовке WAV-файла стоит fact, это означает, что перед блоком аудиоданных присутствует информационный блок fact. Этот блок обычно используется в сжатых WAV-файлах (например, формата ADPCM) для хранения дополнительной информации о данных.

Что такое блок fact?

fact (FourCC): Указывает на блок метаданных. В формате PCM (не сжатый звук) этот блок обычно отсутствует, но в сжатых форматах (например, ADPCM) блок fact присутствует и содержит полезную информацию, такую как количество оригинальных семплов перед сжатием.

Структура блока fact:

Если на offset 36 вместо data находится fact, структура будет выглядеть так:

fact block of wav file

Для сжатого WAV-файла, содержащего блок fact, можно встретить следующее:

  1. На offset 36:
  2. 
    	'f' 'a' 'c' 't' (ASCII 'fact', hex: 0x66616374)
    
    
  3. На offset 40:
  4. Размер блока (например, 4 для обычного fact)

  5. На offset 44:
  6. Количество семплов до сжатия (например, 44100, если файл содержит 1 секунду звука с частотой 44100 Гц).

  7. Следующий блок будет data, указывающий на начало аудиоданных:
  8. Offset 48 (или больше, в зависимости от размера fact):

    
    	'd' 'a' 't' 'a' (ASCII 'data')
    
    	Размер аудиоданных.
    
    	Аудиоданные.
    
    

Что делать, если встречается fact?

Если файл содержит блок fact, его нужно пропустить перед обработкой аудиоданных. Это делается так:

  1. Считать FourCC (fact).
  2. Считать размер блока (dwFactSize).
  3. Пропустить блок размером dwFactSize и перейти к следующему FourCC (обычно это data).

Блок fact обязателен для сжатых форматов WAV (например, ADPCM) и не встречается в файлах PCM.

Размер данных внутри блока fact всегда кратен 4 байтам.

Пропускать ненужные блоки (например, fact, LIST и другие) — стандартная практика при работе с WAV.

PCM (Pulse Code Modulation) — это метод цифрового представления звука. Он заключается в том, что аналоговый звуковой сигнал периодически измеряется (дискретизация) и преобразуется в цифровые значения с определенной точностью (квантизация). Это один из наиболее распространённых форматов звуковых данных, особенно для несжатых аудиофайлов, таких как WAV.

Особенности PCM:

  1. Несжатый формат:PCM не использует сжатие, поэтому данные занимают больше места, но сохраняют высокое качество.
  2. Прямое представление звука:Аудиосигнал хранится как последовательность цифровых амплитуд, напрямую соответствующих аналоговому сигналу.
  3. Пример аудиофайлов на основе PCM: WAV (Microsoft). AIFF (Apple). RAW (без заголовка).

Помимо PCM, существуют форматы, которые используют сжатие (с потерями или без) FLAC, MP3, ADPCM.

Если ваш WAV-файл содержит блок fact по смещению 36 от начала файла, это указывает на то, что файл использует сжатый формат данных, а не чистый PCM. В формате PCM блок fact обычно отсутствует, так как PCM не нуждается в дополнительной информации для декодирования.

Почему в WAV появляется блок fact?

  1. Сжатый формат WAV: Если аудиоданные в файле сжаты (например, с использованием ADPCM или другого метода), требуется дополнительная информация для декодирования этих данных. Блок fact содержит метаинформацию, такую как количество семплов в исходных несжатых данных.
  2. Указание на сжатый формат:Поле wFormatTag в заголовке fmt блока указывает на используемый аудиоформат. Для PCM значение wFormatTag равно 1.Для сжатых форматов значение wFormatTag будет отличным от 1 (например, 2 для ADPCM).

Пример структуры WAV-файла, если он содержит fact:

fact block of wav file

Как определить, что WAV-файл не PCM?

  1. Проверить wFormatTag в fmt: Считать заголовок fmt и проверить значение поля wFormatTag. Если wFormatTag != 1, файл использует сжатый формат (например, ADPCM).
  2. Наличие блока fact:PCM-файлы не содержат блок fact, так как у них нет необходимости указывать количество несжатых семплов.Если блок fact присутствует, файл, скорее всего, сжат.

Если WAV-файл содержит блок fact, это указывает на то, что он использует сжатый формат, например ADPCM. Проверьте значение wFormatTag в заголовке fmt, чтобы точно определить тип формата. Если файл сжат, аудиоданные нужно декодировать перед воспроизведением.

Что делать с файлом, содержащим блок fact?

  1. Пропустите блок fact: Блок fact нужен для сжатых данных, но вам нужно перейти к блоку data, чтобы начать чтение аудиоданных.
  2. Декодируйте данные, если они сжаты: Для ADPCM и других сжатых форматов потребуется декодировать данные перед воспроизведением.
  3. DirectSound 8 может автоматически декодировать аудиоданные, если вы загружаете их в буфер, поддерживающий воспроизведение сжатых форматов, таких как ADPCM. Однако для этого нужно правильно настроить и создать буфер, указав, что данные сжаты..

    Некоторые старые или специфичные аудиоформаты, которые начинают с PCM, но могут поддерживать дополнительные метаданные, могут добавлять блок fact, даже если формат звука остаётся PCM. Это иногда делается для совместимости с более поздними форматами, такими как сжатые форматы или вариации форматов. Это значит что в заголовке wFormatTag может быть равен 1 что значит PCM но такой файл может содержать блок fact что значит что файл сжатый (даже если он не сжатый).

Роль Primary Buffer в DirectSound:

Primary buffer — это основной буфер, который управляет выводом звука на устройство воспроизведения. Он управляет настройками устройства, такими как уровень совместимости, частота дискретизации и другие параметры. На самом деле, вы не загружаете аудио-данные в primary buffer, так как этот буфер используется только для управления устройством вывода.

Secondary buffers — это те буферы, в которые загружаются данные звуковых файлов (например, WAV) и которые затем воспроизводятся. Secondary buffer получает данные для воспроизведения и непосредственно используется для вывода звука.

Почему мы создаем primary buffer:

Обязательность: При работе с DirectSound, для корректной работы системы необходимо создать primary buffer (даже если мы не будем в нем хранить данные для воспроизведения). Это нужно для того, чтобы установить уровень совместимости (кооперативный уровень) с устройством воспроизведения. Без этого шага DirectSound не сможет корректно работать с устройством вывода.

Настройка уровня совместимости: Важнейшая роль primary buffer — это установка кооперативного уровня с помощью SetCooperativeLevel. Это нужно для того, чтобы программа могла контролировать приоритет вывода звука на устройство. Например, если у вас несколько приложений пытаются использовать звук одновременно, этот буфер задает, как ваше приложение будет взаимодействовать с системой (например, приоритетное или фоновое воспроизведение).

Primary buffer необходим для инициализации DirectSound, но его непосредственное использование для воспроизведения звука не требуется. Поэтому если в вашем случае нет необходимости в его использовании, его можно безопасно удалить из кода.

В DirectSound можно создать primary buffer (первичный буфер) с одними параметрами, а воспроизводимые звуки (secondary buffers) могут использовать другие параметры.

  1. Primary Buffer и его роль
  2. Primary buffer управляет тем, как DirectSound микширует и выводит звук на аудиоустройство. Его формат (частота дискретизации, глубина, число каналов) задает, в каком формате аудиоустройство будет воспроизводить микшированный звук. Все вторичные буферы (secondary buffers), которые вы создаете и проигрываете, будут автоматически преобразованы (пересэмплированы), если их формат отличается от формата primary buffer.

  3. Secondary Buffers
  4. Вы можете задавать индивидуальные параметры для каждого вторичного буфера. Например, один буфер может быть стерео с 44.1 кГц, а другой — моно с 22.05 кГц. DirectSound автоматически преобразует их в формат primary buffer, если это необходимо.

  5. Производительность и качество
  6. Если форматы secondary buffers отличаются от primary buffer, DirectSound выполнит автоматическое преобразование (resampling). Это может немного увеличить нагрузку на процессор и в некоторых случаях ухудшить качество звука (в зависимости от алгоритма преобразования). Чтобы избежать преобразований, рекомендуется использовать один и тот же формат для primary buffer и secondary buffers.

Если все звуки в вашей игре или приложении имеют одинаковый формат, настройте primary buffer и secondary buffers с этим форматом, чтобы избежать лишних преобразований. Если нужно воспроизводить звуки с разными форматами, будьте готовы к возможным задержкам или снижению качества из-за пересэмплирования.

Можно задавать разные параметры для primary buffer и secondary buffers, и DirectSound автоматически выполнит преобразование. Однако для лучшей производительности и качества звука желательно, чтобы их форматы совпадали.

Дублирование буфера с помощью IDirectSound8::DuplicateSoundBuffer() имеет несколько важных применений.

Что такое дублирование буфера?

Метод DuplicateSoundBuffer создает новую копию существующего буфера, которая: Делит один и тот же аудиоданные (не создает копию содержимого, а ссылается на оригинальный аудиобуфер). Получает собственный независимый объект буфера, который можно управлять отдельно от исходного.

Зачем дублировать буфер?

  1. Одновременное воспроизведение одного и того же звука
  2. Если вы хотите воспроизвести один и тот же звук (например, звук шагов, выстрелов, или любого другого короткого эффекта) несколько раз одновременно, дублирование необходимо. Почему? В DirectSound один буфер не может быть одновременно воспроизведен несколько раз. Если вы вызовете Play для одного буфера до завершения его воспроизведения, вызов перезапустит воспроизведение с начала. Чтобы этого избежать, создается копия буфера. Каждая копия может быть запущена независимо. Пример: Звук шагов. Левый и правый шаги воспроизводятся с небольшой задержкой, но если звук запускается из одного буфера, второй звук перезапустит первый.

  3. Экономия памяти
  4. Дублирование буфера не создает копию звуковых данных. Вместо этого оно создает ссылку на уже загруженные данные, что экономит память. Это особенно полезно для коротких звуковых эффектов, которые часто воспроизводятся.

  5. Независимое управление
  6. Дублированные буферы имеют свои собственные настройки: Громкость (SetVolume) Частота (SetFrequency) Панорама (SetPan) Каждую копию можно воспроизводить, останавливать или управлять ею независимо от оригинала или других копий.

Если вы часто воспроизводите один и тот же звук одновременно, используйте DuplicateSoundBuffer для создания независимых копий. Убедитесь, что вы освобождаете каждый дублированный буфер после завершения его использования, чтобы избежать утечек памяти.

Почему дублировать буфер обязательно? В DirectSound каждый IDirectSoundBuffer объект поддерживает только одно воспроизведение одновременно. Дублирование позволяет запускать звук в нескольких местах одновременно, что делает это ключевой частью разработки игр и мультимедийных приложений.

Что такое Панорама (Pan) - Панорама (Pan) в контексте аудио — это параметр, который определяет, как звук распределяется между левым и правым каналами в стереосистеме. Этот параметр позволяет "разместить" звук в стереополе, создавая эффект, что звук исходит из определенного направления.

Как работает панорама

Центр (0): Если панорама установлена в центр, громкость звука равномерно распределяется между левым и правым каналами. Звук воспринимается как идущий прямо перед слушателем. Смещение влево (отрицательные значения): Чем больше значение панорамы сдвигается влево, тем громче звук становится в левом канале и тише в правом. Смещение вправо (положительные значения): Аналогично, при сдвиге вправо звук становится громче в правом канале и тише в левом.

В DirectSound параметр панорамы задается через метод SetPan() объекта звукового буфера (IDirectSoundBuffer). Диапазон значений: DSBPAN_LEFT (-10,000): Полностью в левом канале. DSBPAN_RIGHT (+10,000): Полностью в правом канале. 0: Центр, равномерное распределение между каналами.

Pitch (высота звука) — это параметр, определяющий частоту воспроизведения аудиоданных. Он отвечает за восприятие звука как более высокого или более низкого. Изменение высоты звука связано с изменением скорости воспроизведения аудиодорожки.

Как работает Pitch?

Увеличение высоты звука: Увеличение частоты воспроизведения (pitch) ускоряет аудио. Это делает звук выше, но может уменьшить его длительность. Например, частота воспроизведения увеличивается с 44100 Гц до 88200 Гц, и звук кажется "высоким". Уменьшение высоты звука: Уменьшение частоты воспроизведения замедляет аудио. Это делает звук ниже и увеличивает его длительность. Например, частота воспроизведения уменьшается с 44100 Гц до 22050 Гц, и звук кажется "низким".

В DirectSound параметр высоты звука контролируется с помощью метода SetFrequency у объекта буфера (IDirectSoundBuffer). Оригинальная частота: Исходная частота звука задается в структуре WAVEFORMATEX при создании буфера (поле nSamplesPerSec). Например, 44100 Гц — стандартная частота для CD-качества звука. Диапазон частот: Для большинства звуковых карт диапазон частот составляет от DSBFREQUENCY_MIN (200 Гц) до DSBFREQUENCY_MAX (100,000 Гц). Стандартная частота звука, с которой он был записан, равна DSBFREQUENCY_ORIGINAL.