Обычно у WAV-файла со стандартным заголовком размер секции "fmt " составляет 16 байт, тогда у этой секции структура традиционная:
typedef __packed struct _TWavFmtGeneric
{
uint16_t compression; // 2 байта, формат звуковых данных. Определяет
// наличие кодека или его отсутствие.
uint16_t channels; // 2 байта, количество каналов
uint32_t samplerate; // 4 байта, количество выборок в секунду
uint32_t bytespersecond; // 4 байта, количество байт в секунду.
uint16_t blockalign; // 2 байта, выравнивание блоков данных в файле
uint16_t bitspersample; // 2 байта, разрядность одной выборки.
}TWavFmtGeneric;
Однако из этого правила есть исключения, о чем подробнее рассказывается в этой статье.
Основное правило - при обработке WAV-файла нужно проверять размер всех секций. Находящиеся в общем пользовании файлы формата WAV могут неожиданно содержать расширенные секции с традиционными идентификаторами. Например, файлы WAV и AIFF пакета Pro Tools имеют больше секций расширения, которые нигде не задокументированы, а также содержат дополнительную информацию после фактических данных звука. Если Вы хотите успешно добраться до данных звука и определить их начало и конец, то на самом деле нужно искать секцию с идентификатором "data" и прочитать её размер (для файлов AIFF соответствующим идентификатором для информации о данных звука будет "SSND").
Правило 1: у дескриптора секции WAV-файла формат всегда одинаковый. Хорошая новость: несмотря на некоторую путаницу с идентификаторами и их следованием в теле WAV-файла, блок идентификации секций (дескриптор секции) имеет всегда один и тот же формат:
typedef struct _TRIFFsection
{
char id[4]; // 4 байта, текстовый идентификатор секции
uint32_t len; // 4 байта, длина секции
}TRIFFsection;
По этой причине процесс обработки файла значительно упрощается. Все, что требуется, это просто прочитать блоки идентификации секции (назовем его дескриптор секции), и если он нас не интересует, просто переносим позицию чтения в файле на следующий дескриптор. Следующий простой код на Java демонстрирует этот процесс (код просто выведет название всех секций WAV-файла).
// Здесь функция 'in.read(...)' вернет -1, когда был достигнут
// конец файла, так что проверка 'if (in.read(...) < 0)'
// покажет окончание файла.
public static void printWaveDescriptors(File file)
throws IOException
{
try (FileInputStream in = new FileInputStream(file))
{
byte[] bytes = new byte[4];
// Прочитаем первые 4 байта, там должен быть
// дескриптор RIFF:
if (in.read(bytes) < 0)
{
return;
}
printDescriptor(bytes);
// У первой секции всегда размер 12, из которых 4
// уже были прочитаны, так что пропустим еще 8 байт:
in.skip(8);
for (;;)
{
// Цикл с чтением и проверкой каждого дескриптора секции.
if (in.read(bytes) < 0)
{
break;
}
printDescriptor(bytes);
// Прочитаем длину секции.
if (in.read(bytes) < 0)
{
break;
}
// Пропуск в файле длины этой секции. Последующие байты
// должны быть либо другим дескриптором, либо завершением
// файла (EOF).
int length = (Byte.toUnsignedInt(bytes[0])
| Byte.toUnsignedInt(bytes[1]) << 8
| Byte.toUnsignedInt(bytes[2]) << 16
| Byte.toUnsignedInt(bytes[3]) << 24);
in.skip(Integer.toUnsignedLong(length));
}
System.out.println("Конец файла.");
}
}
Например, могут встретиться следующие секции в произвольном WAV-файле (идентификаторы имеют размер 4 байта):
RIFF bext fmt minf elm1 data regn ovwf umid
Следует отметить, что секция и "fmt ", и секция "data" могут совершенно законно находиться в любом месте WAV-файла. Месторасположение этих секций в файле, как и других секций, спецификацией Microsoft RIFF жестко не определено - теоретически они могут следовать друг за другом в любом порядке.
[Размер секции "fmt "]
Итак, с секциями wav-файла разобрались, и стоит иметь в виду еще 3 важных правила, касающихся появления в файле информации о формате (секция "fmt ") и самих звуковых данных (секция "data").
Правило 2: секция "fmt " всегда идет перед секцией "data", это выполняется всегда. Однако перед секцией "data" и после неё могут быть и любые другие секции - в том числе секции не определенные ни в каком стандарте (это даже дает Вам возможность добавлять в WAV-файл свои собственные секции). Совместите правило 1 и правило 2, и с их помощью легко обработаете любой WAV-файл.
Правило 3: у секции "fmt " размер не всегда 16 байт. Существуют вариации 16 байт и 18 байт, однако стандарт не ограничивает размер этой секции, он может быть и больше 18 байт. Если в поле размера дескриптора "fmt " указано больше 16 байт, то байты 17 и 18 также указывают, сколько здесь имеется дополнительных байт. Если оба байта 17 и 18 равны 0, то это просто секция "fmt " с размером 18, идентичная секции "fmt " с размером 16, не более того, и на эти два байта не стоит обращать внимания (их надо просто пропустить). Т. е. совершенно безопасно прочитать предыдущие данные формата точно так же, как если бы это была секция "fmt " стандартного размера 16.
Почему произошло изменение формата секции "fmt "? Проигрыватель медиа-файлов Windows XP Media Player мог воспроизводить 16-битные WAV-файлы, но с 24-битными WAV-файлами потребовалось расширение секции "fmt " до версии 18+. Раньше было много жалоб на то, что Windows не может проигрывать 24-битные WAV-файлы, однако если была секция "fmt " с 18 байтами, то она смогла бы это делать сразу. Компания Microsoft исправила это позже в Windows 7, так что 24 бита сейчас хорошо работают вместе с 16-байтными секциями "fmt ".
Правило 4. Довольно часто встречаются секции с нечетными размерами. Иногда это происходит с 24-битным монофоническим файлом. В спецификации четко не указано, что в дескрипторе задается реальный размер последующей секции, даже если он нечетный. В случае нечетного размера секции после неё (перед следующим дескриптором) добавляются нулевой байт. Таким образом, порции данных всегда начинаются с четных границ, но сам размер порции сохраняется как фактическое (даже нечетное) значение.
[Ссылки]
1. How can I detect whether a WAV file has a 44 or 46-byte header? site:stackoverflow.com. 2. Wave File Format - формат звукового файла WAV. 3. Audio File Format Specifications site:ece.mcgill.ca. |