Как искать в файле ogg vorbis, не загружая все файлы в память?
Я пытаюсь найти способ переместиться в нужную позицию на дорожке, не загружая весь файл в память. И без использования vorbisfile
потому что файл хранится на удаленном сервере. Я прочитал параграф в документации о поиске, но не смог его понять.
1 ответ
Если удаленный сервер позволяет вам использовать HTTP GET с заголовками Range, вы можете "подделать" доступ к файлу, отправив кучу запросов для разных частей, как вы бы сделали для локального файла...
Предполагается: файл инкапсулирован в Ogg и содержит только поток Vorbis...
- Выполните HTTP-запрос HEAD, чтобы получить общую длину файла
- Получите первые 4 КБ файла и "синхронизируйте" заголовки Vorbis. Вам может потребоваться получить больше данных, чтобы завершить это.
- Получите последние 4 КБ файла и "синхронизируйте" последний заголовок страницы Ogg, чтобы получить общее количество образцов
- Выполните описанный в спецификации поиск в двух разделах, заменив HTTP GET w/ Range вместо fseek / fread
Если вы все сделаете правильно, в большинстве случаев поиск должен передавать менее 100 КБ.
ОБНОВИТЬ:
Поиск в двухсекционном разделе немного не интуитивен... Идея состоит в том, чтобы перемещаться по файлу в поисках правильной страницы, но каждый переход "информируется" предыдущими переходами и текущей страницей... Например, наверное лучше всего
Чтобы получить 300 000 выборок в файле с 1 000 000 выборок (я предполагаю, что мы на шаге 4 выше):
- Искать физический файл в {fileStream.Length * .3}
- Читайте вперед, пока не найдете страницу Ogg
- Убедитесь, что страница является частью рассматриваемого потока Vorbis
- Если нет, перейдите на следующую страницу Ogg и перейдите к шагу 3
- Проверьте положение гранулы
- Если не правильная страница, найдите физический файл в {текущая позиция + ((300000 - позиция гранулы) / 1000000) * fileStream.Length} и перейдите к шагу 2
- Вы нашли правильную страницу, но, возможно, потребуется переместиться назад на страницу, чтобы получить "предварительную прокрутку"... Vorbis требует, чтобы 1 пакет был декодирован перед желаемым пакетом.
Вероятно, есть лучшие алгоритмы для этого, но это основная идея.
Помните, что позиция гранулы - это количество образцов в конце страницы, поэтому, когда вы найдете правильную страницу, ее позиция гранулы будет немного больше, чем ваша цель.
Искать файлы ogg сложно.
Список вещей, которые нужно понять
- Когда ogg_page_packets(&og) > 0, ogg_page является конечной страницей
- Когда ogg_page_granulepos(&og) > 0, страница имеет последнюю отметку времени в потоке пакетов.
- Когда ogg_page_peek(&oy,&og) == 1, отображается вся страница.
- Когда ogg_page_peek(&oy,&og) == 0, страница является неполной
- Когда ogg_page_peek(&oy,&og) == -1, функция libogg не нашла начало страницы
- vorbis_granule_time(&vd, ogg_page_granulepos(&og)) выводит время в секундах
Вам понадобится эта функция
int buffer_data(){
//oy is an ogg_sync_state https://xiph.org/ogg/doc/libogg/ogg_sync_state.html
//in is just a file
char *buffer=ogg_sync_buffer(&oy,4096);
int bytes=fread(buffer,1,4096,&in);
ogg_sync_wrote(&oy,bytes);
return(bytes);
}
В моем коде ниже я добавлю еще один уровень, который по сути является файловым буфером в дополнение к странице ogg и пакету ogg. По сути, мой код делит пополам только первую синхронизируемую конечную страницу каждого файлового буфера.
Когда я не могу найти ogg_page_sync, мой код создает второй блочный курсор для загрузки следующего файлового буфера размером 4 КБ, пока я не найду синхронизацию страницы или не выйду за границы.
#include <unordered_map>
struct _page_info {
size_t block_number;
double_t time;
ogg_int64_t granulepos;
};
struct _page_info left_page = { .time = 0, .block_number = 0, .granulepos = 0 };
struct _page_info mid_page = { .time = 0, .block_number = 0, .granulepos = 0 };
struct _page_info right_page = { .time = DBL_MAX, .block_number = 0x7FFFFFFFFFFFFFFF, .granulepos = 0x7FFFFFFFFFFFFFFF };
unordered_map<int, double> block_time;
unordered_map<ogg_int64_t, _page_info> page_info_table;
ogg_page og;
while (left <= right) {
//Seek to block
size_t mid_block = left + (right - left) / 2;
int block = mid_block;
if (block_time.has(block)) {
//Check whether this block has been visited
break;
}
//clear the sync state
ogg_sync_reset(&oy);
file.seek(block * buffer_size);
buffer_data();
bool next_midpoint = true;
while (true) {
//keep syncing until a page is found. Buffer is only 4k while ogg pages can be up to 65k in size
int ogg_page_sync_state = ogg_sync_pageout(&oy, &og);
if (ogg_page_sync_state == -1) {
//Give up when the file advances past the right boundary
if (buffer_data() == 0) {
right = mid_block;
break;
} else {
//increment block size we buffered the next block
block++;
}
} else {
if (ogg_page_sync_state == 0) {
//Check if I reached the end of the file
if (buffer_data() == 0) {
right = mid_block;
break;
} else {
block++;
}
} else {
//Only pages with a end packet have granulepos. Check the stream
if (ogg_page_packets(&og) > 0 && ogg_page_serialno(&og) == vo.serialno) {
next_midpoint = false;
break;
}
}
}
}
if (next_midpoint)
continue;
ogg_int64_t granulepos = ogg_page_granulepos(&og);
ogg_int64_t page_number = ogg_page_pageno(&og);
struct _page_info pg_info = { .time = vorbis_granule_time(vd, granulepos), .block_number = mid_block, .granulepos = granulepos };
page_info_table[page_number] = pg_info;
block_time[mid_block] = pg_info.time;
mid_page = pg_info;
//I can finally implement the binary search comparisons
if (abs(p_time - pg_info.time) < .001) {
//The video managed to be equal
right_page = pg_info;
break;
}
if (pg_info.time > p_time) {
if (pg_info.granulepos < right_page.granulepos)
right_page = pg_info;
right = mid_block;
} else {
if (pg_info.granulepos > left_page.granulepos)
left_page = pg_info;
left = mid_block;
}
}
Когда вы закончите, вы по существу возвращаете ogg_pages, пока не найдете желаемый ogg_packet.
Вот трюк для вычисления отметки времени с последовательно увеличивающимися пакетами
while(ogg_sync_pageout(&oy, &og) > 0)
ogg_stream_pagein(&vo, &og);
ogg_int64_t last_granule = ogg_page_granulepos(&og);
ogg_int64_t total_granule = ogg_page_packets(&og));
while(ogg_stream_packetout(&vo, &op) > 0 ) {
double time = vorbis_granule_time(&vd, last_granule - total_granule--);
}
https://xiph.org/ogg/doc/libogg/reference.html
https://github.com/xiph/theora/blob/master/examples/player_example.c