Как искать в файле ogg vorbis, не загружая все файлы в память?

Я пытаюсь найти способ переместиться в нужную позицию на дорожке, не загружая весь файл в память. И без использования vorbisfileпотому что файл хранится на удаленном сервере. Я прочитал параграф в документации о поиске, но не смог его понять.

1 ответ

Если удаленный сервер позволяет вам использовать HTTP GET с заголовками Range, вы можете "подделать" доступ к файлу, отправив кучу запросов для разных частей, как вы бы сделали для локального файла...

Предполагается: файл инкапсулирован в Ogg и содержит только поток Vorbis...

  1. Выполните HTTP-запрос HEAD, чтобы получить общую длину файла
  2. Получите первые 4 КБ файла и "синхронизируйте" заголовки Vorbis. Вам может потребоваться получить больше данных, чтобы завершить это.
  3. Получите последние 4 КБ файла и "синхронизируйте" последний заголовок страницы Ogg, чтобы получить общее количество образцов
  4. Выполните описанный в спецификации поиск в двух разделах, заменив HTTP GET w/ Range вместо fseek / fread

Если вы все сделаете правильно, в большинстве случаев поиск должен передавать менее 100 КБ.

ОБНОВИТЬ:

Поиск в двухсекционном разделе немного не интуитивен... Идея состоит в том, чтобы перемещаться по файлу в поисках правильной страницы, но каждый переход "информируется" предыдущими переходами и текущей страницей... Например, наверное лучше всего

Чтобы получить 300 000 выборок в файле с 1 000 000 выборок (я предполагаю, что мы на шаге 4 выше):

  1. Искать физический файл в {fileStream.Length * .3}
  2. Читайте вперед, пока не найдете страницу Ogg
  3. Убедитесь, что страница является частью рассматриваемого потока Vorbis
  4. Если нет, перейдите на следующую страницу Ogg и перейдите к шагу 3
  5. Проверьте положение гранулы
  6. Если не правильная страница, найдите физический файл в {текущая позиция + ((300000 - позиция гранулы) / 1000000) * fileStream.Length} и ​​перейдите к шагу 2
  7. Вы нашли правильную страницу, но, возможно, потребуется переместиться назад на страницу, чтобы получить "предварительную прокрутку"... Vorbis требует, чтобы 1 пакет был декодирован перед желаемым пакетом.

Вероятно, есть лучшие алгоритмы для этого, но это основная идея.

Помните, что позиция гранулы - это количество образцов в конце страницы, поэтому, когда вы найдете правильную страницу, ее позиция гранулы будет немного больше, чем ваша цель.

Искать файлы ogg сложно.

Список вещей, которые нужно понять

  1. Когда ogg_page_packets(&og) > 0, ogg_page является конечной страницей
  2. Когда ogg_page_granulepos(&og) > 0, страница имеет последнюю отметку времени в потоке пакетов.
  3. Когда ogg_page_peek(&oy,&og) == 1, отображается вся страница.
  4. Когда ogg_page_peek(&oy,&og) == 0, страница является неполной
  5. Когда ogg_page_peek(&oy,&og) == -1, функция libogg не нашла начало страницы
  6. 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

https://xiph.org/vorbis/doc/libvorbis/reference.html

https://xkcd.com/979/

https://xiph.org/oggz/doc/group__basics.html

Другие вопросы по тегам