Ogg/Vorbis
является бесплатным, открытым, не требующим лицензирования форматом для
хранения цифровой аудио информации. Название состоит из 2-х имен: Ogg – имя контейнера для хранения метаданных, и vorbis – имя кодека созданного для применения в составе Ogg.
Разработчики формата утверждают, что качество звучания у них лучше, чем
в mp3. Я не проверял, так что этот аспект, вам предстоит проверить
самостоятельно. :) Для успешного применения кодека в своих проектах, вам
необходимо скачать Ogg/vorbis SDK, ссылка будет ниже.
Интеграция в музыкальный класс.
В состав SDK входит как сам кодек, так и очень
облегчающая работу небольшая обёрточка называемая vorbisfile. Вы,
конечно, можете использовать кодек напрямую, благо примеры есть в
поставке, но это достаточно хлопотное дело, так как количество структур и
функций там очень велико, и неподготовленному разуму будет довольно
трудно. Так что мы пойдем по более легкому пути, и воспользуемся
средством предоставляемом разработчиком для начинающих/не хотящих лезть
в дебри. :) Кстати, то, что я видел во множестве фрисурцевых
проигрывателях, доказывает, что все предпочитают пользоваться именно
этим средством.
Для начала подключите необходимые библиотеки и заголовочные файлы:
#include <vorbis/codec.h> #include <vorbis/vorbisfile.h> #pragma comment(lib, "ogg.lib") #pragma comment(lib, "vorbisfile.lib")
Принцип работы библиотеки очень прост: открываем файл, если надо, получаем необходимую информацию о файле схожую на ID3 Tag
в mp3, и данные о звуковых потоках (формат, частота и т.д.). Читаем
файл или целиком, или по частям, пока не достигнем конца, и закрываем
файл. Но, давайте все по порядку.
В приватный раздел нашего класса добавим несколько описаний, после чего он примет такой вид:
//… private: // Идентификатор источника ALuint mSourceID; // Переменные библиотеки vorbisfile // Главная структура описания файла OggVorbis_File *mVF; // Структура комментариев к файлу vorbis_comment *mComment; // Информация о файле vorbis_info *mInfo; // Файловый поток содержащий наш ogg файл std::ifstream OggFile; bool mStreamed; // Functions // Функция чтения блока из файла в буфер bool ReadOggBlock(ALuint BufID, size_t Size); // Функция открытия и инициализации OGG файла bool LoadOggFile (const std::string &Filename, bool Streamed); bool LoadWavFile (const std::string &Filename);
Добавилось, как вы видите 4 новых переменных.
Структура OggVorbis_File содержит большое количество
информации о файле (состояния, свойства) которые мы использовать
напрямую не будем. Просто знайте, что эта главная структура при работе с
ogg/vorbis, и её экземпляр должен передаваться во все функции библиотеки vorbisfile.
vorbis_comment может содержать любую текстовую
информацию. Работа с этой структурой проста. Она содержит: массив строк -
комментариев, массив длин этих комментариев, и количество комментариев.
Так же, содержится отдельно строка о создателе файла. Я не показывал в
коде пример работы с этой структурой, это вы запросто можете сделать
самостоятельно.
vorbis_info – содержит несколько полей. Основные из них:
channels – количество каналов в файле (1 – моно, 2 – стерео), и rate –
частота дискретизации потока.
ifstream – поток вывода из файла с помощью которого мы будем работать с нашим контейнером музыки.
Добавилось так же 2 функции:
ReadOggBlock – функция чтения из файла Size байт данных.
Если Size равно размеру файла, то происходит чтение всего файла.
Считанные данные записываются в буфер OpenAL с идентификатором BufID.
LoadOggFile – открываем, и инициализируем Ogg файл, в зависимости от входных параметров. Как вы видите, эта функция уже поддерживает потоковое проигрывание.
Раскомментируем блок чтения Ogg файла в процедуре Open:
if (Ext == "OGG") { mStreamed = Streamed; return LoadOggFile(Filename, Streamed); }
Теперь подробно разберем функцию инициализации.
bool remSnd::LoadOggFile(const string &Filename, bool Streamed) { int i, DynBuffs = 1, BlockSize; // OAL specific SndInfo buffer; ALuint BufID = 0; // Структура с функциями обратного вызова. ov_callbacks cb; // Заполняем структуру cb cb.close_func = CloseOgg; cb.read_func = ReadOgg; cb.seek_func = SeekOgg; cb.tell_func = TellOgg; // Создаем структуру OggVorbis_File mVF = new OggVorbis_File; // Открываем OGG файл как бинарный OggFile.open(Filename.c_str(), ios_base::in | ios_base::binary); // Инициализируем файл средствами vorbisfile if (ov_open_callbacks(&OggFile, mVF, NULL, -1, cb) < 0) { // Если ошибка, то открываемый файл не является OGG return false; } // Начальные установки в зависимости от того потоковое ли проигрывание // затребовано if (!Streamed) { for (TBuf::iterator i = Buffers.begin(); i != Buffers.end(); i++) { if (i->second.Filename == Filename) BufID = i->first; } // Размер блока – весь файл BlockSize = ov_pcm_total(mVF, -1) * 4; } else { // Размер блока задан BlockSize = DYNBUF_SIZE; // Количество буферов в очереди задано DynBuffs = NUM_OF_DYNBUF; alSourcei(mSourceID, AL_LOOPING, AL_FALSE); } // Получаем комментарии и информацию о файле mComment = ov_comment(mVF, -1); mInfo = ov_info(mVF, -1); // Заполняем SndInfo структуру данными buffer.Rate = mInfo->rate; buffer.Filename = Filename; buffer.Format = (mInfo->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; // Если потоковое проигрывание, или буфер со звуком не найден то if (Streamed || !BufID) { for (i = 0; i < DynBuffs; i++) { // Создаем буфер alGenBuffers(1, &buffer.ID); if (!CheckALError()) return false; Buffers[buffer.ID] = buffer; // Считываем блок данных ReadOggBlock(buffer.ID, BlockSize); if (!CheckALError()) return false; if (Streamed) // Помещаем буфер в очередь. { alSourceQueueBuffers(mSourceID, 1, &buffer.ID); if (!CheckALError()) return false; } else alSourcei(mSourceID, AL_BUFFER, buffer.ID); } } else { alSourcei(mSourceID, AL_BUFFER, Buffers[BufID].ID); } return true; }
Сначала инициализируется структура ov_callbacks. Эта
структура содержит указатели на 4 функции работы с источником данных:
чтение, поиск, закрытие, и сообщение о текущем месте положения читающего
указателя. Вся эта суета от того, что функция ov_open(), библиотеки
vorbisfile, работает только с stdin,
stdout. Это далеко не всегда удобно и приемлемо. Поэтому, разработчики
предложили средство для работы с любыми источниками данных (будь то
поток, как в нашем случае, или ваша собственная структура). Единственное
неудобство при этом – вы самостоятельно должны будете реализовать
вышеназванные 4 функции для работы с вашим контейнером данных. В их
реализации нет ничего сложного, в этом вы сами можете убедиться,
посмотрев код:
size_t ReadOgg(void *ptr, size_t size, size_t nmemb, void *datasource) { istream *File = reinterpret_cast<istream*>(datasource); File->read((char *)ptr, size * nmemb); return File->gcount(); } int SeekOgg(void *datasource, ogg_int64_t offset, int whence) { istream *File = reinterpret_cast<istream*>(datasource); ios_base::seekdir Dir; File->clear(); switch (whence) { case SEEK_SET: Dir = ios::beg; break; case SEEK_CUR: Dir = ios::cur; break; case SEEK_END: Dir = ios::end; break; default: return -1; } File->seekg((streamoff)offset, Dir); return (File->fail() ? -1 : 0); } long TellOgg(void *datasource) { istream *File = reinterpret_cast<istream*>(datasource); return File->tellg(); } int CloseOgg(void *datasource) { return 0; }
Всем функциям передается в качестве параметра datasource -
указатель на объект-хранилище данных. Так как у нас это ifstream, то мы
сразу же приводим указатель к этому типу. Самая интересная функция в
этом квартете – это SeekOgg(). Вас может смутить строчка
File->clear(). Но это не очищение файла, а сброс флагов состояния
объекта ifstream. Необходимо сказать, что функция Seek должна уметь
реагировать на указатели позиции файла SEEK_SET(начало),
SEEK_СUR(текущая позиция), SEEK_END(конец).
Давайте, вернемся к нашему барану – функцииLoadOggFile().
Как можно заметить, при открытии файла используется
функция ov_open_callbacks(), которой передается: адрес на контейнер с
данными, адрес структуры OggVorbis_File и структура ov_callbacks с
адресами функций.
Далее в нашей инициализирующей процедуре происходит
подготовка данных к дальнейшей работе в зависимости от затребованного
режима воспроизведения звука – потоковый, или нет. Если проигрывание не
потоковое, то, так же как и в случае с wav файлами, мы ищем в массиве
Buffers уже существующий буфер с заданным звуком и устанавливаем размер
данных для чтения из OGG файла равной длине всего файла. Это достигается
путём произведения количества семплов несжатого файла (вызовом функции
ov_pcm_total()), на длину семпла. Если же наш файл должен проигрываться в
потоковом режиме, то мы устанавливаем количество динамических буферов, и
длину каждого буфера. Далее идет сохранение данных о звуке.
И вот мы добрались до, собственно, реализации технологии потокового проигрывания в OpenAL.
Эта библиотека предоставляет нам механизм поочередного проигрывания
буферов. Источнику можно, вместо единственного буфера, проассоциировать
очередь буферов, которые будут проигрываться последовательно. Алгоритм
обновления данных в буферах мы рассмотрим чуть ниже в функции Update.
Таким образом, в инициализации, мы заполняем динамические
буфера данными, и добавляем их в очередь источника, посредством вызова
функции alSourceQueueBuffers(), которой передаем: идентификатор
источника, количество буферов для добавления, и их идентификаторы.
Теперь давайте рассмотрим функцию ReadOggBlock().
bool remSnd::ReadOggBlock(ALuint BufID, size_t Size) { // Переменные char eof = 0; int current_section; long TotalRet = 0, ret; // Буфер данных char *PCM; if (Size < 1) return false; PCM = new char[Size]; // Цикл чтения while (TotalRet < Size) { ret = ov_read(mVF, PCM + TotalRet, Size - TotalRet, 0, 2, 1, & current_section); // Если достигнут конец файла if (ret == 0) break; else if (ret < 0) // Ошибка в потоке { // } else { TotalRet += ret; } } if (TotalRet > 0) { alBufferData(BufID, Buffers[BufID].Format, (void *)PCM, TotalRet, Buffers[BufID].Rate); CheckALError(); } delete [] PCM; return (ret > 0); }
Вся «соль» функции находится в процедуре ov_read(),
которая считывает данные порциями, и возвращает количество прочитанных
данных. Если возвращает 0, то достигнут конец файла. Затем мы записываем
данные в буфер.
Смена буферов по мере проигрывания звука очень важная задача. Давайте, посмотрим сначала на код.
void remSnd::Update() { if (!mStreamed) return; int Processed = 0; ALuint BufID; // Получаем количество отработанных буферов alGetSourcei(mSourceID, AL_BUFFERS_PROCESSED, &Processed); // Если таковые существуют то while (Processed--) { // Исключаем их из очереди alSourceUnqueueBuffers(mSourceID, 1, &BufID); if (!CheckALError()) return; // Читаем очередную порцию данных и включаем буфер обратно в очередь if (ReadOggBlock(BufID, DYNBUF_SIZE) != 0) { alSourceQueueBuffers(mSourceID, 1, &BufID); if (!CheckALError()) return; } else // Если конец файла достигнут { // «перематываем» на начало ov_pcm_seek(mVF, 0); // Добавляем в очередь alSourceQueueBuffers(mSourceID, 1, &BufID); if (!CheckALError()) return; // Если не зацикленное проигрывание то стоп if (!mLooped) Stop(); } } }
Весь алгоритм построен на анализе состояний буферов в
очереди. Буфер может находится в 3-х состояниях: UNUSED (не использует
ни одним источником), PROCESSED (уже проигран), PENDING (проассоциирован
к источнику, но еще не проигран). Так вот, функция
alGetSourcei(mSourceID, AL_BUFFERS_PROCESSED, &Processed) в
переменную Processed возвращает количество буферов очереди в состоянии
PROCESSED. Далее мы пробегаем по всем этим буферам. Каждого исключаем из
буфера, заполняем новой порцией данных, и снова добавляем в конец
очереди. Реализуется нечто, наподобие конвейера.
Так же, не забудьте немного изменить метод Close() нашего класса:
void remSnd::Close() { alSourceStop(mSourceID); if (alIsSource(mSourceID)) alDeleteSources(1, &mSourceID); if (!mVF) { ov_clear(mVF); delete mVF; } }
Здесь мы деинициализируем структуру OggVorbis_File, и освобождаем память.
Вот и всё. Использование для потоковых OGG файлов такое
же, как и для wav, с единственным отличием – необходимо периодически
вызвать функцию Update. Хочется отметить, что варьируя количество
динамических буферов в очереди и их размер, можно регулировать
потребляемые ресурсы процессора и памяти для работы с потоковым звуком.
Заметьте, что класс получился расширяемый, и вы сами можете попробовать добавить свои форматы аудио файлов.
Полезные ссылки.
1) Сайт Vorbis консорциума. Там можно найти FAQ, примеры и статьи: http://www.vorbis.com/
2) Сайт для разработчиков: http://www.xiph.org/ogg/vorbis/, там же вы найдёте Ogg/Vorbis SDK
1) Сайт Vorbis консорциума. Там можно найти FAQ, примеры и статьи: http://www.vorbis.com/
2) Сайт для разработчиков: http://www.xiph.org/ogg/vorbis/, там же вы найдёте Ogg/Vorbis SDK
Комментариев нет:
Отправить комментарий