Читання файлів формату WAVE

B оригінальної специфікації IFF автори акцентують увагу на тому, що різні блоки даних можуть зявлятися в будь-якому порядку Таким чином, може статися, що в деяких файлах WAVE, наприклад, блок звукових даних буде слідувати перед блоком формату, визначальним спосіб зберігання даних

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

Хоча такий підхід виправданий, він має серйозний недолік: необхідно, щоб відразу був доступний весь файл B Зокрема, якщо планується скористатися потоковим поширенням файлу по мережі і програванням його в міру отримання даних, не можна переглянути весь файл і локалізувати області, не розпочавши програвання

B моїй версії класу WaveRead реалізований інший підхід, частково для забезпечення використання потокового аудіо, частково через простоту Замість того, щоб заносити на «карту» розташування кожного блоку, я читаю файл від початку до кінця і обробляю кожен блок у міру його появи Проте мот існувати файли WAVE, які за допомогою даної програми програний можна

Контейнери

Згадайте, що значущість кожного блоку залежить і від його типу, і від контейнера, в якому цей блок укладений Для відстеження цієї інформації я організував стек, де зберігаю інформацію про блоки Наприклад, при читанні блоку fmt, розташованого всередині контейнера RIFF WAVE, в стек були б записані два елементи: один для зовнішнього контейнера і один для блоку fmt Мінлива

_currentChunk визначає поточне положення вершини стека -1 Означаетчто

стек порожній

Лістинг 174 Члени класу WaveRead

private:

struct {

/ / Стек блоків WAVE

unsigned long type / / Тип блоку unsigned long size / / Розмір блоку

unsigned long remaining / / Залишилося прочитати байтів bool isContainer / / Істина, якщо це контейнер unsigned long containerType / / Тип контейнера

} _chunk[5]

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

void NextChunk(void)

Метод NextChunk є основним для цього класу, притому досить великим за розміром Даний метод спочатку пропускає залишок поточної області та відповідним чином оновлює стек Потім читається заголовок наступної області і приймається рішення, що з нею робити

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

Лістинг 175 Реалізація класу WaveRead

