Просто и ясно
о формате WAVE файла.
----------------------------------
Приведенной информации вполне
достаточно для работы с PCM WAVE
файлами (8/16 бит, моно/стерео)
WAVE файлы являются подмножеством
файлов RIFF формата (Resource
Interchange File Format), разработанного для
хранения ресурсов
мультимедиа. Об этом формате надо
знать совсем немного. Основной
элемент RIFF файла - т.н. чанк (chunk),
имеющий структуру
typedef struct
{
DWORD ckID; // Идентификатор чанка,
служит для опознания чанка
DWORD ckSize; // Размер чанка (без ckID &
cdSize) в байтах
BYTE ckData[ckSize];// Данные
} CK;
Основные типы чанков имеют
идентификаторы "RIFF" и "LIST"
и могут состоять
из вложенных чанков (субчанков).
Мы рассмотрим наиболее простой
случай WAVE файла, состоящего из
одного
лишь RIFF-чанка, содержащего WAVE-форму
(WAVE-form). Честно говоря, я ни разу
не видел wave файла, содержащего
более одного WAVE-чанка, поэтому мы
рассмотрим именно файл с одним-единственным
WAVE-чанком.
WAVE-форма
----------
WAVE-форма наиболее простой
категории - PCM (см. ниже) имеет
следующий вид:
<WAVE-форма> = 'WAVE' + <fmt-чанк> +
<data-чанк> , где
'WAVE' - просто сигнатура WAVE-формы
<fmt-чанк> - чанк с информацией о
звуковом сигнале
<data-чанк> - чанк с собственно
сигналом
<fmt-чанк> = 'fmt ' + <ckSize> +
<WaveFormat> + <fmt-specific> , где
'fmt ' - сигнатура fmt-чанка
<ckSize> - его размер
<WaveFormat> - структура WaveFormat(mmsystem.h),
описанная ниже
<fmt-specific> - структура с
дополнительной информацией о
формате,
имеет переменную длину и зависит от
wFormatCategory (см. ниже). В случае с PCM
удобно
пользоваться структурой WaveFormatEx
(mmsystem.h),
объединяющей в себе WaveFormat и два
поля из fmt-specific.
Документация по Win32 SDK утверждает,
что WaveFormatEx
будет работать для ВСЕХ не-PCM
форматов, что идет вразрез
с утверждением MM Programmer`s Reference о
переменной
длине fmt-specific. Так что вопрос с не-PCM
форматами
мне пока не ясен.
<data-чанк> = 'data' + <ckSize> + <собственно
сигнал> , где
'data' - сигнатура data-чанка
<ckSize> - его размер
<собственно сигнал> -
последовательность байт,
описывающая сигнал
(см. Формат данных PCM)
WaveFormat
----------
Структура WaveFormat имеет вид:
typedef struct
{
WORD wFormatTag; // Категория формата
WORD nChannels; // Число каналов
DWORD nSamplesPerSec; // Частота
дискретизации
DWORD nAvgBytesPerSec; // Байт в секунду
WORD nBlockAlign; // Выравнивание данных в
data-чанке
} WaveFormat;
Рассмотрим эту структуру подробнее.
wFormatTag Категория формата (неудачный
перевод: калька format category).
От этого значения зависят значения
остальных полей этой
структуры, структура <fmt-specific> и
data-чанка. Существует
несколько категорий формата; самая
доступная - PCM (Pulse
Code Modulation) имеет wFormatTag = 1.
nChannels 1 - моно, 2-стерео, о большем
числе каналов документация
умалчивает.
nSamplesPerSec Частота дискретизации (число
сэмплов в секунду).
nAvgBytesPerSec Среднее число байт в
секунду, используется для
эффективной
буферизации. Для PCM вычисляется по
формуле:
(nChannels*nSamplesPerSec*nBitsPerSample)/8.
nBlockAlign Выравнивание данных в data-чанке.
Для PCM вычисляется
по формуле:
(nChannels*nBitsPerSample)/8.
<fmt-specific> - Для категории PCM эта
структура имеет одно значащее поле
UINT nBitsPerSample, которое поведает нам о
разрядности
дискретизации (см. wFormatTag & Формат
данных PCM). Если,
например,nBitsPerSample = 12 , то сэмпл
хранится в старших
12 битах слова, а младшие 4 - нули.
Следом идет поле
WORD cbSize, используемое не-PCM форматом (
так, формат
ADPCM, например, хранит здесь некий
коэффициент,
необходимый для кодирования/декодирования
сигнала). Для
PCM-формата это поле может
отсутствовать.
Формат данных PCM
-----------------
Здесь описана схема размещения
данных в data-чанке wave файла.
В моно wave файле сэмплы расположены
последовательно один за другим:
sample[0],sample[1],sample[2]...
В стерео wave файле сэмплы идут
попарно:
left[0],right[0],left[1],right[1],left[2]...
Для более, чем двухканального
сигнала (квадрозвук?!?)
последовательность
чередования каналов не определена (а,
может уже и определена, а я не знаю).
Channel0 - байт для левого канала
Channel1 - байт для правого канала
8 bit mono:
-Sample1- -Sample2- -Sample3- -Sample4-
Channel0 Channel0 Channel0 Channel0
8 bit stereo:
--------Sample1------- --------Sample2-------
Channel0 Channel1 Channel0 Channel1
16 bit mono:
--------Sample1------- --------Sample2-------
Channel0 Channel0 Channel0 Channel0
(low byte) (high byte) (low byte) (high byte)
16 bit stereo:
-------------------Sample1---------------------
Channel0 Channel0 Channel1 Channel1
(low byte) (high byte) (low byte) (high byte)
Средние и крайние значения
элемента дискретизации
вычисляются так:
Разрядность Формат данных
------------------------------
1-8 bit unsigned char
8-16 bit int
например,
Формат Max Min Midpoint
---------------------------------------
8 bit PCM 255 0 128
16 bit PCM 32767 -32768 0
Для примера разберем начало
простенького PCM WAVE файла по байтам
Все смещения (слева) и размеры полей
(справа в квадратных скобках )
приведены в hex виде.
---------------------- Начало RIFF-чанка
00 'RIFF' [4]
04 DWORD - размер RIFF-чанка [4]
---------------------- Начало WAVE-формы
08 'WAVE' [4]
---------------------- Начало fmt-чанка
0C 'fmt ' [4]
10 DWORD - размер fmt-чанка (10h или 12h) [4]
---------------------- Структура WaveFormat (или
WaveFormatEx)
14 WORD wFormatTag = 1 (это же PCM) [2]
16 WORD nChannels = 1 [2]
18 DWORD nSamplesPerSec = 11025 [4]
1C DWORD nAvgBytesPerSec = 11025 [4]
20 WORD nBlockAlign = 1 [2]
22 WORD nBitsPerSample = 8 [2]
24 WORD cbSize (=0 или отсутствует для PCM. [2]
Далее в круглых скобках приведены
смещения для случая без cbSize)
---------------------- Конец fmt-чанка
---------------------- Начало data-чанка
26 (24) 'data' [4]
2A (28) DWORD размер data-чанка [4]
2E (2C) Sample0,Sample1,Sample2,... [???]
---------------------- Конец WAVE-формы
---------------------- Конец RIFF-чанка
Работать с таким файлом можно по
следующей грубой схеме:
1. Проверяем сигнатуру 'RIFF' по
смещению 0
2. Проверяем сигнатуру 'WAVE' по
смещению 8
3. Проверяем wFormatTag=1 по смещению 14
4. Читаем nChannels,nBitsPerSample по смещениям
16 и 22
5. Если надо, читаем nSamplesPerSec по
смещению 18
6. Начиная со смещения 24, начинаем
искать data-чанк. Этого
можно было бы и не делать, а сразу
читать сигнал по смещению 2E(2C),
но я встречал wave файлы, у которых
после fmt-чанка вставлен некий
fact-чанк длины 4 (+4 на сигнатуру +4 на
ckSize), о назначении коего
мне, к сожалению, ничего не известно.
Таким образом, после прочтения
fmt-чанка надо пройти по всем таким
чанкам, пока не упремся в
data-чанк.
7. Читаем сигнал по смещению 2E(2С) или
по смещению сигнатуры 'data' плюс 8
и до конца файла (или, если не лень,
смотрим размер data-чанка и
соответственно читаем, сколько
надо)
Надеюсь, сей скромный документ
поможет читателю разобраться в
азах программирования звука,
потратив на это гораздо меньше
времени, чем
в моем случае. При его составлении
весьма активно использовались
¦ Microsoft Windows Multimedia Programmer`s Reference
¦ Win32 SDK Programmer`s Guide
¦ некоторый личный опыт.
Serg Sorokin