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

0

Определим единственный класс IffRead. Чтобы использовать его, откройте файл и проинициализируйте новый объект  IffRead открытым файлом.

Листинг 19.2. Программа iff.h

#include "audio.h"

#include "compress.h"

bool IsIffFile(istream &file);

class IffRead: public AudioAbstract {

private:

istream & _stream; AbstractDecompressor *_decoder; void InitializeDecompression();

void DumpTextChunk(unsigned long, const char *);

unsigned char *_formatData;

unsigned long _formatDataLength;

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

*preferred);

void MinMaxChannels(int *min, int *max, int *preferred);

public:

IffRead(istream & s);

~IffRead();

size_t GetSamples(AudioSample *buffer, size_t numSamples);

size_t ReadBytes(AudioByte *buffer, size_t numSamples);

};

Макрос ChunkName преобразует четыре символа в единый 32-битный код.

Листинг 19.3. Программа iff.cpp

#include "iff.h"

#include "compress.h"

#include "dpcm.h"

#include <cstdlib>

#define ChunkName(a,b,c,d) (                        \ ((static_cast<unsigned long>(a)&255)<<24)                 \

+ ((static_cast<unsigned long>(b)&255)<<16)             \

+ ((static_cast<unsigned long>(c)&255)<<8)              \

+ ((static_cast<unsigned long>(d)&255)))

IffRead::IffRead(istream & s): _stream(s) {

// Формат   файла: Electronic Arts’ IFF/8SVX

cerr << "File Format: Electronic Arts’ IFF/8SVX\n";

_decoder = 0;

_fortnatData = 0;

_formatDataLength = 0;

}

IffRead::~IffRead() {

if(_formatData) { delete [] _formatData; }

if(_decoder) delete _decoder;

}

Общие замечания

Теоретически данные внутри контейнера могут следовать почти в любом порядке. B связи с этим бесперебойное чтение произвольного файла IFF становится проблематичным.  Всегда  требуется  прочитать  информацию  о  звуковом  формате  (блок VHDR) до чтения звуковых данных (блок BODY). Хотя будет несложно организовать поиск  по  файлу  для  определения  местоположения  отдельных  областей,  подобный подход утомителен и затрудняет потоковое воспроизведение аудиофайлов.

Ha  практике  все  записывают  информацию  о  звуковом  формате  (блок  VHDR) перед  блоком  звуковых  данных  (блок  BODY).  Следовательно,  задачу  можно  упростить,  непосредственно  читая  файл  и  обрабатывая  блоки  по  мере  их  появления. Важной  особенностью  является  вложенность  блоков,  поэтому  необходимо  следить за тем, какие блоки содержатся в других блоках.

Контейнеры

Значимость  любого блока зависит от его типа и типа охватывающего его блока.  Для  отслеживания  вложенных  блоков  я  храню  стек  информации  о  блоках. Например,  при  чтении  блока  BODY,  расположенного  внутри  контейнера  FORM,

8SVX стек  содержал  бы  два  элемента.  Переменная  _currentChunk определяет

текущую вершину стека. Значение -1 показывает, что стек пуст.

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

private:

struct {

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

unsigned long type;

//

Тип  блока.

unsigned long size;

//

Размер  блока.

unsigned long remaining;

//

Осталось прочитать  байтов.

bool isContainer;

//

Истина, если этот  блок -

//

контейнер.

unsigned long containerType;

//

Тип  контейнера.

} _chunk[5];

int _currentChunk;

//

Вершина   стека.

void PopStack();

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

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

void NextChunk(void);

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

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

Листинг 19.5. Реализация класса lffRead

void IffRead::PopStack(void) {

bool IffRead::ReadIff8svxChunk(unsigned long type, unsigned long size) {

return false;

}

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

return false;

}

void IffRead::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 (ReadIff8svxChunk(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";

}

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

Контейнер FORM 8SVX

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

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

_currentChunk = 1;   // Очистка  стека. NextChunk();          // Считываем  внешний  блок.

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

FORM/8SVX.

if ( (_currentChunk != 0)

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

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

|| (_chunk[0].containerType!=ChunkName(‘8′,’S’,’V’,’X’)))

{

// Тип  внешнего блока  в  IFF-файле не   является FORM/8SVX! cerr << "Outermost chunk in IFF file isn’t FORM/8SVX!!"; exit(1);

}

Даже в том случае, если тип текущего блока не является FORM, мы точно зна-

ем, что внешний блок всегда имеет тип FORM.

Листинг 19.7. Обработка IFF/8SVX-блоков конкретного формата и возвращение истины

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

// Тип  внешнего блока  не   FORM?

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

_currentChunk = 1;

return false;

}

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

Листинг 19.7. Обработка IFF/8SVX-блоков конкретного формата и возвращение истины (продолжение)

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

_chunk[_currentChunk].isContainer = true;

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

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

_chunk[_currentChunk].remaining = 4;

if (_currentChunk > 0) {

// Блок  FORM замечен на   внутреннем  уровне?

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

}

