Главная » Программирование звука » Чтение файлов AIFF

0

B  оригинальной  спецификации  IFF  авторы  обращают  особое  внимание  на  то, что блоки данных различных типов могут следовать в любом порядке. Поэтому не исключено, что в некоторых файлах AIFF, например, блок звуковых данных будет расположен перед форматным блоком, определющим способ хранения данных.

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

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

B  моем  классе  AiffRead реализован  иной  подход.  Это  вызвано  отчасти  необходимостью  обеспечить  возможность  работы  с  потоками, отчасти   простотой  реализации. Вместо того чтобы картировать местоположение каждой области, я читаю файл  от  начала  до  конца,  обрабатывая  каждый  блок  по  мере  обнаружения.  Хотя теоретически  могут  существовать  файлы  AIFF,  которые  не  удастся  воспроизвести с помощью этой программы, я ни разу не сталкивался с подобной ситуацией.

Контейнеры

Вспомните,  что  значимость  каждого  блока  зависит  и  от  его  типа,  и  от  типа включающего  его  контейнера.  Для  отслеживания  этой  информации  я  храню  стек информации об областях.

При  чтении,  например,  блока  ssnd,  расположенного  внутри  контейнера  FORM AIFF,  стек  содержит  два  элемента.  Переменная  _currentChunk указывает  текущую вершину стека; -1 означает, что стек пуст.

Листинг 18.4. Интерфейс функций хранения блоков класса AiffRead

private:

// Стек  блоков.

struct {

unsigned long type;          // Тип  блока.

unsigned long size;          // Размер блока.

unsigned long remaining;     // Осталось прочесть  байтов.

bool isContainer;            // Истина, если это  контейнер.

unsigned long containerType; // Тип  контейнера.

} _chunk[5];

int _currentChunk;              // Вершина   стека.

void PopStack();

bool ReadAiffSpecificChunk(unsigned long type, unsigned long size);

void DumpTextChunk(unsigned long size, const char *);

bool ReadIffGenericChunk(unsigned long type, unsigned long size);

void NextChunk(void);

Ядро  этого  класса   метод  NextChunk.  Первым  делом  он  пропускает  остаток текущего  блока  области  и  соответствующим  образом  обновляет  стек.  Затем  читается заголовок следующего блока и принимается решение, что с ним делать.

Так  как ключевая  особенность  в  том,  что  данные  читаются  из  файла,  необходимо  позаботиться  о  проверке  конца  файла.  Как  только  мы  его  достигаем,  стек опустошается.  Прежде чем обрабатывать  текущий  блок, остальные функции  должны удостовериться, что стек не пуст.

Листинг 18.5. Реализация класса AiffRead

