Программа записывает звук в файл, другая программа его открывает и воспроизводит. Чтобы это сработало, обе должны одинаково понимать, где в файле частота дискретизации (sample rate)частота дискретизации (sample rate) — Число отсчётов в каждом канале за одну секунду, то же число кадров аудиоданных в секунду. Измеряется в герцах: 44 100 Гц означает 44 100 отсчётов на канал в секунду., сколько каналов и с какого байта начинаются отсчёты (sample)отсчёт (sample) — Значение сигнала в один момент дискретизации, одно число. В звукозаписи его также называют сэмплом.. WAV-файл (WAVE file)WAV-файл (WAVE file) — Звуковой файл со структурой RIFF и типом формы WAVE. Чанк fmt описывает параметры звука, чанк data содержит отсчёты; обычно это несжатый PCM. отвечает на эти вопросы набором помеченных блоков. Их можно прочитать вручную, не зная ничего, кроме смещений и порядка байтов.
Ниже по байтам разобран учебный файл из 122 байт. Из его устройства следует, почему звук не всегда начинается со смещения 44, зачем в файле блок JUNK без полезных данных и откуда берётся предел в 4 ГБ.
Файл как последовательность байтов
Файл на диске — это последовательность байтов (byte). Каждый байт хранит целое число от 0 до 255. Номер байта от начала файла называют смещением (offset): первый байт имеет смещение 0, сотый — 99.
Байты принято записывать двумя шестнадцатеричными цифрами: от 00 до FF. Так их показывают программы просмотра файлов, и так они даны на рисунках ниже. Буквы в файле хранятся кодами ASCII: R — это 52, I — 49, F — 46.
Число больше 255 не помещается в один байт и занимает несколько. Важно, в каком порядке они лежат. В WAV первым идёт младший байт — такой порядок называют little-endianпорядок байтов little-endian (little-endian) — Запись многобайтового числа, при которой младший байт идёт первым. Число 44 100 = 0xAC44 в четырёх байтах хранится как 44 AC 00 00.. Частота 44 100 Гц в шестнадцатеричной записи равна 0x0000AC44 и хранится в четырёх байтах как 44 AC 00 00. Чтобы получить число обратно, байты умножают на степени (power)степень (power) — При положительном целом показателе m запись aᵐ означает произведение m одинаковых множителей a. Показатель 0 даёт 1 при ненулевом основании, отрицательный целый показатель — обратную величину соответствующей положительной степени. 256 по порядку: .
Контейнер RIFF
WAV построен на RIFFRIFF (Resource Interchange File Format) — Структура файла из помеченных блоков — чанков. Файл начинается с чанка RIFF: в нём тип формы, например WAVE, и вложенные чанки.. Эту структуру описали IBM и Microsoft в 1991 году для мультимедийных файлов разных видов: звука, изображений, MIDI (IBM, Microsoft, 1991, гл. 2). Сам RIFF не говорит, что лежит внутри. Он задаёт только способ разложить данные по блокам — чанкам (chunk)чанк (chunk) — Блок файла RIFF: идентификатор из четырёх символов, 32-битный размер данных и сами данные. После данных нечётной длины стоит нулевой байт выравнивания, в размер он не входит..
Файл целиком — один чанк с идентификатором RIFF. Его первые 12 байт:
- Четыре символа
RIFF(байты 0–3). - Размер содержимого — 32-битное число без знака в little-endian (байты 4–7).
- Тип формы (form type) — четыре символа
WAVE(байты 8–11). У других форматов на основе RIFF здесь стоит иное значение, напримерRMIDдля MIDI.
Размер в байтах 4–7 не включает первые 8 байт: идентификатор и само поле размера. Поэтому он на 8 меньше длины файла. В учебном файле 122 байта, и в поле записано 114. После WAVE идут вложенные чанки, до конца файла.
Чанк: идентификатор, размер, данные
Чанкчанк (chunk) — Блок файла RIFF: идентификатор из четырёх символов, 32-битный размер данных и сами данные. После данных нечётной длины стоит нулевой байт выравнивания, в размер он не входит. устроен одинаково в любом файле RIFF:
- идентификатор (chunk ID) — четыре символа ASCII; короткое имя дополняется пробелами, поэтому чанк формата называется
fmtс пробелом в конце; - размер (chunk size) — 32-битное число без знака, длина данных в байтах;
- данные — ровно столько байт, сколько указано в размере.
Если размер нечётный, после данных записан ещё один нулевой байт — байт выравнивания (pad byte). Благодаря ему каждый чанк начинается с чётного смещения. В размер этот байт не входит (IBM, Microsoft, 1991, с. 2-2). Отсюда правило, по которому находится следующий чанк:
- смещение идентификатора текущего чанка, байт;
- значение поля размера, байт;
- остаток от деления на 2: 1 для нечётного размера, 0 для чётного.
Восьмёрка — это заголовок чанка: 4 байта идентификатора и 4 байта размера. В учебном файле внутри списка LIST лежит чанк INAM с названием Demo: четыре буквы и завершающий нулевой байт, размер 5. Он начинается со смещения 108, данные занимают байты 116–120, байт 121 — выравнивание. Следующий чанк мог бы начаться со смещения .
На рис. 1 показаны все байты учебного файла. Наведите указатель или коснитесь байта, чтобы увидеть, к какому полю он относится и как читается значение. Над дампом — карта из пяти чанков верхнего уровня: RIFF (заголовок файла), JUNK, fmt , data и LIST.
RIFFИдентификатор RIFFckID
- Смещение
- 0–3, 4 байта
- Байты
52 49 46 46- Чтение
- Четыре символа ASCII: «RIFF».
Четыре символа ASCII в начале файла: дальше идёт чанк RIFF, внутри которого лежат все остальные.
Чанк fmt: параметры звука
Чанк fmt (format) описывает, как устроены звуковые данные. В простейшем случае его данные занимают 16 байт (таблица 1). Названия полей взяты из структуры WAVEFORMATEX в документации Microsoft (Microsoft Learn).
| Смещение | Байт | Поле | Значение |
|---|---|---|---|
| 0 | 2 | код формата (wFormatTag) | 1 |
| 2 | 2 | число каналов (nChannels) | 2 |
| 4 | 4 | частота дискретизации (nSamplesPerSec) | 44 100 |
| 8 | 4 | байт в секунду (nAvgBytesPerSec) | 176 400 |
| 12 | 2 | размер кадра в байтах (nBlockAlign) | 4 |
| 14 | 2 | разрядность (wBitsPerSample) | 16 |
Код формата 1 означает импульсно-кодовую модуляцию (pulse-code modulation, PCM)импульсно-кодовая модуляция (pulse-code modulation, PCM) — Представление сигнала последовательностью квантованных отсчётов, взятых через равные промежутки времени. В WAV код формата 1 означает PCM с целыми отсчётами., то есть несжатые целые отсчёты. Знак и перевод отдельного отсчёта в float разобраны в статье о целочисленном PCM. Значение 3 означает отсчёты с плавающей точкой (IEEE float). Другие коды обозначают сжатые форматы, данные которых без отдельного декодера не прочитать.
Частота дискретизациичастота дискретизации (sample rate) — Число отсчётов в каждом канале за одну секунду, то же число кадров аудиоданных в секунду. Измеряется в герцах: 44 100 Гц означает 44 100 отсчётов на канал в секунду. здесь — число кадров в секунду, а не число всех отсчётов: у стерео (stereo)стерео (stereo) — Аудиоформат с двумя каналами, обычно левым и правым. Кадр несжатых стереоданных содержит два отсчёта одного момента времени. 44 100 кадров содержат 88 200 отсчётов. Разрядность (bit depth)разрядность (bit depth) — Число бит для представления одного квантованного отсчёта. B бит дают 2ᴮ кодов; при равномерном квантовании шаг зависит также от полного диапазона сигнала. — бит на один отсчёт одного канала.
Два поля не несут новой информации: их можно вычислить из остальных (IBM, Microsoft, 1991, с. 3-24):
- число каналов;
- разрядность, бит на отсчёт; деление на 8 переводит биты в байты;
- частота дискретизации, кадров в секунду;
- размер одного кадра, байт;
- объём данных за секунду, байт/с.
Для учебного файла байта и байт в секунду. Эти поля общие для всех кодов формата. У сжатых форматов их считают по правилам конкретного кодека (codec)кодек (codec) — Алгоритм или программа, которая кодирует сигнал в сжатый поток и декодирует его обратно. Формат файла при этом задаёт упаковку данных и метаданных., а не по формуле (2). По byteRate проигрыватель оценивает нужный размер буфера (buffer size)размер буфера (buffer size) — Длина или вместимость аудиобуфера в единицах конкретного API. В настройках аудиодрайвера обычно означает число кадров, то же число отсчётов на канал. Объём в байтах зависит также от числа каналов и формата отсчёта., а blockAlign подсказывает, какими порциями читать данные, чтобы не разрезать кадр.
Чанк fmt бывает длиннее 16 байт. Вариант на 18 байт добавляет поле cbSize с длиной дополнительных параметров, вариант на 40 байт — для расширенного описания (Kabal). Поэтому читать поля нужно по размеру чанка, а переходить к следующему — по формуле (1), не предполагая длину 16.
Чанк data: отсчёты и длительность
Чанк data содержит сами отсчёты, кадр за кадром. В стерео каналы чередуются: левый отсчёт первого кадра, правый отсчёт первого кадра, затем второй кадр (IBM, Microsoft, 1991, разд. «Data Packing for PCM WAVE Files»). Это то же чередование каналов (interleaved)чередование каналов (interleaved) — Хранение аудиоданных в одном массиве: подряд идут отсчёты всех каналов одного кадра, затем следующего. Для стерео: L₀, R₀, L₁, R₁., что в буфере программы.
В учебном файле 16 байт данных — четыре кадра (audio frame)кадр аудиоданных (audio frame) — Набор из одного отсчёта каждого канала в общий момент дискретизации. В моно кадр содержит одно число, в стерео — два. Определение относится к несжатым аудиоданным. по 4 байта. Каждый отсчёт — 16-битное целое со знаком в little-endian. Например, байты 00 01 дают 0x0100 = 256. Отрицательные числа записаны в дополнительном коде (two’s complement)дополнительный код (two’s complement) — Представление знакового целого в N битах: при установленном старшем бите беззнаковое значение этих битов уменьшают на 2ᴺ. Диапазон — от −2ᴺ⁻¹ до 2ᴺ⁻¹−1; у нуля одна запись.: байты 00 FF дают −256.
Отсчёты разрядностью до 8 бит хранятся без знака: тишине соответствует 128, а не 0. Начиная с 9 бит используются числа со знаком. Отсчёт занимает наименьшее целое число байтов, в которое помещается: 24-битный — три байта (IBM, Microsoft, 1991, разд. «Data Format of the Samples»).
Длительность записи следует из размера данных и полей fmt :
- размер чанка data, байт;
- число кадров;
- длительность, с.
Минута стерео в 16 битах и 44,1 кГц занимает байт, около 10 МБ. В учебном файле кадра, и звучат они мс. Если размер данных не делится на размер кадра, последний неполный кадр отбрасывают: так, например, считает число кадров JUCE (WavAudioFormat, JUCE 9.0.3).
Чтение по чанкам
Самый простой WAV-файл состоит из трёх чанков подряд: RIFF, fmt на 16 байт и data. Тогда отсчёты начинаются со смещения 44: . Отсюда распространённое упрощение «пропусти 44 байта и читай звук». В учебном файле перед fmt стоит JUNK на 36 байт, и отсчёты начинаются со смещения 80. Программа с упрощением прочитала бы заголовок fmt как звук.
Надёжное чтение поэтому идёт по чанкам:
- Проверить, что байты 0–3 — это
RIFF, а байты 8–11 —WAVE. Конец данных — смещение размер из байтов 4–7, но не дальше конца файла. - Начиная со смещения 12, прочитать идентификатор и размер очередного чанка.
- Если это
fmt, разобрать поля. Еслиdata, запомнить смещение и размер отсчётов. Иначе ничего не делать. - Перейти к следующему чанку по формуле (1) и повторить, пока до конца данных остаётся хотя бы 8 байт.
- Если не встретился
fmtилиdata, файл повреждён или это не PCM WAV.
Вот этот алгоритм на C++ без библиотек. Файл целиком читается в массив байтов, а числа собираются из байтов сдвигами. Так результат не зависит от порядка байтов процессора и от выравнивания данных в памяти. Приведение указателя на байты к std::uint32_t* от обоих зависело бы.
using Bytes = std::vector<std::uint8_t>;
std::uint16_t readU16 (const Bytes& b, std::size_t pos)
{
return static_cast<std::uint16_t> (b.at (pos) | (b.at (pos + 1) << 8));
}
std::uint32_t readU32 (const Bytes& b, std::size_t pos)
{
return readU16 (b, pos) | (std::uint32_t (readU16 (b, pos + 2)) << 16);
}
std::string readId (const Bytes& b, std::size_t pos)
{
std::string id;
for (std::size_t i = 0; i < 4; ++i)
id += static_cast<char> (b.at (pos + i));
return id;
}readU16 ставит второй байт на место старшего: сдвиг на 8 бит умножает его на 256. readU32 так же собирает 32-битное число из двух 16-битных половин. Метод at проверяет границы и выбрасывает исключение, если файл обрывается посреди поля.
struct WavInfo
{
std::uint16_t format = 0, channels = 0, blockAlign = 0, bits = 0;
std::uint32_t sampleRate = 0, byteRate = 0;
std::size_t dataOffset = 0, dataSize = 0;
bool hasFmt = false, hasData = false;
};
WavInfo parseWav (const Bytes& b)
{
if (b.size() < 12 || readId (b, 0) != "RIFF" || readId (b, 8) != "WAVE")
throw std::runtime_error ("не RIFF/WAVE");
const std::size_t end = std::min<std::size_t> (b.size(), 8 + std::size_t (readU32 (b, 4)));
WavInfo info;
std::size_t pos = 12;
while (pos + 8 <= end)
{
const std::string id = readId (b, pos);
const std::size_t size = readU32 (b, pos + 4);
const std::size_t body = pos + 8;
if (size > end - body)
throw std::runtime_error ("чанк выходит за конец файла");
if (id == "fmt " && size >= 16)
{
info.format = readU16 (b, body);
info.channels = readU16 (b, body + 2);
info.sampleRate = readU32 (b, body + 4);
info.byteRate = readU32 (b, body + 8);
info.blockAlign = readU16 (b, body + 12);
info.bits = readU16 (b, body + 14);
info.hasFmt = true;
}
else if (id == "data")
{
info.dataOffset = body;
info.dataSize = size;
info.hasData = true;
}
pos = body + size + (size & 1); // незнакомые чанки просто пропускаем
}
if (! info.hasFmt || ! info.hasData)
throw std::runtime_error ("нет чанка fmt или data");
return info;
}Выражение size & 1 — побитовое И с единицей: оно оставляет младший бит, то есть даёт тот же остаток от деления на 2, что в формуле (1). Проверка size > end - body ловит размер, который обещает больше байтов, чем есть в файле. Без неё обрезанная при копировании запись привела бы к чтению за концом массива.
Для учебного файла функция вернёт 2 канала, 44 100 Гц, 16 бит и данные со смещения 80 длиной 16 байт. Файл передаётся так:
std::ifstream file ("demo.wav", std::ios::binary);
const Bytes bytes ((std::istreambuf_iterator<char> (file)), std::istreambuf_iterator<char>());
const WavInfo info = parseWav (bytes);Нужны заголовки <algorithm>, <cstdint>, <fstream>, <iterator>, <stdexcept>, <string> и <vector>. Чтение всего файла в память подходит для коротких записей. Для длинных читают только заголовки, а отсчёты — блоками со смещения dataOffset.
Служебные чанки
Кроме fmt и data в файлах часто встречаются чанки с вспомогательными сведениями. Звук они не меняют, но сдвигают положение остальных чанков.
JUNK: заполнитель
Данные JUNK не имеют значения, программа пропускает его по размеру (IBM, Microsoft, 1991, с. 2-18). Обычно JUNK резервирует место заранее. Рекомендация EBU — ставить первым чанком JUNK не меньше 28 байт: если запись превысит 4 ГБ, на его месте окажется чанк ds64 формата RF64 (EBU Tech 3306, 2009, разд. 3.5). Так делает и JUCE: WavAudioFormat по умолчанию записывает JUNK сразу после WAVE, и размер заголовка не зависит от того, понадобится RF64 или нет (JUCE 9.0.3). Учебный файл повторяет этот приём.
bext: сведения Broadcast Wave
Формат Broadcast Wave (BWF) от EBU добавляет к WAV чанк bext (EBU Tech 3285, версия 2.0, 2011). В нём описание записи, автор, дата и время создания, положение первого отсчёта в отсчётах от полуночи, показатели громкости (loudness)громкость (loudness) — Ощущение того, насколько сильным кажется звук. Зависит от звукового давления, частотного состава и условий восприятия. и история обработки (coding history). Поля до истории занимают 602 байта, а история — строки переменной длины. Поэтому размер bext бывает нечётным, например 603, и тогда после него стоит байт выравнивания.
LIST и INFO: текстовые сведения
Чанк LIST с типом INFO содержит вложенные чанки по тем же правилам: INAM — название, IART — исполнитель, ISFT — программа, создавшая файл. Каждый хранит строку с нулевым байтом в конце (IBM, Microsoft, 1991, с. 2-14). В учебном файле LIST стоит после data. Чтобы найти его, нужно пропустить весь звук.
Частные чанки
Программы записывают и собственные чанки, описание которых может быть не опубликовано. Например, в файлах, экспортированных из некоторых программ, после data встречается чанк LGWV; в спецификациях RIFF и BWF его нет. Читателю достаточно правила: незнакомый идентификатор пропускается по размеру. Спецификация BWF требует от совместимых программ сохранять такие чанки при перезаписи файла, даже если они их не понимают (EBU Tech 3285, разд. 2.1).
Запись минимального WAV
Запись идёт в обратном порядке: сначала вычисляются размеры, затем байты укладываются по таблице 1. Минимальный файл — одна секунда цифровой тишины, 16 бит, моно (mono)моно (mono) — Аудиоформат с одним каналом. Кадр несжатых монофонических данных содержит один отсчёт., 48 000 Гц. Кадр занимает 2 байта, данные — байт. Размер в заголовке RIFF складывается из WAVE и двух чанков с их заголовками: .
void putU16 (Bytes& out, std::uint16_t v)
{
out.push_back (v & 0xFF);
out.push_back (v >> 8);
}
void putU32 (Bytes& out, std::uint32_t v)
{
putU16 (out, v & 0xFFFF);
putU16 (out, v >> 16);
}
void putId (Bytes& out, const char* id)
{
out.insert (out.end(), id, id + 4);
}
void writeSilence()
{
const std::uint32_t sampleRate = 48000;
const std::uint16_t channels = 1, bits = 16;
const std::uint16_t blockAlign = channels * bits / 8; // 2 байта на кадр
const std::uint32_t dataSize = sampleRate * blockAlign; // 96 000 байт = 1 с
Bytes out;
putId (out, "RIFF");
putU32 (out, 4 + (8 + 16) + (8 + dataSize)); // 96 036
putId (out, "WAVE");
putId (out, "fmt ");
putU32 (out, 16);
putU16 (out, 1); // PCM
putU16 (out, channels);
putU32 (out, sampleRate);
putU32 (out, sampleRate * blockAlign); // 96 000 байт/с
putU16 (out, blockAlign);
putU16 (out, bits);
putId (out, "data");
putU32 (out, dataSize);
out.resize (out.size() + dataSize, 0); // тишина: все отсчёты 0
std::ofstream ("silence.wav", std::ios::binary)
.write (reinterpret_cast<const char*> (out.data()), std::streamsize (out.size()));
}putU16 и putU32 делают обратное readU16 и readU32: сначала записывают младший байт. Получается файл из 96 044 байт. Его первые 44 байта в шестнадцатеричной записи:
52 49 46 46 24 77 01 00 57 41 56 45 RIFF, 96 036, WAVE
66 6D 74 20 10 00 00 00 fmt , 16
01 00 01 00 80 BB 00 00 00 77 01 00 PCM, 1 канал, 48 000 Гц, 96 000 байт/с
02 00 10 00 кадр 2 байта, 16 бит
64 61 74 61 00 77 01 00 data, 96 000Нули в данных означают тишину только для отсчётов со знаком. В 8-битном файле тишина записывается байтами 80 (128). У 8-битного моно с нечётным числом кадров размер data нечётный, и после данных нужен байт выравнивания.
Чтение через JUCE
В JUCE файл открывают через AudioFormatManager: после registerBasicFormats() он перебирает известные форматы, включая WAV, и возвращает читатель AudioFormatReader (JUCE, AudioFormatManager).
juce::AudioFormatManager formats;
formats.registerBasicFormats();
std::unique_ptr<juce::AudioFormatReader> reader (
formats.createReaderFor (juce::File ("/path/to/demo.wav")));
if (reader != nullptr)
{
const double rate = reader->sampleRate; // 44100
const auto channels = reader->numChannels; // 2
const auto bits = reader->bitsPerSample; // 16
const auto frames = reader->lengthInSamples; // 4 кадра
juce::AudioBuffer<float> buffer ((int) channels, (int) frames);
reader->read (&buffer, 0, (int) frames, 0, true, true);
}createReaderFor возвращает nullptr, если ни один формат не распознал файл. Освободить читатель должен вызывающий код, поэтому указатель сразу передаётся в std::unique_ptr. lengthInSamples — число кадров, то есть отсчётов одного канала (JUCE, AudioFormatReader).
За этими строками скрыта работа из предыдущих разделов. Читатель JUCE обходит чанки, пропускает незнакомые с учётом байта выравнивания, понимает RF64 и расширенное описание формата. Сведения из bext и LIST/INFO он кладёт в metadataValues. Метод read переводит целые отсчёты в float в диапазоне от −1 до 1 и раскладывает чередующиеся каналы по отдельным массивам AudioBuffer. Такой буфер на весь файл годится для коротких записей: длинные читают блоками, меняя четвёртый аргумент — номер первого кадра.
Ограничения формата
Предел 4 ГБ и RF64
Все размеры в RIFF — 32-битные числа без знака, наибольшее из них 4 294 967 295 байт, около 4 ГБ. Для стерео в 16 битах и 44,1 кГц это примерно 6 ч 46 мин звука, для стерео в 24 битах и 48 кГц — около 4 ч 9 мин, для восьми каналов в 24 битах и 96 кГц — около 31 мин.
Формат RF64 из EBU Tech 3306 снимает предел, почти не меняя структуру. Первые четыре байта — RF64 вместо RIFF. Сразу после WAVE стоит чанк ds64 с 64-битными размерами файла и данных. В 32-битных полях записано FFFFFFFF (−1 в дополнительном коде): это знак, что настоящее значение нужно брать из ds64. Программа, которая ожидает RIFF, отвергнет такой файл уже на первых четырёх байтах и не примет −1 за настоящий размер.
Расширенное описание формата
Поля fmt из таблицы 1 не говорят, какой канал на какой громкоговоритель идёт, и не отличают 20 значащих бит в трёхбайтовом отсчёте от полных 24. Для этого есть код формата 0xFFFE (WAVE_FORMAT_EXTENSIBLE): данные fmt занимают 40 байт и включают маску каналов, число значащих бит и идентификатор настоящего формата (Microsoft Learn). Расширенное описание рекомендуется, когда каналов больше двух, разрядность больше 16 бит, значащих бит меньше размера отсчёта или нужно указать раскладку громкоговорителей (Kabal). На практике 24-битное стерео часто записывают и с кодом 1: так поступает, например, JUCE, если маска каналов не задана. Поэтому читатель должен понимать оба варианта.
Самопроверка
Файл 8 бит, моно, 22 050 Гц. Какие значения стоят в полях размера кадра и байт в секунду?
Размер кадра байт, байт в секунду по формуле (2).
Чанк bext с размером 603 начинается со смещения 12. С какого смещения начнётся следующий чанк?
. Данные занимают байты 20–622, байт 623 — выравнивание, потому что 603 нечётно.
В стереофайле 16 бит, 44 100 Гц размер data равен 1 058 400 байт. Сколько в нём кадров и сколько секунд он звучит?
Кадр занимает 4 байта, поэтому кадров . Длительность с, или по формуле (3).
Почему программа, которая всегда читает звук со смещения 44, может выдать щелчок или шум в начале?
Перед fmt или data могут стоять другие чанки, например JUNK или bext, а fmt бывает длиннее 16 байт. Тогда по смещению 44 лежат не отсчёты, а байты заголовков, и программа воспроизведёт их как звук.
Источники
- IBM Corporation, Microsoft Corporation. Multimedia Programming Interface and Data Specifications 1.0, август 1991 — гл. 2, с. 2-2–2-18: чанки, байт выравнивания, пропуск незнакомых чанков,
LIST/INFO,JUNK; гл. 3, с. 3-22 и далее: форма WAVE, чанкfmt, формулы, расположение отсчётов PCM. - Kabal P. Wave File Specifications. в списке литературы → — сводка полей
fmtдля размеров 16, 18 и 40 байт, условия дляWAVE_FORMAT_EXTENSIBLE. - Microsoft Learn. WAVEFORMATEX structure — назначение полей,
nBlockAlignкак размер кадра, ограничения простого описания иWAVEFORMATEXTENSIBLE. - EBU Tech 3285. Specification of the Broadcast Wave Format, версия 2.0, май 2011 — разд. 2.1–2.3: структура BWF, частные чанки, поля
bext. - EBU Tech 3306. MBWF / RF64: An Extended File Format for Audio, июль 2009 — разд. 3.4–3.5: предел 4 ГБ,
RF64,ds64, резерв черезJUNK. - JUCE 9.0.3. juce_WavAudioFormat.cpp, AudioFormatManager и AudioFormatReader — обход чанков,
JUNKпри записи, поля читателя и методread.