Читання файлів формату IFF/8SVX

Визначимо єдиний клас IffRead Щоб використовувати його, відкрийте файл і проініціалізіруйте новий обєкт IffRead відкритим файлом

Лістинг 192 Програма iffh

#include &quotaudioh&quot

#include &quotcompressh&quot

bool IsIffFile(istream &ampfile)

class IffRead: public AudioAbstract {

private:

istream &amp _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 &amp s)

~IffRead()

size_t GetSamples(AudioSample *buffer, size_t numSamples)

size_t ReadBytes(AudioByte *buffer, size_t numSamples)

}

Макрос ChunkName перетворює чотири символи в єдиний 32-бітний код

Лістинг 193 Програма iffcpp

#include &quotiffh&quot

#include &quotcompressh&quot

#include &quotdpcmh&quot

#include &ltcstdlib&gt

#define ChunkName(a,b,c,d) (                        \ ((static_cast&ltunsigned long&gt(a)&amp255)&lt&lt24)                 \

+ ((static_cast&ltunsigned long&gt(b)&amp255)&lt&lt16)             \

+ ((static_cast&ltunsigned long&gt(c)&amp255)&lt&lt8)              \

+ ((static_cast&ltunsigned long&gt(d)&amp255)))

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

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

cerr &lt&lt &quotFile 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 показує, що стек порожній

Лістинг 194 Інтерфейс функцій зберігання блоків класу 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, який спочатку пропускає непрочитану частина поточного блоку і відповідним чином оновлює стек Потім він читає заголовок наступної області і визначає, що з нею робити

Так як важливою особливістю методу є те, що дані читаються з файлу, слід подбати про перевірку кінця файлу За його досягненні стек спустошується Решта функцій перед обробкою поточної області повинні упевнитися, що стік не порожній

Лістинг 195 Реалізація класу 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 (_streameof()) {

/ / Прочитуємо черговий блок

_currentChunk = 1 / / Спустошуємо стек return

}

unsigned long type = ReadIntMsb(_stream,4) unsigned long size = ReadIntMsb(_stream,4) if (_streameof()) {

_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] = &quotCODE"

code[0] = (type&gt&gt24)&amp255code[1] = (type&gt&gt16)&amp255

code[2] = (type&gt&gt8 )&amp255code[3] = (type  )&amp255

/ / Пропускаємо нерозпізнаний блок типу ..