void AiffRead::PopStack(void) {

bool AiffRead::ReadAiffSpecificChunk(unsigned long type, unsigned long size) {

return false;

}

bool AiffRead::ReadIffGenericChunk(unsigned long type, unsigned long size) {

return false;

}

// Сброс памяти текстового  блока см. следующую  главу.

void AiffRead::DumpTextChunk(unsigned long size, const char

*name) {

char *text = new char[size+2];

_stream.read(text,size);

long length = _.stream.gcount();

_chunk[_currentChunk].remaining = length;

text[length] = 0;

cerr << name << " " << text << "\n";

delete [] text;

}

void AiffRead::NextChunk(void) { PopStack();

if (_stream.eof()) {

// Считываем  очередной  блок.

_currentChunk = 1;   // Опустошаем  стек.

return;

}

unsigned long type = ReadIntMsb(_stream,4); unsigned long size = ReadIntMsb(_stream,4); if (_stream.eof()) {

_currentChunk = 1;   // Опустошаем  стек.

return;

}

_currentChunk++;

_chunk[_currentChunk].type = type;

_chunk[_currentChunk].size = size;

_chunk[_currentChunk].remaining = size;

_chunk[_currentChunk].isContainer = false;

_chunk[_currentChunk].containerType = 0;

if (ReadAiffSpecificChunk(type,size)) return; if (ReadIffGenericChunk(type,size)) return; char code[5] = "CODE";

code[0] = (type>>24)&255;code[1] = (type>>16)&255;

code[2] = (type>>8 )&255;code[3] = (type   )&255;

// Нераспознанный блок  …

// игнорируется.

cerr << "Ignoring unrecognized `" << code << "" chunk\n";

}

Методы    ReadAiffSpecificChunk  и    ReadIffGenericChunk  отвечают    за обработку  данных  внутри  всех  распознанных  блоков.  Подробнее  мы  поговорим об этом в следующих разделах.

Есть  несколько  типов  блоков,  которые  являются  общими  для  форматов  AIFF

и IFF/8SVX. O них речь пойдет в следующей главе.

Контейнер FORM AIFF

Файл  AIFF  содержит  единственный  контейнер  FORM,  который,  в  свою  очередь,  включает  в  себя  все  остальные  блоки  файла.  Я  предлагаю  считывать  внешний блок во время инициализации.

Листинг 18.6. Инициализация стека блоков в классе AiffRead

_currentChunk = 1;  // Опустошаем  стек. NextChunk();

// Убеждаемся,  что  первый блок  -

// это  контейнер FORM/AIFF.

if ( (_currentChunk ! = 0)

|| (_chunk[0].type != ChunkName(‘F’,’O’,’R’,’M’))

|| (_chunk[0].isContainer != true)

|| ( (_chunk[0].containerType != ChunkName(‘A’,’I’,’F’,’F’))

&&(_chunk[0].containerType != ChunkName(‘A’,’I’,’F’,’C’)))

)

{

// Внешний   блок в  файле AIFF не   является  контейнером

// FORM/AIF?

cerr << "Outermost chunk in AIFF file isn’t FORM/AIF?!!";

exit(1);

}

}

Блок FORM легко обрабатывается внутри метода NextChunk. Нужно только пометить его как контейнер и прочитать в контейнерный тип.

Листинг 18.7. Обработка AIFF блок конкретного формата и возвращение истины

if (type == ChunkName(‘F’,’O’,’R’,’M’)) {

_chunk[_currentChunk].isContainer = true;

// Сначала необходимо проверить  размер  контейнера.

_chunk[_currentChunk].containerType = ReadIntMsb(_stream,4);

_chunk[_currentChunk].remaining = 4;

if (_currentChunk > 0) {

// Ha внутреннем  уровне  обнаружен блок  FORM?

cerr << "FORM chunk seen at inner level?!?!\n";

}

return true;

}

Даже если текущий блок не является блоком FORM, мы твердо знаем, что внеш-

ний блок всегда будет относиться к этому типу.

Листинг 18.7. Обработка AIFF-блока конкретного формата и возвращение истины (продолжение)

if ((_currentChunk >= 0) && (_chunk[0].type != ChunkName(‘F’,’O’,’R’,’M’))){

// Внешний   блок не   относится к  типу  FORM?

cerr << "Outermost chunk is not FORM?!?!\n";

_currentChunk = 1;

return true;

}

Блок FVER

Чтобы в будущем избежать проблем, когда снова потребуется обновить AIFF-C,

был предложен новый тип блоков FVER. Такой блок содержит метку времени,

показывающую   дату   опубликования   использованной   версии   стандарта   AIFF-C. Когда   стандарт   претерпевает   значительные   изменения,   эта   временная   пометка изменяется.  Ha  момент  написания  этой  книги  самая  последняя  версия  стандарта AIFF-C  была  опубликована  23  мая  1990  г.  в  14:40.  Использующая  стандартный  для компьютеров   Macintosh   формат   представления   времени   (количество   секунд,   прошедших  с  1  января  1904  года,  0:00)  временная  пометка  имеет  значение  2726318400 (в шестнадцатеричной записи A2805140).

Листинг 18.7. Обработка AIFF-блока конкретного формата и возвращение истины (продолжение)

if (type == ChunkName(‘F’,’V’,’E’,’R’)) { unsigned long version = ReadIntMsb{_stream,4); if (version != 2726318400) {

// Нераспознан  формат AIFC-файла.

cerr << "Unrecognized AIFC file format.\n";

exit(1);

}

_chunk[_currentChunk].remaining = 4;

return true;

}