return true;

}

Блок VHDR

Блок   VHDR содержит   основную   информацию   о  формате   звуковых   данных. IFF/8SVX  первоначально  разрабатывался  как  формат  записи  семплов  музыкальных  инструментов.  Один  файл  IFF/8SVX  содержит  семпл  одного  инструмента. Звуковые данные, представленные в табл. 19.2, иллюстрируют ряд возможностей, специально придуманных для того, чтобы облегчить подобное применение.

Звуковые  данные  IFF/8SVX  состоят  из  начальной  (базовой)  области,  за  которой следует секция повторения. При имитации инструмента область повторения кольцо может проигрываться до конца ноты. Когда IFF/8SVX используется для простой записи, а не в качестве семпла, длина секции повторения устанавливается равной нулю.

При  воспроизведении  оцифровки  инструмента  необходимо  изменять  высоту звукадля получения различных нот. B случае с IFF/8SVX используются две технологии. Основной подход проигрывание звука на разных скоростях. Зная высоту

тона записанной и необходимой вам нот, вы можете изменить скорость воспроизведения так, чтобы получить желаемую высоту тона. Этот метод достаточно хорошо работает во многих приложениях, но для достижения более высокого качества звучания необходимо принять во внимание тот факт, что звук большинства инструментов слегка изменяется в зависимости от ноты. Файлы IFF/8SVX дают возможность сохранять несколько записей для одного и того же инструмента на разных высотах тона, что позволяет выбрать запись, наиболее близкую к желаемой высоте, и свести погрешность к минимуму. Третье поле в табл. 19.2 представляет высоту тона самой высокой записанной ноты. B области BODY ноты хранятся последовательно, начиная с самой высокой. Высота тона каждой следующей ноты на октаву ниже, чем предыдущей,  и  каждая  последующая  нота  записывается  при  отличной  от  остальных частоте дискретизации. Заметьте, что поле для записи количества отсчетов в секунду игнорируется при записи звучания инструмента, так как значимой является информация о высоте тона.

При записи простых звуков большая часть информации блока VHDR не требуется или не используется. B этом случае поля для записи количества отсчетов повторения, количества отсчетов на цикл и поле октавы устанавливаются равными нулю.

Листинг 19.7. Обработка IFF/8SVX-блоков конкретного формата и возвращение истины (продолжение)

if (type == ChunkName(‘V’,’H’,’D’,’R’)) (

if (_currentChunk != 1) {

// Блок  VHDR встречен не   на   том   уровне?

cerr << "VHDR 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;

}

Метод  InitializeDecompression гарантирует,  что  информация  о  формате (из области VHDR) была считана и сохранена в переменной _formatData. Эта информация   нужна   для   использования   методов   GetSamples и   MinMaxSamplingRate.

Таблица 19.2. Содержимое области VHDR

Байты                          Описание

4                                    Отсчеты базовой области для самой высокой ноты

4                                    Отсчеты повторения для самой высокой ноты

4                                    Количество отсчетов в цикле для самой высокой ноты

2                                    Отсчетов в секунду

1                                    Число октав в блоке BODY

1                                    Кодировка (0 ИКМ, 1 кодировка Фибоначчи, 2 экспоненциальная)

4                                    Громкость (65536 максимальная)

Листинг 19.5. Реализация класса IffRead (продолжение) size_t IffRead::GetSamples(AudioSample *buffer, size_t numSamples) {

if (!_decoder) InitializeDecompression();

return _decoder->GetSamples(buffer,numSamples);

}

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

*preferred) {

if (!_decoder) InitializeDecompression();

long samplingRate = BytesToIntMsb(_formatData+12,2);

*min = *max = *preferred = samplingRate;

}

Файлы IFF/8SVX всегда являются монофоническими.

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

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

{

*min = *max = *preferred = 1;

}

Для выбора декодера прежде всего необходимо удостовериться в том, что блок

VHDR был прочитан, а затем использовать код типа кодировки.

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

void IffRead::InitializeDecompression() {

if (_decoder) return;

while (!_formatData) {    // Читаем блок  VHDR.

NextChunk();

if (_currentChunk < 0) {

// Блок  VHDR не   найден?

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

exit(1);

}

}

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

// подбираем  декомпрессор.

unsigned long type = BytesToIntMsb(_formatData+15, 1);

if (!_decoder) {     // Тип  компресии  … не  поддерживается. cerr << "I don’t support IFF/8SVX compression type " <<

type << "\n" ;

exit(1);

}

}

Данные ИКМ

По существу, все файлы IFF/8SVX используют 8-битные звуковые дан-

ные ИКМ.

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

if (type == 0) {         // Формат  ИКМ.

_decoder = new DecompressPcm8Signed(*this);

}

ДИКМ Фибоначчи

Это  кодирование  часто  называют  дельтпа-кодированием  Фибоначчи  (Fibonacci delta).  K  сожалению,  термин  дельта-кодирование  (delta  encoding)  прочно  укоренился  в  кругах,  связанных  с  аппаратным  обеспечением:  он  обозначает  технику