cerr &lt&lt &quotIgnoring unrecognized `&quot &lt&lt code &lt&lt &quot&quot chunk\n"

}

Метод NextChunk також відповідає за обробку даних усередині розпізнаної області Докладніше ми обговоримо це в наступних розділах

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

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

Лістинг 196 Ініціалізація стека блоків класу 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

Лістинг 197 Обробка IFF/8SVX-блоков конкретного формату і повернення істини

if ((_currentChunk &gt= 0) &amp&amp (_chunk[0]type = ChunkName(F,O,R,M))) {

/ / Тип зовнішнього блоку не FORM

cerr &lt&lt &quotOutermost chunk is not FORM!?!\n"

_currentChunk = 1

return false

}

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

Лістинг 197 Обробка 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 &gt 0) {

/ / Блок FORM помічений на внутрішньому рівні

cerr &lt&lt &quotFORM chunk seen at inner level!?!\n"

}

return true

}

Блок VHDR

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

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

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

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

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

Лістинг 197 Обробка IFF/8SVX-блоков конкретного формату і повернення істини (продовження)

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

if (_currentChunk = 1) {

/ / Блок VHDR зустрінутий не на тому рівні

cerr &lt&lt &quotVHDR chunk seen at wrong level!?!\n"

}

_formatData = new unsigned char[size+2]

_streamread(reinterpret_cast&ltchar *&gt(_formatData),size)

_formatDataLength = _streamgcount()

_chunk[_currentChunk]remaining = 0

return true

}

Метод InitializeDecompression гарантує, що інформація про формат (з області VHDR) була лічена і збережена у змінній _formatData Ця інформація потрібна для використання методів GetSamples і MinMaxSamplingRate

Таблиця 192 Вміст області VHDR

Байти Опис

4 Відліки базовій області для найвищої ноти

4 Відліки повторення для найвищої ноти

4 Кількість відліків в циклі для найвищої ноти

2 відліку в секунду

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

1 Кодування (0 ІКМ, 1 кодування Фібоначчі, 2 експонентна)

4 Гучність (65536 максимальна)

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

if (_decoder) InitializeDecompression()

return _decoder-&gtGetSamples(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 завжди є монофонічними

Лістинг 195 Реалізація класу lffRead (продовження)

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

{

*min = *max = *preferred = 1

}

Для вибору декодера насамперед необхідно упевнитися в тому, що блок

VHDR був прочитаний, а потім використовувати код типу кодування

Лістинг 195 Реалізація класу lffRead (продовження)

void IffRead::InitializeDecompression() {

if (_decoder) return

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

NextChunk()

if (_currentChunk &lt 0) {

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

cerr &lt&lt &quotNo 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 &lt&lt &quot\n&quot

exit(1)

}

}

Дані ІКМ

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

ві ІКМ

Лістинг 198 Створення декомпресора для IFF/8SVX-файла відповідно до типу компресії

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

_decoder = new DecompressPcm8Signed(*this)

}

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

Це кодування часто називаютьдельтпа-кодуванням Фібоначчі(Fibonacci delta) K жаль, терміндельта-кодування(Delta encoding) міцно укорінився в колах, повязаних з апаратним забезпеченням: він позначає техніку

1-бітного кодування З цієї причини я вважаю за краще використовувати термін

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

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

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

_decoder = new DecompressDpcmFibonacci(*this)

}

Експонентна ДИКМ

Експонентна ДИКМ,абоекспоненціальне дельта-кодування,менш поширене, але його легше підтримувати

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

if (type == 2) {/ / Експоненціальна ДИКМ

_decoder = new DecompressDpcmExponential(*this)

}

Блок BODY

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

Лістинг 195 Реалізація класу lffRead (продовження)

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

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

NextChunk()

if (_currentChunk &lt 0) {

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

cerr &lt&lt &quotI didnt find any sound data?!?\n"

return 0

}

}

if (numBytes &gt _chunk[_currentChunk]remaining)

numBytes = _chunk[_currentChunk]remaining

_streamread(reinterpret_cast&ltchar *&gt(buffer), numBytes)

numBytes = _streamgcount()

_chunk[_currentChunk]remaining = numBytes

return numBytes

}

Помилки lFF/8SVX

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

Наступний блок коду порівнює значення кількості відліків, збережене в області VHDR, з довжиною області BODY Якщо нічого не прояснюється, я примусово встановлюю деякі прийнятні значення і оцінюю результат

Лістинг 197 Обробка 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) &amp&amp (vhdrSamples == 0)) {

/ / Тут сказано, що у файлі IFF/8SVX немає

/ / Даних

cerr &lt&lt &quotIFF/8SVX file says it has no data?\n"

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

/ / Відтворити, послухаємо ..

cerr &lt&lt &quotIll try to play something anyway, here goes..\n"

_chunk[_currentChunk]size = 1000000L

} else if (bodyLength == 0) {

/ / B файлі IFF/8SVX є відліки, проте в блоці body

/ / Ні даних

cerr &lt&lt &quotIFF/8SVX file has samples, but the body has no data?\n"

/ / Можливо, блок body пошкоджений,

/ / Але я все-таки спробую ..

cerr &lt&lt &quotMaybe the body is damaged, Ill try anyway..\n"

_chunk[_currentChunk]size = vhdrSamples

} else if (vhdrSamples == 0 {

/ / B блоці body файлу IFF/8SVX є дані, але немає відліків

cerr &lt&lt &quotIFF/8SVX file has data in the body, but no samples!\n"

/ / Можливо, пошкоджений заголовок подивимося, що вийде ..

cerr &lt&lt &quotMaybe the header is damaged, Ill try anyway..\n"

}

}

return true

}

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

Багато блоки містять текстову анотацію Вони можуть зявлятися в лю-

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

Лістинг 195 Реалізація класу lffRead (продовження)

void IffRead::DumpTextChunk(unsigned long size, const char *name)

{

char *text = new char[size+2]

_streamread(text,size)

long length = _streamgcount()

_chunk[_currentChunk]remaining = length

text[length] = 0

cerr &lt&lt name &lt&lt &quot &quot &lt&lt text &lt&lt &quot\n"

delete [] text

}

Всі ці області мають один і той же основний формат вони містять рядок ASCII, що закінчується нульовим символом Метод DumpTextChunk просто читає текст і друкує його, випереджаючи відповідним ключовим словом

Лістинг 199 Обробка IFF-блоку непатентованої виду і повернення істини

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

DumpTextChunk(size,&quotAnnotation:&quot)

return true

}

if (type == ChunkName ((, з, ), ‘)) {/ / Авторські права

DumpTextChunk(size,&quotCopyright:&quot)

return true

if (type == ChunkName (N, A, M, E)) {/ / Назва роботи

DumpTextChunk(size,&quotName:&quot)

return true

}

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

DumpTextChunk(size,&quotAuthor:&quot)

return true

}

Джерело: Кінтцель Т Керівництво програміста по роботі зі звуком = A Programmers Guide to Sound: Пер з англ М: ДМК Пресс, 2000 432 с, іл (Серія «Для програмістів»)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*