【问题标题】:How to seek in ogg vorbis file without loading all file into memory?如何在 ogg vorbis 文件中查找而不将所有文件加载到内存中?
【发布时间】:2014-08-12 10:17:32
【问题描述】:

我正在尝试找到一种方法来移动到轨道中的所需位置,而无需将所有文件加载到内存中。而没有使用vorbisfile,因为文件存储在远程服务器中。我阅读了文档中关于寻求但无法理解的段落。

【问题讨论】:

    标签: audio codec ogg seek vorbis


    【解决方案1】:

    如果远程服务器允许您使用带有 Range 标头的 HTTP GET,您可以通过向不同部分发送一堆请求来“伪造”文件访问,就像您对本地文件所做的那样......

    假设:该文件是 Ogg 封装的,并且其中只有 Vorbis 流...

    1. 执行 HTTP HEAD 请求以获取文件的总长度
    2. 获取文件的前 4KB 并“同步”Vorbis 标头。您可能需要获取更多数据才能完成此操作。
    3. 获取文件的最后 4KB 并“同步”最后一个 Ogg 页面标题以获取总样本数
    4. 执行规范描述的二分搜索,用 HTTP GET w/ Range 代替 fseek / fread

    如果你做对了,在大多数情况下,寻找应该传输不到 100KB。

    更新:

    二分搜索有点不直观......想法是在文件中跳转以寻找正确的页面,但每次跳转都是由先前的跳转和当前页面“通知”的......最好举个例子:

    在一个包含 1,000,000 个样本的文件中寻找 300,000 个样本(我假设我们在上面的第 4 步):

    1. 寻找物理文件到 {fileStream.Length * .3}
    2. 继续阅读,直到找到 Ogg 页面
    3. 检查页面是否是相关 Vorbis 流的一部分
    4. 如果没有,请转到下一个 Ogg 页面并转到第 3 步
    5. 检查颗粒位置
    6. 如果不是正确的页面,寻找物理文件到{当前位置 + ((300000 - 颗粒位置) / 1000000) * fileStream.Length} & 转到第 2 步
    7. 您找到了正确的页面,但可能需要向后移动一个页面才能获得“前贴片”... Vorbis 需要在所需数据包之前解码 1 个数据包。

    对此可能有更好的算法,但这是基本思想。

    请记住,颗粒位置是页面末尾的样本计数,因此当您找到正确的页面时,其颗粒位置将比您的目标大。

    【讨论】:

    • 我有像 read()、seek()、write() 这样的 API,所以我可以像处理计算机上的文件一样处理远程文件。问题是我只是不明白 ogg vorbis 中的二分搜索是如何工作的。如果 ogg 页面可以有不同的大小,我该如何移动到某个页面?
    【解决方案2】:

    寻找 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_particle_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 时,我的代码会创建第二个块光标来加载下一个 4k 文件缓冲区,直到我找到页面同步或超出边界。

    #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

    【讨论】:

      猜你喜欢
      • 2010-12-27
      • 1970-01-01
      • 2015-07-13
      • 2011-02-08
      • 1970-01-01
      • 1970-01-01
      • 2016-02-25
      • 2015-06-21
      • 1970-01-01
      相关资源
      最近更新 更多