1-битного  кодирования.  По  этой  причине  я  предпочитаю  использовать  термин

ДИКМ Фибоначчи.

Листинг 19.8. Создание декомпрессора для IFF/8SVX-файла в соответствии с типом компрессии (продолжение)

if (type == 1) {         // Фибоначчи  ДИКМ.

_decoder = new DecompressDpcmFibonacci(*this);

}

Экспоненциальная ДИКМ

Экспоненциальная  ДИКМ,  или  экспоненциальное  дельта-кодирование,  менее распространено, но его легче поддерживать.

Листинг 19.8. Создание декомпрессора для IFF/8SVX-файла в соответствии с типом компрессии (продолжение)

if (type == 2) {         // Экспоненциальная  ДИКМ.

_decoder = new DecompressDpcmExponential(*this);

}

Блок BODY

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

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

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

while (_chunk[_currentChunk].type != ChunkName(‘B’,’O’,’D’,’Y’)) {

NextChunk();

if (_currentChunk < 0) {

// He найдено  звуковых  данных?

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;

}

Ошибки lFF/8SVX

K  сожалению,  при  работе  с  устоявшимися  файловыми  форматами  приходится прилагать  серьезные  усилия  для  выявления  и  устранения  наиболее  распространенных ошибок, которые появляются при создании таких файлов. B случае с файлами  IFF  (включая  AIFF  и  WAVE)  чаще  всего  процедуры  записи  присоединяют аудиоданные  к  фиксированномузаголовку.  B  результате  не  исключена  непоследовательная  установка  ряда  размерных  параметров.  (Заголовок  IFF/8SVX  включает три  поля  размера:  области  FORM,  области  BODY и  количество  отсчетов,  сохраненных  в  области  VHDR.)  B  худшем  случае  все  эти  поля  будут  установлены  равными нулю, что является ошибкой.

Следующий  блок  кода  сравнивает  значение  количества  отсчетов,  сохраненное в  области  VHDR,  с  длиной  области  BODY.  Если  ничего  не  проясняется,  я  принудительно устанавливаю некоторые приемлемые значения и оцениваю результат.

Листинг 19.7. Обработка IFF/8SVX-блоков конкретного формата и возвращение истины (продолжение)

if (type == ChunkName(‘B’,’O’,’D’,’Y’)) {

if (_formatData) {

unsigned long vhdrSamples = BytesToIntMsb(_formatData+0,4);

unsigned long bodyLength = _chunk[_currentChunk].size;

// Имеет   ли   смысл?

if ((bodyLength == 0) && (vhdrSamples == 0)) {

// Тут   сказано, что  в  файле IFF/8SVX нет

// данных.

cerr << "IFF/8SVX file says it has no data!?\n";

// Все-таки  попробую что-нибудь

// воспроизвести, послушаем…

cerr << "I’ll try to play something anyway, here goes…\n";

_chunk[_currentChunk].size = 1000000L;

} else if (bodyLength == 0) {

// B файле  IFF/8SVX есть  отсчеты, однако  в  блоке  body

// нет данных!

cerr << "IFF/8SVX file has samples, but the body has no data!?\n";

// Возможно, блок body поврежден,

// но   я все-таки  попробую…

cerr << "Maybe the body is damaged, I’ll try anyway…\n";

_chunk[_currentChunk].size = vhdrSamples;

} else if (vhdrSamples == 0 {

// B блоке body файла  IFF/8SVX есть данные, но   нет отсчетов?

cerr << "IFF/8SVX file has data in the body, but no samples?!\n";

// Возможно, поврежден заголовок  посмотрим,  что получится…

cerr << "Maybe the header is damaged, I’ll try anyway…\n";

}

}

return true;

}

Текстовые блоки

Многие  блоки  содержат  текстовую  аннотацию.  Они  могут  появляться  в  лю-

бом типе файла IFF (включая AIFF и AIFF-C).

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

void IffRead::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;

}

Все  эти  области  имеют  один  и  тот  же  основной  формат;  они  содержат  строку ASCII,   заканчивающуюся   нулевым   символом.   Метод   DumpTextChunk просто   читает текст и печатает его, предваряя подходящим ключевым словом.

Листинг 19.9. Обработка IFF-блока непатентованного вида и возвращение истины

if (type == ChunkName(‘A’,’N’,’N’,’O’)) {   // Комментарий.

DumpTextChunk(size,"Annotation:");

return true;

}

if (type == ChunkName(‘(‘, ‘с’,’)’,’ ‘)) {   // Авторские права.

DumpTextChunk(size,"Copyright:");

return true;

if (type == ChunkName(‘N’,’A’,’M’,’E’)) {   // Название работы.

DumpTextChunk(size,"Name:");

return true;

}

if (type == ChunkName(‘A’,’U’,’T’,’H’)) {   // Автор.

DumpTextChunk(size,"Author:");

return true;

}

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

По теме:

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