void WaveRead::NextChunk(void) {

if (_streameof()) {

/ / Прочитуємо наступний блок

_currentChunk = 1 / / Очищаємо стек return

}

unsigned long type = ReadIntMsb(_stream,4) unsigned long size = ReadIntLsb(_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

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"

}

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

Є одна важлива невелика деталь B файлах RIFF (як і у всіх форматах файлів, являющіхсяпроізводнимі від IFF) будь блок займає парна кількість байтів Якщо реальний розмір блоку непарний, значить там присутній додатковий заповнює байт, який необхідно пропустити

Лістинг 176 Пропускаємо решту блоку

if ((_currentChunk &gt= 0) &amp&amp (_chunk[_currentChunk]isContainer))

{

unsigned long lastChunkSize = _chunk[_currentChunk]size

if (lastChunkSize & 1) {/ / Є байт-заповнювач

_chunk[_currentChunk]remaining++

lastChunkSize + + / / Враховуємо заповнення при оновленні

/ / Контейнера

}

SkipBytes(_stream,_chunk[_currentChunk]remaining)

/ / Відкидаємо блок

_currentChunk – / / Викидаємо блок з стека

/ / Контрольна перевірка: блок-оболонка

/ / Повинен бути контейнером

if ((_currentChunk &lt 0) || (_chunk[_currentChunk]isContainer)) {

/ / Блок укладений не в контейнері

cerr &lt&lt &quotChunk contained in non-Container!?!\n"

exit(1)

}

/ / Зменшуємо розмір контейнера

if (_currentChunk &gt= 0) {

/ / Санітарна перевірка: переконаємося, що

/ / Розмір контейнера достатній

/ / Крім того, уникнемо по-справжньому

/ / Неприємних ситуацій переповнення

if ((lastChunkSize+8) &gt _chunk[_currentChunk]remaining) {

/ / Помилка: розміри блоку не дозволяють

/ / Розмістити його в контейнері

cerr &lt&lt &quotError: Chunk is too large to fit in container?!?\n"

_chunk [_currentChunk] remaining = 0 / / Контейнер порожній

} else

_chunk[_currentChunk]remaining = lastChunkSize + 8

}

}

Блок, який ми тільки що обробили, можливо, був останнім у своєму

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

Лістинг 177 Відкидаємо закінчені контейнери

/ / Якщо є форми, які також

/ / Закінчені, відкинемо їх while ((_currentChunk> = 0) / / Є блок

&amp&amp (_chunk[_currentChunk]remaining &lt 8)

)

{

SkipBytes(_stream,_chunk[_currentChunk]remaining)

/ / Відкидаємо його

unsigned long lastChunkSize = _chunk[_currentChunk]size

_currentChunk – / / Відкидаємо контейнерний блок

/ / Санітарна перевірка: блок, який включає

/ / Поточний, повинен бути контейнером

if (_chunk[_currentChunk]isContainer) {

/ / Контейнер блоку не є контейнером

cerr &lt&lt &quotChunk contained in non-container!?!\n"

exit (1)

}

/ / Зменшуємо розмір контейнера

if (_currentChunk &gt= 0) {

if ((lastChunkSize+8) &gt _chunk[_currentChunk]remaining) {

/ / Помилка в WAVE-файл: блок занадто великий

/ / І не підходить

cerr &lt&lt &quotError in WAVE file: Chunk is too large to fit?!?\n"

lastChunkSize = _chunk[_currentChunk]remaining

}

_chunk[_currentChunk]remaining = lastChunkSize + 8

}

}

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

Koнтeйнep RIFF WAVE

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

Лістинг 178 Ініціалізація обєкта WaveRead

_currentChunk = 1 / / Очищаємо стек NextChunk ()

/ / Перевіряємо, що перший блок –

/ / Це контейнер RIFF / WAVE

if ( (_currentChunk = 0)

|| (_chunk[0]type = ChunkName(R,I,F,F))

|| (_chunk[0]isContainer = true)

|| (_chunk[0]containerType = ChunkName(W,A,V,E))

)

{

/ / Зовнішній блок в WAVE-файлі не є

/ / RIFF-блоком

cerr &lt&lt &quotOutermost chunk in WAVE file isnt RIFF!"

exit(1)

}

Навіть якщо поточний блок не є блоком RIFF, ми знаємо, що зовнішній завжди повинен належати до цього типу

Лістинг 179 Обробка WAVE-блоку конкретного формату і повернення

if ((_currentChunk &gt= 0) &amp&amp

(_chunk[0]type = ChunkName(R,I,F,F))){

/ / Зовнішній блок не є RIFF-блоком

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

_currentChunk = 1

return

}

B рамках методу NextChunk RIFF-блок легко обробити Потрібно тільки по-

мітити його як контейнер і прочитати в контейнерний тип

Лістинг 179 Обробка WAVE-блоку конкретного формату і повернення

(Продовження)

if (type == ChunkName(R,I,F,F)) {

_chunk[_currentChunk]isContainer = true

/ / Спочатку необхідно перевірити розмір контейнера

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

_chunk[_currentChunk]remaining = 4

if (_currentChunk &gt 0) {

/ / RIFF-блок помічений на внутрішньому рівні

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

}

return

}

Блок fmt

Блок fmt містить інформацію про реально використовуваному звуковому форматі Точне вміст області fmt варіюється залежно від методу компресії B табл 172 показаний формат, застосовуваний для чистих даних ІКМ Інші технології стиснення розширюють цей блок, поміщаючи в нього додаткову інформацію

Я вирішив скористатися найпростішим методом обробки форматних даних Виявивши блок fmt, зчитую його і зберігаю вміст в памяті При цьому я не намагаюся використовувати будь-яку частина інформації до тих пір, поки в ній не виникне реальна потреба

Таблиця 172 Вміст області блоку fmt для даних ІКМ

Розмір Опис

2 Код компресії (див табл 173)

2 Кількість каналів

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

4 Середня кількість байтів в секунду

2 Вирівнювання блоку

2 Значущих бітів на відлік

2 Кількість байтів додаткової інформації

n Додаткова інформація, повязана з конкретним компресором

Лістинг 179 Обробка WAVE-блок конкретного формату і повернення

(Продовження)

if (type == ChunkName(f,m,t, )) {

if (_currentChunk = 1) {

/ / FMT-блок зустрівся не на тому рівні

cerr &lt&lt &quotFMT 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

}

Нам немає необхідності аналізувати дані про формат доти, поки ктонибудь не запросить інформацію про фото Наприклад, коли запитують частоту дискретизації або кількість каналів, ми можемо вважати відповідні байти безпосередньо зі збережених даних про формат Аналогічним чином, якщо хтось запросить відліки, необхідно переконатися, що обєкт декомпресора вже ініціалізованим Метод InitializeDecompression гарантує прочитання блоку fmt і, крім того, ініціалізує декомпрессор

Лістинг 174 Члени класу WaveRead (продовження)

private:

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

void MinMaxChannels(int *min, int *max, int *preferred) size_t GetSamples(AudioSample *buffer, size_t numSamples) void InitializeDecompression()

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

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

*preferred) { InitializeDecompression()

unsigned long samplingRate = BytesToIntLsb(_formatData+4,4)

*max = *min = *preferred = samplingRate

}

void WaveRead::MmMaxChannels(int *min, int *max, int *preferred)

{

InitializeDecompression()

unsigned long channels = BytesToIntLsb(_formatData+2,2)

*min = *max = *preferred = channels

}

size_t WaveRead::GetSamples(AudioSample *buffer, size_t numSamples)

{

if (_decoder) InitializeDecompression()

return _decoder-&gtGetSamples(buffer,numSamples)

}

Створення обєкта декомпресора

Фірмою Microsoft було зареєстровано для використання у файлах WAVE майже 100 кодів компресії Список кодів визначений у заголовку файлу mmregh, включеному в поточну версію засобів розробки Microsoft B табл 173 перераховуються деякі з найбільш важливих кодів

При виборі декодера я насамперед переконуюся в тому, що блок fmt був прочитаний Потім, використовуючи код компресії, я намагаюся створити підходящий обєкт декомпресії

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

void WaveRead::InitializeDecompression() {

if (_decoder) return

/ / Переконаємося, що читаємо блок fmt

while (_formatData) { NextChunk()

if (_currentChunk &lt 0) {

/ / He знайдений блок fmt

cerr &lt&lt &quotNo fmt chunk found!?!\n"

exit (1)

}

}

/ / Грунтуючись на типі компресії, підбираємо

/ / Декомпрессор

unsigned long type = BytesToIntLsb(_formatData+0, 2)

if (_decoder) {

/ / Цей тип компресії WAVE-файлу не підтримується

cerr &lt&lt &quotI dont support WAVE compression type &quot &lt&lt type &lt&lt

&quot\n"

Таблиця 173 Приклади кодів формату WAVE

Код Опис

0 Невідомий / неприпустимий

1 ІКМ

2 Microsoft АДІКМ

6 ITU G711 А-функція

7 ITU G711 мю-функція

17                                               IMA ADPCM

20 ITU G723 АДІКМ

49                                                GSM 610

64 ITU G721 АДІКМ

80                                                MPEG

65535 Експериментальний

exit (1)

}

}

Дані ІКМ

Більшість файлів WAVE використовують дані у форматі чистої ІКМ Відліки розміром 8 біт або менше зберігаються як беззнакові дані великі зберігаються як знакові

Лістинг 1710 Підбір декомпресора для WAVE-файлу виходячи з типу компресії

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

unsigned long bitsPerSample = BytesToIntLsb(_formatData+14,

2)

if (bitsPerSample <= 8) / / B форматі WAVE 8-бітові

/ / Дані записуються

/ / В беззнакового форматі

_decoder = new DecompressPcm8Unsigned(*this)

else if (bitsPerSample <= 16) / / 16-бітові дані зі знаком.

_decoder = new DecompressPcml6LsbSigned(*this)

}