Блок COMM

Блок COMM содержит информацию о формате записи звука.

Листинг 18.7. Обработка AIFF-блока конкретного формата и возвращение истины (продолжение)

if (type == ChunkName(‘C’,’O’,’M’,’M’)) {

if (_currentChunk != 1) {

// Блок  COMM встретился на   недопустимом  уровне.

cerr << "COMM chunk seen at wrong level?!?!\n";

}

_formatData = new unsigned char[size+2];

_stream.read(reinterpret_cast<char *>(_formatData),size);

_formatDataLength = _stream.gcount();

_chunk[_currentChunk].remaining = 0;

return true;

}

Большинство   аудиоформатов   подразумевает   хранение   значений,   таких   как частота   дискретизации,   в   виде   целых   чисел.   Для   повышения   точности   фирма Apple  решила  использовать  формат  с  плавающей  точкой.  Данный  подход  применим   к   приложениям,   требующим   повышенной   точности   (например,   производство  кино или  видеофильмов),  но  работа  с  таким  форматом  усложняет  чтение  и  запись  данных.  Точный  формат,  используемый  Apple,   80-битный,  с  плавающей точкой.  Первые  16  бит  применяются  для  записи  бита  знака  и  15-битной  двоичной  экспоненты.  Разумно  предположить,  что  число  нормализовано;  значит  можно   проигнорировать   младшие   32   бита   и  использовать  быстрое   преобразование в целое.

Листинг 18.5. Реализация класса AiffRead (продолжение)

void AiffRead::MinMaxSamplingRate(long *min, long *max, long

*preferred) { InitializeDecompression();

unsigned ieeeExponent = BytesToIntMsb(_formatData+8,2);

unsigned long ieeeMantissaHi = BytesToIntMsb(_formatData+10,4);

ieeeExponent &= 0x7FFF;   // Удаляем знаковый бит

// (частота  дискретизации не   может

// быть  меньше   0).

long samplingRate = ieeeMantissaHi >> (16414 ieeeExponent);

*min = *max = *preferred = samplingRate;

}

void AiffRead::MinMaxChannels(int *min, int *max, int *preferred)

{

InitializeDecompression();

unsigned long channels = BytesToIntMsb(_formatData+0,2);

*min = *max = *preferred = channels;

}

Точное  содержимое  блока  типа  COMM варьируется в  зависимости  от  используемого метода сжатия. B табл. 18.2 показан формат, который применяется для чистых  данных  ИКМ.  Другие  технологии  сжатия  используют  расширенный  формат с дополнительной информацией.

B системном программном обеспечении Apple 4-байтные коды широко применяются для идентификации типов данных и другой важной информации. B случае с AIFF-C эти коды определяют метод сжатия. Мультимедийное программное обеспечение Apple способно найти звуковой кодек на основе данного 4-байтного кода, что делает возможным динамическую установку новых кодеков. Заметим, что наименование  метода  компрессии  используется  только  для  информации  пользователя; оно может варьироваться в зависимости от программы записи.

При  получении  запроса  на  звуковые  отсчеты  сначала  необходимо  выяснить, какой   декодер   привлечь.   Это   обеспечивается   вызовом   метода   InitializeDecompression.

Листинг 18.5. Реализация класса AiffRead (продолжение)

size_t AiffRead::GetSamples(AudioSample *buffer, size_t numSamples) {

Таблица 18.2. Содержимое блока COMM

Размер          Описание

2                        Количество каналов

4                        Общее количество фреймов с отсчетами

2                         Количество битов на отсчет

10                       Фреймы с отсчетами в секунду (80-битное число с плавающей точкой IEEE)

4                         Код компрессии (см. табл. 18.3)

n                         Наименование метода компрессии

Таблица 18.3. Избранные коды форматов для АIFF-С

Код                                   Описание

NONE                                    PCM

АСЕ2                                     Сжатие АСЕ 2:1

АСЕ8                                     Сжатие АСЕ 8:3

МАСЗ                                    Сжатие MACE 3:1

МАС6                                    Сжатие MACE 6:1

ulaw                                       ITU G.711 мю-функция

ima4                                       IMAADPCM

if (!_decoder) InitializeDecompression();

return _decoder->GetSamples(buffer,numSamples);

}

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

