Audio 如何在ogg vorbis文件中查找而不将所有文件加载到内存中?

Audio 如何在ogg vorbis文件中查找而不将所有文件加载到内存中?,audio,codec,ogg,seek,vorbis,Audio,Codec,Ogg,Seek,Vorbis,我试图找到一种方法,在不将所有文件加载到内存的情况下,移动到曲目中所需的位置。而不使用vorbisfile,因为该文件存储在远程服务器中。我阅读了文档中关于查找的段落,但无法理解。如果远程服务器允许您使用带有范围头的HTTP GET,您可以通过发送一系列不同部分的请求来“伪造”文件访问,就像您对本地文件一样 假设:文件是Ogg封装的,其中只有Vorbis流 执行HTTP HEAD请求以获取文件的总长度 获取文件的前4KB并“同步”Vorbis头。您可能需要获取更多数据才能完成此操作 获取文件的最

我试图找到一种方法,在不将所有文件加载到内存的情况下,移动到曲目中所需的位置。而不使用
vorbisfile
,因为该文件存储在远程服务器中。我阅读了文档中关于查找的段落,但无法理解。

如果远程服务器允许您使用带有范围头的HTTP GET,您可以通过发送一系列不同部分的请求来“伪造”文件访问,就像您对本地文件一样

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

  • 执行HTTP HEAD请求以获取文件的总长度
  • 获取文件的前4KB并“同步”Vorbis头。您可能需要获取更多数据才能完成此操作
  • 获取文件的最后4KB并“同步”最后一个Ogg页眉以获取总样本数
  • 执行规范描述的双段搜索,用HTTP GET w/Range代替fseek/fread
  • 如果您做得对,在大多数情况下,seeking的传输应该小于100KB

    更新:

    双区搜索有点不直观。。。我们的想法是在文件中跳转,寻找正确的页面,但是每次跳转都会被之前的跳转和当前页面“通知”。。。最好举个例子:

    要在一个包含1000000个样本的文件中获取300000个样本(我假设我们在上面的步骤4中):

  • 将物理文件搜索到{fileStream.Length*.3}
  • 向前阅读,直到找到Ogg页面
  • 检查页面是否是相关Vorbis流的一部分
  • 如果没有,请转至下一个Ogg页面并转至步骤3
  • 检查颗粒位置
  • 如果不是正确的页面,请将物理文件查找到{current position+((300000-granular position)/1000000)*fileStream.Length}并转至步骤2
  • 您已找到正确的页面,但可能需要向后移动一页以获得“预卷”。。。Vorbis要求在所需数据包之前解码1个数据包
  • 可能有更好的算法来实现这一点,但这是基本思想


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

    如果远程服务器允许您将HTTP GET与范围标头一起使用,您可以“伪造”通过发送对不同部分的一系列请求来访问文件,就像对本地文件一样

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

  • 执行HTTP HEAD请求以获取文件的总长度
  • 获取文件的前4KB并“同步”Vorbis头。您可能需要获取更多数据才能完成此操作
  • 获取文件的最后4KB并“同步”最后一个Ogg页眉以获取总样本数
  • 执行规范描述的双段搜索,用HTTP GET w/Range代替fseek/fread
  • 如果您做得对,在大多数情况下,seeking的传输应该小于100KB

    更新:

    双区搜索有点不直观。。。我们的想法是在文件中跳转,寻找正确的页面,但是每次跳转都会被之前的跳转和当前页面“通知”。。。最好举个例子:

    要在一个包含1000000个样本的文件中获取300000个样本(我假设我们在上面的步骤4中):

  • 将物理文件搜索到{fileStream.Length*.3}
  • 向前阅读,直到找到Ogg页面
  • 检查页面是否是相关Vorbis流的一部分
  • 如果没有,请转至下一个Ogg页面并转至步骤3
  • 检查颗粒位置
  • 如果不是正确的页面,请将物理文件查找到{current position+((300000-granular position)/1000000)*fileStream.Length}并转至步骤2
  • 您已找到正确的页面,但可能需要向后移动一页以获得“预卷”。。。Vorbis要求在所需数据包之前解码1个数据包
  • 可能有更好的算法来实现这一点,但这是基本思想


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

    查找ogg文件很困难

    要理解的事情清单

  • 当ogg_页面_数据包(&og)>0时,ogg_页面是一个结束页面
  • 当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_Granules_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_页面_同步时,我的代码会创建第二个块光标来加载下一个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文件很难

    要理解的事情清单

  • 当ogg_页面_数据包(&og)>0时,ogg_页面是一个结束页面
  • 当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_Granules_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);
    }
    
    在下面的代码中,我将添加另一层,它本质上是文件buff