Дані IMA ADPCM

Microsoft використовує власний варіант компресії IMA ADPCM При цьому для вказівки довжини кожного пакета звукових даних 2 байта додаються кданним, наведеним у табл 172

Лістинг 1710 Підбір декомпресора для WAVE-файлу виходячи з типу компресії (продовження)

if (type == 17) {/ / Формат IMA ADPCM

unsigned long bitsPerSample = BytesToIntLsb(_formatData+14, 2)

if (bitsPerSample = 4) {

/ / Для IMA ADPCM потрібно 4 біта на

/ / Відлік, а не ..

cerr &lt&lt &quotIMA ADPCM requires 4 bits per sample, not "

cerr &lt&lt bitsPerSample &lt&lt &quot\n"

exit (1)

}

if (_formatDataLength &lt 20) {

/ / Для IMA ADPCM потрібна додаткова

/ / Інформація про декомпресії

cerr  &lt&lt  &quotIMA ADPCM requires additional decompression data\n"

exit(1)

}

int packetLength = BytesToIntLsb(_formatData+18,2)

int channels = BytesToIntLsb(_formatData+2,2)

_decoder = new

DecompressImaAdpcmMs(*this,packetLength,channels)

}

Функції μ і А

B файлах WAVE можна використовувати компресію за допомогою компресії МЮИ А-функцій

