Содержание
Весь процесс можно кратко описать следующими последовательностями (пока все просто):
Цикличное формирование данных связано с тем, что:
В документации есть более подробное описание контейнера ogg.
Здесь также важно понимать что сжатие данных происходит за счет манипуляции с битрейтом (частота подачи/поступления данных, немного теории по этому поводу здесь). Если очень кратко, то процесс кодирования это преобразование постоянного битрейта (constant bitrate) PCM данных в переменный (variable) или усредненный (average). Здесь есть немного информации о том, как производить настройку битрейта для vorbis.
Весь процесс кодирования, на примере нижеизложенного кода, можно описать так:
Примечание: В рассмотренном ниже коде подразумевается запись в m_pFile типа FILE*, но запись возможна и в область оперативной памяти. Так как vorbis кодек не накладывает ограничений на битность семпла, то в данном коде было использовано 2 байта на семпл (int16_t)
cpp//логический поток ogg ogg_stream_state oOggStream; //страница битового потока ogg_page oOggPage; //пакет сырых данных для декодирования ogg_packet oOggPacket; //статическая информация (настройки) битового потока vorbis_info oVoInfo; //комментарии vorbis_comment oVoComment; //состояние кодека https://xiph.org/vorbis/doc/libvorbis/vorbis_dsp_state.html vorbis_dsp_state oVoMainState; //блок аудиоданных https://xiph.org/vorbis/doc/libvorbis/vorbis_block.html vorbis_block oVoDataBlock;
cpp//инициализация кодировщика (статических данных) с переменным битрейтом (variable bitrate) vorbis_info_init(&oVoInfo); iRetCode = vorbis_encode_init_vbr(&oVoInfo, pOutDesc->u8Channels, pOutDesc->uSampleRate, 0.1); /* можно заюзать усредненный битрейт (average bitrate): iRetCode = vorbis_encode_init(&vi,pOutDesc->u8Channels, pOutDesc->uSampleRate,-1,128000,-1); * но в данной реализации пусть кодировщик сам разбирается :) */ if(iRetCode) return false; //инициализируем комментарий vorbis_comment_init(&oVoComment); vorbis_comment_add_tag(&oVoComment, "ENCODER", "SkyXEngine plugin [class 'codec_ogg']"); vorbis_analysis_init(&oVoMainState, &oVoInfo); vorbis_block_init(&oVoMainState, &oVoDataBlock); //инициализация битового потока и присвоение ему серийного номера srand(time(NULL)); ogg_stream_init(&oOggStream,rand()); //создание и инициализация заголовков (идентификационного, комментариев, кода) ogg_packet oHeaderId, oHeaderComment, oHeaderCode; vorbis_analysis_headerout(&oVoMainState, &oVoComment, &oHeaderId, &oHeaderComment, &oHeaderCode); ogg_stream_packetin(&oOggStream, &oHeaderId); ogg_stream_packetin(&oOggStream, &oHeaderComment); ogg_stream_packetin(&oOggStream, &oHeaderCode);
cppwhile(!iEndOfStream) { iRetCode = ogg_stream_flush(&oOggStream, &oOggPage); if(iRetCode == 0) break; fwrite(oOggPage.header, 1, oOggPage.header_len, m_pFile); fwrite(oOggPage.body, 1, oOggPage.body_len, m_pFile); }
cpp//количество блоков семплов (количество каналов * байт на семпл) int iCountBlocks = uSize/ (pOutDesc->u8BlockAlign); /* получаем выделенный массив (по количеству каналов) массивов (по количеству семплов) aaBuffer[iChannel][iSample] */ float **aaBuffer = vorbis_analysis_buffer(&oVoMainState, iCountBlocks); //заполняем выделенный float массив нормализованными данными [-1.0, 1.0] for(int i = 0; i < iCountBlocks ; ++i) { for(int iChannels = 0; iChannels<pOutDesc->u8Channels; ++iChannels) { int16_t i16Sample = ( (int16_t*)pData )[i]; aaBuffer[iChannels][i]=float(i16Sample)/32768.f; } } //сообщаем кодировщику что поступили данные для записи vorbis_analysis_wrote(&oVoMainState, iCountBlocks);
pOutDesc->u8BlockAlign - размер блока семплов на каждый канал в байтах, если на 1 семпл выделяется 2 байта (int16_t), и звук имеет 2 канала тогда u8BlockAlign = 2 канала * 2 байта = 4 байта на 1 блок
UPDATE. Приведенный выше код успешно работал на 32 битной сборке, однако с тем же ogg файлом на 64 битной сборке программа падала с ошибкой переполнения стека. Проблема в том, что внутри @%vorbis_analysis_wrote выделяется память на стеке по общему количеству блоков, это может привести к переполнению стека если передать большое количество блоков. Решение проблемы - цикличный сборс данных пачками (например по 1024 блока):
cpp//количество блоков семплов (количество каналов * байт на семпл) int iCountBlocks = uSize/ (pOutDesc->u8BlockAlign); //по сколько блоков записывать за один раз int iPartBlocks = 1024; //количество уже записанных блоков int iCountReadedBlocks = 0; //циклом (частями) передаем данные кодировщику while (iCountReadedBlocks < iCountBlocks) { //количество уже прочитанных сэмплов uint32_t uCountReadedSamples = (iCountReadedBlocks*pOutDesc->u8Channels); //количество блоков для текущей итерации int iCurrBlocks = iPartBlocks; if (iCountBlocks - iCountReadedBlocks < iPartBlocks) iCurrBlocks = iCountBlocks - iCountReadedBlocks; float **aaBuffer = vorbis_analysis_buffer(&oVoMainState, iCurrBlocks); for (int i = 0; i < iCurrBlocks; ++i) { for (int iChannels = 0; iChannels<pOutDesc->u8Channels; ++iChannels) { int16_t i16Sample = ( (int16_t*)pData)[uCountReadedSamples + i]; aaBuffer[iChannels][i] = float(i16Sample) / 32768.f; } } vorbis_analysis_wrote(&oVoMainState, iCurrBlocks); iCountReadedBlocks += iCurrBlocks; }
cpp//если еще не дошли до конца битового потока, тогда продолжаем запись while(!iEndOfStream) { /*разбивка несжатых данных на блоки, если не удалось, тогда сообщаем что данных больше не будет если не сообщить об этом, то не все данные будут записаны, не получится дойти до конца битового потока, потому что запись идет постраничная и последняя неполня страница не будет записана */ if(vorbis_analysis_blockout(&oVoMainState, &oVoDataBlock)!=1) vorbis_analysis_wrote(&oVoMainState, 0); //поиск режима кодирования и отправка блока на кодировку vorbis_analysis(&oVoDataBlock, NULL); vorbis_bitrate_addblock(&oVoDataBlock); //получение следующего доступного пакета while( vorbis_bitrate_flushpacket(&oVoMainState, &oOggPacket) ) { //отправка пакета в битовый поток ogg_stream_packetin(&oOggStream, &oOggPacket); while(!iEndOfStream) { //формирование пакетов в страницы и отправка в битовый поток int result=ogg_stream_pageout(&oOggStream, &oOggPage); if(result==0) break; fwrite(oOggPage.header, 1, oOggPage.header_len, m_pFile); fwrite(oOggPage.body, 1, oOggPage.body_len, m_pFile); //если все записано (находимся в конце битового потока), сообщаем о завершении if(ogg_page_eos(&oOggPage)) iEndOfStream=1; } } }
cppogg_stream_clear(&oOggStream); vorbis_block_clear(&oVoDataBlock); vorbis_dsp_clear(&oVoMainState); vorbis_comment_clear(&oVoComment); vorbis_info_clear(&oVoInfo);
Страница на официальном сайте проекта где крайне лаконично описан процесс vorbis кодирования PCM данных. А здесь по ссылкам черпал информацию по кодированию из первоисточника.
Пример кодирование на github, помог быстро понять что к чему, однако местами оказался сложнее чем есть на самом деле.
Есть еще Ogg media, но это уже совсем другой проект.