Листинг 18.5. Реализация класса AiffRead (продолжение)

void AiffRead::InitializeDecompression() {

if (_decoder) return;

// Проверяем,  что мы прочли блок типа  COMM.

while (!_formatData) { NextChunk ();

if (_currentChunk < 0) {

// He найден блок типа  COMM?

cerr << "No ‘COMM’ chunk found?!?!\n";

exit(1);

}

}

// B соответствии  с  типом компрессии подбираем

// декомпрессор.

unsigned long type = ChunkName(‘N’,’O’,’N’,’E’);

// По  умолчанию  нет. if (_formatDataLength >= 22)

type = BytesToIntMsb(_formatData+18, 4);

if (!_decoder) {

char code[5] = "CODE";

code[0] = (type>>24)&255;   code[1] = (type>>16)&255;

code[2] = (type>>8 )&255;   code[3] = (type  )&255;

cerr << "I don’t support AIFF-C compression type " << code

<< "\n";

exit (1);

}

}

Данные ИКМ

Для поддержки обратной совместимости файл, записанный со звуковыми данными  ИКМ,  должен  быть  описан  как  файл  AIFF  (а  не  AIFF-C).  Большинство файлов  в  формате  AIFF/AIF-C  сохраняется  именно  так.  Отсчеты  ИКМ  хранятся в  формате  чисел  со  знаком;  запись  многобайтных  отсчетов  ведется  со  старшего разряда.

Листинг 18.8. Создание декомпрессора для АIFF-файла в соответствии с типом компрессии

if (type == ChunkName(‘N’,’O’,’N’,’E’)) {   // Формат   ИКМ. unsigned long bitsPerSample = BytesToIntMsb(_formatData+6, 2);

_decoder = new DecompressPcm8Signed(*this);

else if (bitsPerSample <= 16)        // 16-битные данные

// со знаком.

_decoder = new DecompressPcm16MsbSigned(*this);

}

Данные мю-функции

Файлы  формата  AIFF-C  позволяют использовать  компрессию  с помощью  мю-

функции.

Листинг 18.8. Создание декомпрессора для AIFF-файла в соответствии с типом компрессии (продолжение)

if (type == ChunkName(‘u’,’l’,’а’,’w’)) {    // Формат

// мю-функции.

_decoder = new DecompressG711MuLaw(*this);

}

Данные IMA ADPCM

Фирма  Apple  использует  свой  собственный  вариант  сжатия  IMA  ADPCM.

Подробнее об этом рассказывается в главе 13.

Листинг 18.8. Создание декомпрессора для AIFF-файла в соответствии с типом компрессии (продолжение)

if (type == ChunkName(‘i’,’m’,’a’,’4′)) {   // Формат   IMA ADPCM. int channels = BytesToIntMsb(_formatData+0,2);

_decoder = new DecompressImaAdpcmApple(*this,channels);

}

Блок SSND

B блоках типа SSND хранятся сжатые звуковые данные. Они начинаются с 8 байт информации, которая обычно не используется.

Листинг 18.7. Обработка AIFF-блока специфического формата и возвращение истины (продолжение)

if (type == ChunkName(‘S’,’S’,’N’,’D’)) { SkipBytes(_stream,8);

_chunk[_currentChunk].remaining = 8;

return true;

}

Здесь  важен  метод  ReadBytes.  Когда  кто-либо  запрашивает  звуковые  данные,  нужно,  во-первых,  убедиться,  что  они  считываются  из  блока  SSND,  во-вторых, извлечь соответствующее количество байтов и вернуться.

Листинг 18.5. Реализация класса AiffRead (продолжение)