Лістинг 1710 Підбір декомпресора для WAVE-файлу виходячи з типу компресії (продовження)

if (type == 6) {

_decoder = new DecompressG711ALaw(*this)

}

if (type == 7) {

_decoder = new DecompressG711MuLaw(*this)

}

Інші методи стиснення

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

Лістинг 1710 Підбір декомпресора для WAVE-файлу виходячи з типу компресії (продовження)

if (type == 2) {

/ / Компресія MS ADPCM не підтримується

cerr &lt&lt &quotI dont support MS ADPCM compression\n"

}

Блок data

Блок data зберігає стислі звукові дані Немає необхідності робити з ним що-небудь в рамках методу NextChunk

Лістинг 179 Обробка WAVE-блоку конкретного формату і повернення (продовження)

if (type == ChunkName (d, а, t, а)) {

return

}

Тут важливий метод ReadBytes Коли хто-небудь запитує звукові дан-

ві, спочатку потрібно переконатися, що читання здійснюється з області data, a за-

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

Лістинг 174 Члени класу WaveRead (npoдoлжeніе)

public:

size_t ReadBytes(AudioByte *buffer, size_t numSamples)

Лістинг 175 Реалізація класу WaveRead (npoдoлжeніe)

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

while (_chunk [_currentChunk] type = ChunkName (d, а, t, а)) {

NextChunk()

if (_currentChunk &lt 0) {

/ / Ніяких звукових даних не виявлено

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

}

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

Багато блоки містять текстові анотації Ці блоки можуть зявлятися в будь-якому типі файлу RIFF, не тільки в WAVE Всі їхні назви починаються з символу I у верхньому регістрі, який показує, що блоки використовуються в інформаційних цілях

Лістинг 1711 Обробка WAVE-блоку непатентованої формату і повернення

if ((type & 0xFF000000) == ChunkName (I, 0,0,0)) {/ / Перший

/ / Символ I?

char *text = new char[size+2]

_streamread(text,size)

long length = _streamgcount()

_chunk[_currentChunk]remaining = length

text[length] = 0

if (type == ChunkName (I, C, M, T)) / / Коментар

cerr &lt&lt &quotComment: "

else if (type == ChunkName (I, C, O , P )) / / Авторські

/ / Права

cerr &lt&lt &quotCopyright: "

else if (type == ChunkName (I, N, A, M)) / / Назва

/ / Твори

cerr &lt&lt &quotTitle: "

else if (type == ChunkName (I, A, R, T)) / / Виконавець

cerr &lt&lt &quotArtist: "

else

cerr << "Text:"; / / Інформаційні блоки іншого типу.

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

return

}

Джерело: Кінтцель Т Керівництво програміста по роботі зі звуком = 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>

*

*