size_t AiffRead::ReadBytes(AudioByte *buffer, size_t numBytes) {

while (_chunk[_currentChunk].type != ChunkName(‘S’,’S’,’N’,’D’)) {

NextChunk();

if (_currentChunk < 0) { // Стек  пуст?

// Нет   звуковых данных!

cerr << "I didn’t find any sound data!?!?\n";

return 0;

}

}

if (numBytes > _chunk[_currentChunk].remaining)

numBytes = _chunk[_currentChunk].remaining;

_stream.read(reinterpret_cast<char *>(buffer), numBytes);

numBytes = _stream.gcount();

_chunk[_currentChunk].remaining = numBytes;

return numBytes;

}

Глава 19. Формат файла IFF/8SVX

Основные  идеи,  лежащие  в  основе  обменных  файловых  форматов,  были  внедрены  фирмой  Electronic  Arts  для  использования  на  машинах  Commodore  Amiga. Фирма  разработала  гибкий  файловый  формат  IFF  (Interchange  File  Format,  формат  файлов  для  обмена)  для  хранения  различных  типов  данных.  Файлы  IFF  могут  содержать  одно  изображение,  форматированный  текст,  анимацию,  звук  или любую  комбинацию  типов  данных.  Для  хранения  своих  собственных  типов  данных разработчики могут создавать новые типы блоков.

Структура файлов IFF та же, что и у RIFF или AIFF-файлов; она описывалась в  главе  17.  Тем  не  менее  многобайтные  значения  в  IFF-файлах  хранятся  в  формате  MSB.  Кроме  того,  в  IFF-файлах  использованы  другие  имена  блоков.  Хотя сегодня   IFF   применяется   лишь   изредка,   он   является   предшественником   RIFF и  AIFF.  Исходная  документация  IFF  в  большой  степени  подходит  для  всех  трех файловых форматов.

Обычно  файл  IFF  состоит  из  единственного  блока  FORM,  тип  которого  определяют данные, записанные в файле. Реже внешней областью являются блоки CAT или  LIST с  несколькими  вложенными  блоками  FORM.  B  этой  главе  мы  рассмотрим только файлы, содержащие единственную область FORM.

Идентификация файлов IFF/8SVX

Файлы,   о   которых   пойдет   речь,   имеют   единственный   внешний   контейнер FORM типа  8SVX (аббревиатура  фразы  «8-bit  sampled  voice»   8-битный  оцифрованный  голос).  Существуют  другие  способы  структуризации  файлов  IFF,  однако они используются редко.

Листинг 19.1. Идентификация файлов формата IFF/8SVX

bool IsIffFile(istream &file) { file.seekg(0);     // K началу файла. unsigned long form = ReadIntMsb(file,4); if (form != CnunkName(‘F’,’O’,’R’,’M’))

return false;   // He IFF-файл.

SkipBytes(file,4); // Пропускаем поле  длины   блока.

unsigned long type = ReadIntMsb(file,4);

if (type == ChunkName(‘8′,’S’,’V’,’X’))

return true;

return false;      // IFF-файл, но   его  формат не   8SVX.

}

Обзор формата IFF/8SVX

Наиболее  широко  используемый  на  компьютерах  Amiga  аудиоформат   IFF; файл  состоит  из  одного  контейнера  FORM типа  8SVX,  который  часто  называют IFF/8SVX.  Форма  8SVX  была  разработана  для  хранения   семплов  (оцифрованных звуков) музыкальных инструментов.

Форма  8SVX  может  содержать  множество  блоков  различных  типов.  Некото-

рые из них перечислены в табл. 19.1.

Таблица 19.1. Типы блоков IFF/8SVX

Имя                                           Описание

VHDR                                               Информация о звуковом формате

NAME                                               Наименование звука

(с)                                              Информация об авторских правах

AUTH                                               Автор

ANNO                                              Дополнительный комментарий

BODY                                               Звуковые данные

АТАК                                                Атака

RLSE                                                Затухание

Источник: Кинтцель Т.  Руководство программиста по работе со звуком = A Programmer’s Guide to Sound: Пер. с англ. М.: ДМК Пресс, 2000. 432 с, ил. (Серия «Для программистов»).

По теме:

  • Комментарии