Ogg 格式_ogg转mp3的免费软件

Ogg 格式_ogg转mp3的免费软件【原作者:神武竹•未经允许,禁止转载】「前言」•警告⚠️:请区分Ogg与OGG*本文所述Ogg为一种开源多媒体容器格式,由Xiph.Org基金会开发支持,常见于音视频文件和网络传输

【原作者:神武竹 • 未经允许,禁止转载】

「前言」

•请区分Ogg与OGG

* 本文Ogg为一种开源多媒体容器格式,由Xiph.Org基金会开发支持,常见于音视频文件和网络传输。

* Oracle Golden Gate(OGG)一种基于日志的结构化数据复制软件,由Orcale甲骨文公司开发支持。

经典混淆:

8abf007303234e729f3efe99cca03e22.png

72d78e211d7f4b38b23fbd9b509da4c5.png

(仅指出错误,没有针对性。上两篇文章虽有纰漏,也不乏参考价值。重点:Ogg不是一种压缩格式)

• 本文主要参考文献【Xiph.Org基金会官网Ogg规格文档】

• 辅助资料:2006浙江大学 冯炯硕士论文《Ogg/Vorbis解码器设计实现》

( 代码实践性很强,只有少量纰漏,致敬前辈!)

• 代码示例均为Java语言。其他语言程序员可跳过示例,了解原理即可。(还没写)

• 本文技术性较强,读者应有 基础计算机知识 和 编程基础 。凡重要、晦涩、存疑处都加【】注释。

• 本文涉及英文资料,无官方中文翻译,因而使用作者翻译,感谢指正。凡重要或易混淆的译名也加【】注释。

        Ogg是Xiph.Org基金会开发的开源流媒体容器格式。适用于Opus、Vorbis、Theora、Kate、PCM等多种音视频编码格式。

        中网有很多Ogg技术文章。但都有通病:

一是翻译混乱,Xiph官方无中文支持,导致一些文章的翻译冗余或用机翻,更有甚者上来就甩你一篇英文;二是胡乱删减,官方英文文档极为详细,有些文章省略过多细节,连“逻辑流”“物理流”的含义都不解释,你压根找不到真正解释清楚了“多路复用”的文章;三是没有实践,要么不上代码空谈连篇,要么直接套其它代码库;四是反复“转载”,层层降值。

        本人第二篇技术性文章,一方面 希望写一篇 可供参考 的文章;另一方面承接第一篇Xiph基金会,提高文章的专业性,顺便练练文笔~

       图片均源自网络,侵删 |

       感谢阅读与指正

【原作者:神武竹 • 未经允许,禁止转载】


「概述」

        Ogg是一种流媒体容器格式。提供了对流式【连续的按时间排列的数据】数据进行 分割、存储、排序、按时间顺序查找以及寻错保护【数据丢失或错误时进行调整】 的框架,也可以将多个流混合,即多路复用,形成视频或其他复杂文件。

        Ogg 是容器【container】,是个箱子,类似微软的Wav格式。Ogg文件理论上可以装任何数据,而不仅仅是音视频,甚至可以存 pdf 或 jpg (只要有对应的解码器)。Ogg容器和内部存储的数据 / 元数据【metadata,描述数据的数据】本质上是分离的,好比 箱子跟箱子里放的东西 无关,称之为解耦。Ogg可直接作容器,也能作另一个更复杂(可能非线性)容器的组分。

        Ogg同时指Ogg多媒体项目(原名Squish),该项目包括Ogg、Vorbis、Theora(Opus不属于Ogg项目)。参考作者第一篇文章【Xiph基金会】。

4365d6b20ddc4b768318b650b445039c.png

【原作者:神武竹 • 未经允许,禁止转载】


「设计特点」

        简易。实现容易,无论是分页、分段还是多路复用;不需要重新打包源数据;字节对齐【你就理解为以字节为单位】;编解码复杂度持平;容器结构固定。

        流式。Ogg主要用于流媒体数据【流式传输的多媒体数据,适用网络传输即时播放】传输 / 存储 / 播放。编解码器 能且只能 一次性、不回头地编解码整个Ogg流,不需要也不能 花时间查找数据或使用多余的缓冲区。Ogg适用于面向流的TCP、本地文件;UDP、RTP(面向数据报)不宜搭配Ogg。

        线性。Ogg 摒弃了索引的设计,理由是它对流媒体是多余的,Ogg拒绝非线性访问,允许但强烈不推荐 优化编码(如二次编码,分两次扫描数据)或 交互解码。

       绝对颗粒位置【absolute granule position】的设计,具体见 基本概念-页。相当于绝对时间戳【单纯指时间标记】。不同类型数据的颗粒位置有不同的意义,编解码器可以自行定义、使用,非常灵活;在复合流中又可以互相转化,使音频、视频以及字幕/弹幕能统一装在容器里,进行播放和跳转。(而不是把音频、视频分两块)

        空间高效<未必>。每在65307Bytes约64kB以下,通常一页4~6kB,其中容器数据只有27 ~ 282字节,空间开销0.4 ~ 0.7%。

        捕获/定位【capture,以下用“定位”】。每页大小 < 64kB,所以最多读取128kB(130613Bytes)就能定位Ogg流,因而可以从流的任何一处开始解码。

        查找【seek 。能相对简单地进行“低精度”或“高精度”查找。参见 高级-查找。

        多路复用【Muxtiplex,简称mux】。将多个基本流交错成复合流,使音频、视频、弹幕/字幕 等混合在一起 ,可以同时解码播放。见 高级-多路复用。

        完全解耦。容器与 数据/元数据 是分离的;容器和编解码器也是分离的;对复合流而言,不同基本流间的关系跟容器本身更是毫无关系的。Ogg只是个箱子,里面装的什么、怎么用、什么关系 跟箱子本身没有一毛钱关系,Ogg只负责把数据按你定好的规则放进去、存好、取出来。所以Ogg可以存放 各种各样 的数据。当然解耦有一定弊端,见 高级-Skeleton。


「彩蛋」

        很多人误认为Ogg是音频格式。实际上Ogg容器存储的Vorbis文件才是一种音频格式,Ogg还可以存储FLAC、Opus、PCM音频,以及Theora、Dirac、Daala视频、Kate、XSPF、JSPF文本。只不过最初只有Vorbis装在Ogg里,合称Ogg Vorbis,简称Ogg。后来虽有了其他格式,大家都叫习惯了;于是2007年Xiph将错就错,规定只有Ogg Vorbis文件的拓展名为“.ogg”,而视频换成“.ogv”、音效换成“.oga”等。

        Ogg目前规格版本还是0,只更新了一个可选的Skeleton【见Skeleton】来辅助解码,可选索引还在草案中。(其实Xiph就是懒)收回这句话,Xiph2012年前后提出了Ogg 2 — TransOgg,针对传输和安全性对Ogg进行了改进。主要改进有:强制性元数据流Skeleton,最大系带值由255降至252,可选的索引Ogg Index,强制编解码器信息写入Skeleton,元数据增强等等。然而和Ogg Index一样,尚在草案中(10年了喂!)。参见【XiphWiki TransOgg】。

        Ogg的名字源于上世纪世界第三款网络游戏 Netrek 里的梗:“Ogging”,不能写成“OGG”,但老有人写错。

        梗百科:Netrek 是一款开源网络游戏,也是世界上第一款网络团队游戏。玩家作为一艘星舰舰长与敌舰干架。游戏分两队,通过消灭敌人而占领所有行星即胜利。

742182e8a23647709b98a0afa9de8a8b.png Netrek 游戏截图

        其中玩家编号是    <队伍首字母> + 十六进制数字(0~9 a~f)   。而观众(观察模式的玩家) 或者 人机 是    <队伍首字母> + g ~ z字母    。

        1990年11月某日,卡内基梅隆大学计算机室里三个玩家Jay Hui,Byron Sinor,Steve Russel正在Netrek开黑。三人都是游戏新手,菜。

        服务器腐竹Terence(就他把游戏带进学校)黑进游戏伪装成人机,编号为“Og”(Orions猎户队)。Terence可是大佬级别,他决定给三个菜鸟“上上课”。他秀出一套高阶操作:自杀性袭击 —- 即开局 不管资源不发育 直接全买武器 跟正在发育的对面硬刚。

        于是,“机器人” Og 大杀四方,血洗三军,给Steve吓坏了。安静的计算机室里,Steve突然暴喝:“Help!It’s ‘O’ ‘g’  !”( “救命!Og来了”,念两个字母)然后继续喊“Hellllllllpppp!It’s Og!It’s Ogggggggg!”( “救命啊啊啊啊,是Og!”念成“奥格”)。

        Ogg于是乎成了梗,游戏里代指这种自杀性袭击战术“Ogging”;Ogg在生活中可以表示“不考虑退路、强行地做”。

        Ogg 始于1993年Monty开发的大项目,项目目标是开发压缩多媒体格式。其中音频压缩的软件叫做”Squish”。“squish”直译“挤压”引申为“压缩”。Squish网页公布后,Monty得知“Squish”这个名字已经被一家邮递公司使用,为避免侵权,在基友Mike建议下,改成“OggSquish”。

        为什么叫Ogg呢?Monty构思Squish的90年代,美国PC的CPU几乎都是Intel i386芯片,i486才刚刚推出。但Monty开发的压缩算法非常复杂。

        “啊哦,他们(用户)至少需要一个i486芯片” “如果用Squish软件播音乐,半个计算单元都闲不下来。” (monty)这当然是一种“不留退路”,也就是Ogging了。

        后来,Ogg 变成容器格式的名字,而Squish变成了Ogg编解码器的名字(已淘汰)。总之,Ogg不是人名,也不能写成”OGG”。

       至于它的徽标…呃…

76a28da569714b9da5fd542aaf5dfdf5.pngd46c74e7014c4098b8d7148e2f0c437d.gif早期OggSquish徽标

你看清了吗 ?徽标取材于北欧神话“雷神战蛇”的故事。但这 分辨率 和 配色 真是一言难尽(Squish与Ogg已经无关了;所以封面没用这张图)。寓意嘛……官网说……

       “蛇的身子很像正弦曲线,雷神索尔(Thor)举起雷神之锤(Mjollnir)要 砸扁(compress,双关“压缩”)长的像 周期信号(知道傅立叶变换的话你就懂了)耶梦加得(Jörmungandr蛇名)…… 是不是有感觉了?”

       【此时无声胜有声】

       


「基本概念」

<Bitstream>

       常译“位流”,“比特流”或“数据流”,指流式计算机数据。下文统一“比特流”。

<包>

        Packet或“数据包”。逻辑流由包组成。源数据被分成小块小块的数据,即包(怎么分不是Ogg的事)。包大小从0到无限字节(可以为0!),不需要框架信息(即把包首尾相连,很难重新分成包),框架信息由Ogg提供。不需要 不等于 一定没有

       学过编程可能更容易理解:包就是一个任意大小字节byte数组,是从源数据上分割下来的。0长度的包叫空包【nil】

       虽然包可以随便分割出来,但更推荐分解能直接解码的相对独立的包。包可以对应视频帧、音频帧;单个包在500 ~ 1 KB 大小时效率最大,且可以直接用于UDP传输(数据报限制1024字节)。(明面上“无限制”,结果还有这么多“建议”,这波上屋抽梯)

       逻辑流第一个包叫BOS包【Beginning Of Stream,流的开头】,最后一个包叫EOS包【End Of Stream,流的末尾】

<逻辑流>

        Logic Bitstream。“逻辑意义上的数据流”,指编码器创建的比特流。可以看作 包构成的流,或看作一个“数据源”。逻辑流中的数据必须是同一类型的:比如一首歌(音频)可作一个逻辑流,一部无声电影(视频)可以当一个逻辑流,一篇文章(文本)也能作逻辑流。但一首MV(音频 + 视频)最好分成两个逻辑流;带弹幕的动画(音频 + 视频 + 文本)应分为三个逻辑流。

       逻辑流就是Ogg容器要装的数据。

6d9686486b414228b15d3afac692b01c.png

逻辑流 由(通常)无框架信息的包 组成

<物理流>

        Physical Bitstream。“物理意义上的流”,指Ogg编码器输出的比特流。可以看作 页组成的新流。要么是基本流,要么是复合流,但必须是 单个由页组成的流。物理流即Ogg原始数据,相当于装完数据后的整个容器。

        物理流中的每个基本流都有一个唯一流序列号【基本概念-页】,标记在页上,按序列号即可把混合的页重新组装成基本流。

<基本流>

         Elementary Stream 或 Elementary Physical Bitstream。单个逻辑流分页形成的新流叫做基本流。它可以单独作物理流,也可以多路复用后变成复合流。

         Logical Ogg Bitstream。逻辑Ogg流。根据官网文档,这个单词与 基本流 应该同义。这也导致很多文章混用 逻辑流 和 基本流 两个概念,无伤大雅。区别可能在于:“基本流” 强调 “一种物理流 / 复合流的一部分”,而“逻辑Ogg流” 强调 “构成物理流的一部分”。

         一个基本流最少只有一页(BOS)。

<复合流>

         Multiplexed Bitstream【 Multiplexed Physical Bitstream,或“复合物理流”/“混合物理流”;mixed streams,或译“混合流”;又有multiplexed streams,强调“多路复用的流”。以上统一“复合流”】。由多个基本流 多路复用 构成。见 多路复用。

<链>

        Chain【或“链条”】

        已知   1 逻辑流 分页 ->  1 基本流 | n 基本流 混合 -> 复合流 | 基本流 / 复合流 -> 物理流。多个物理流串联又成了一个新流,即链【chain】。“串联”叫做Chaining【意译】。链中的单个物理流叫做节【link,直译“链条”,强调链中的一环,意译为“节”】。

        注意,节与节不能混合、交错,只能串联。前一节没有结束,后一节不能开始。此时每个基本流的流序列号在整个链内都必须是唯一的。链中的每一节都是独立的物理流。如图是两个物理流组成的链:

d266cd4083084d70894762d4e2c2a1bc.png

方块表示页  同颜色表示同一逻辑流 右下角页号

initial-BOS页 term-EOS页

        两个节都是复合流。前一个物理流 有 红/蓝/绿 三个逻辑流,后一个有 黄/紫 两个逻辑流。只有红蓝绿三个逻辑流的最后一页都出现之后,即三个流结束之后,黄/紫逻辑流才能开始。

<段>

         Segment【或“片”】包分成页,首先要把每个包分成更小的数据:段。本质上还是个字节数组,长度在0 ~ 255 之间,段的长度又叫 系带值【lacing value】。见 基础编解码-数据包分割。

复习一下:

|     逻辑流 分页 ->       基本流  

 |    基本流/复合流 属于 物理流  

|   基本流 多路复用 ->   复合流

*****************敲黑板*****************

<页>

         Page【或“页面”】,Ogg物理流的基本单元。页与页是相互独立的。整个容器由页构成,页本身 和 页与页/页与流 的关系是Ogg规格的核心。所有容器功能都集中在页中。页的大小可变。

页头(Page Header)

        页头是页的功能结构,包含了全部容器信息。结构如图:

6f9157b73995421bb35c2efb0cf0bfd9.png

图片来自维基百科

字节1~4(对应图中0 ~ 3)

捕获域【capture pattern,或“捕获模式”】

      “捕获”指「设计特点」里的捕获。“捕获Ogg流”指找到Ogg流中某一页的精确位置,下文译为“定位”。Ogg流中页与页首尾相连,只要找到了一页的准确位置,紧接着就是下一页(但是不能找上一页)。

       捕获域就是页的标志,四个字节固定为 0x4F676753 ,ASCII为“OggS”,即“Ogg Stream”,注意大小写。

       解析Ogg文件时,捕获域可以当幻数【Magic Number,又称“魔数”,在数据首部设置的固定数字,以识别文件类型】使用;而在网络传输时,捕获域使程序可以在流的任意一处定位并解析。见 基础编解码-捕获。

字节5

流结构版本【stream_structure_version】:

        即Ogg容器的版本,目前只能为0。

字节6

头部类型【header type flag】:

          页头的标志,标记此页在流中的上下文【context,指一个对象与其前后对象】关系。这个字节是按位【bit】取值的(C 语言的朋友肯定熟悉)。在这个字节(8位)里,每位的 1 或 0 都有含义。Ogg只用了后3位,剩下的留未来使用。

     (从右向左)

        第一位:如果为0,此页以一个新包开始,是新页【Fresh】;如果为1,则此页的数据 与 上一页的最后一个未完成的包 是同一个包,即这一页是上一页的续页【Continued】。当然,续页也可能只包含一个包而且还没续完,则下一页还是续页。

        第二位:BOS位。如果为1,表示此页是其所属逻辑流的第一页【Beginning Of Stream】。对应的它的页号【见下文】必须为0。见 基础编解码 – 填页头。

        第三位:EOS位。如果为1,表示此页是其所属逻辑流的最后一页【End Of Stream】。一个基本流可以只有一页,这页同时是BOS和EOS页。见 基础编解码 – 填页头。

        综上所述,BOS页是流的第一页,所以一定是个新页(前面没有其他页)。所以如果第二位为1,则第一位一定为0。则这个字节有6种个有效值:000、001、010、101、100、110。十进制即:0 1 2 4 5 6。

字节7~14

绝对颗粒位置:

         8字节的有符号整数。下面长篇大论:

       在流中实现跳转(seek)和多路复用,都需要有时间戳。根据时间,程序便可以精确跳到流的某一处 开始解码;根据时间,编码器才能把 单位不同 的  视频/音频/字幕  在混合的时候 按时间顺序排列。

        但我们又说,Ogg容器和数据是完全分离的:想知道总时间,要从头开始解码!

        为了实现 解耦 + 时间戳 两大目标,Ogg采用了绝对颗粒位置的设计。Ogg要求颗粒位置必须是绝对时间戳【流的总时间,如总样本数/总帧数】,叫做“绝对颗粒位置”。注意! “总时间/绝对时间” 是指从 <流开始> 到 <这个包> 为止 的时间,所以一个包的 总时间 / 绝对时间 相当于  前面所有包加上自己  的时间之和。

        颗粒位置本身只是个数字,它的单位由数据类型决定:对于音频,单位是 个(PCM样本)【关于PCM编码,可参见《PCM数据格式》】;对于视频,一般单位是 帧 ;也可以直接以毫秒为单位。颗粒位置可以和实际时间互相转换,例如音频总时间为 样本数➗样本率,视频总时间是 帧数➗帧率。颗粒位置如何转换,取决于编解码器,与容器无关,不需要修改Ogg代码,符合开闭原则【修改关闭,拓展开放】。

        颗粒位置的转换完全由编解码器决定,除了时间之外,还可以在颗粒位置里填充其他数据(保证颗粒位置仍然按非递减顺序排列),从而添加Ogg不提供的功能。Theora视频格式就是这么做的。

        视频帧分为关键帧和中间帧,每两个关键帧之间的中间帧必须依赖前一个关键帧才能解码。所以跳转时,必须跳转到关键帧处;但是Ogg又没有标记关键帧的设计,这时候,颗粒位置的灵活性就看出来了。

        Thoera把8字节分成 6bytes<总帧数> + 2bytes<偏移量>。每个关键帧的<总帧数> = 之前所有关键帧(包括自己)数 + 之前所有非关键帧数,依赖此关键帧的所有中间帧的<总帧数>等于关键帧的<总帧数>,<偏移量>表示此帧与关键帧的距离(单位帧),关键帧的<偏移量>为0。

        这样,一 保证颗粒位置仍然非递减排序,二 把关键帧位置巧妙嵌入Ogg容器中。

        因为单位不同(视频帧/PCM样本就不同),不同的颗粒位置可能转换为同一时间:比如可能 视频的 640 帧 =  音频的 12800 个样本 =  16 秒。要把它们转换成同一单位(建议是 秒/毫秒 )再跳转/多路复用。

        烧脑吗?回归原题。

        每个包都有自己的时间和总时间,每个包都可以算出自己的绝对颗粒位置。包的时间不一定是固定的。下一个包的“总时间” = 上一个包的“总时间” + 下一个包的“时间”。一个包的“时间”可以为0。见 基础编解码-填页头。下文提到颗粒位置时,请一定注意“总时间”和“时间”的区别。

        绝对颗粒位置 按Ogg规定  是本页最后一个 <完成的包> 的颗粒位置,如果这一页的最后一个包还没完成(见 基础编解码-数据包分页),就取倒数第二个数据包。如果整整一页只有一个包而且还没完成,颗粒位置取 补码 -1页的颗粒位置可以为-1,包不可以。

字节15~18

流序列号【stream serial number】:

         即页所属逻辑流的序列号。用来识别逻辑流,只有在多路复用时有用。可以是-1外的任何值(-1表示空)。

字节19~22

页号【page sequence no】:

         页在所属逻辑流中的编号,表示这是其所属逻辑流中的第几页,从0开始。用于检查页是否丢失。

字节23~26

CRC校验和【page checksum】:

        页的校验码【check code,加在数据尾检查数据漏误的一串数字。】。32位CRC算法,CRC是一种优秀的检错码,很有难度,想学习自行搜索,可参考这里【年代早 无图 细节满满】,精通英语的大佬看这里【老外 专业性极强】。

       如果你已经懂了CRC,请注意:Ogg的CRC采用直接算法(不反转),寄存器初始值为0,余数为0。因为CRC码在页头而不是数据尾,计算时先把 CRC段 的4个字节 全填0,算出校验和 再填到这4个字节里,校验时同理。

字节27

段数【page segments】:

         即此页中段的数量,也是段表的长度(单位:字节),大小范围0 ~ 255,说明一页最多有255段。如果为0,说明此页不含数据,为空页【见下文】。

字节28 ~ 27 + n

        段表【segment table】

         段的长度表 / 系带值表。表的长度 段表长度决定。段表后面是页数据【page data,与页头相对】,实际上就是 段 组成的一大串字节。段表的每个字节 都表示后面一个 段 的长度(系带值),范围0~255(字节)。

        例如,段表有四个字节:255 255 255 173

        那么,紧跟段表后面就是按顺序排列的 255、255、255、173 字节的 四段。

        由上可算出:一页最大大小是 27 + 255 * 255  = 65307 Bytes,略小于64KiB(64*1024 = 65536)。最小是27字节,即只包含一个页头,这种页叫“空页”【nil page】,通常在EOS时使用空页结尾。过大的页不建议用:因为一旦数据出错,这么大一页都要丢弃,血亏!页的最佳大小是4 ~ 6KiB。

        注意:上文所有整数的字节按低字节序【Little Endian,又译“小端派”“低位编址”】,即低位字节(字节 不是 位 !)在前,高位字节在后。比如  0x 00 00 25 9a  四个字节,输出时变成 9a 25 00 00


「基础编解码」

        这一节讲述了最基础的Ogg编解码过程,官方libogg 库实现了基础编解码,代码参考gitlab-libogg。

        解码显然比编码简单,先讲解编码过程。搞懂编码,解码就通了。这里只涉及 单个逻辑流 <—> 基本流的过程,对于 多个基本流 <—> 复合流 的情况,见 多路复用。

壹:数据包分割 【segmentation分段;或译“分片”】

        Ogg要存储的数据就是一堆包(同一逻辑流),大小不固定,但页的大小是有限制的。第一步,将包分成更小的段,使之能组装成页;而段 又要能重新组成 包。所以分割(分 系带值)是有套路的:

       首先把一个包从前向后,每255字节分为一段(段最大255)。如果分到最后,不够255字节,就把剩下< 255的分成一段;如果刚好255字节,为了区分边界,再加一个长度为0的段。这样每个包的最后一段 一定 < 255字节。

       例如,一个753字节的包 + 一个255字节的包  会被分成:

       255  255  243  255  0          共  5  段

       分段的设计使 过大/过小 的数据包都不会严重浪费空间,而且不限制包的大小。当然最好还是在255 ~ 510字节效率最高。(< 255字节的较小包浪费严重,> 510字节的较大包大约只耗费0.5%)。

       段重新组成包也简单。段按顺序排列,每个 长度< 255 的段都是一个包的末尾,把它和它前面255字节的段合起来即可:

       255   255   243       255   0       67

                             ^                   ^        ^

                       753B包       255B包   67B包

贰:数据包分页【pagination】

       走完第一步,现在我们有了一大堆段。接下来组装成页,我们且不管 页头其他部分,先把段和段表装起来。

       数据包的大小不定,所分出来的 段的数量 也是不确定的,而页的大小有限制。大数据包  一页装不下  ;小数据包 一页只装一个 又太浪费了;如果一页只装整数个包,页的大小就很可能参差不齐(虽然页大小可变,但4 -6KiB最舒服)。综上,我们不能按包来分页,而应该用更小的单位:段。

       按段组装页,就意味着数据包可以随意跨越页的边界。一个包可能 头几段 在第一页, 后几段 在第二页;特大数据包可能跨越好几页;小数据包可能好几个都塞在一页里。如此一来,页便可以把控在4 – 6KiB 左右。

       打个比方,一个包看成一篇文章,页就看作书页。文章有长有短:可能一篇长文占几页;也可能几篇短文放一页;文章还可以跨页;但每页的大小都差不多。Monty就是这么比喻的,于是起名为“页”。如图:

       但是,Ogg建议尽量避免跨页的包,因为这变相增加了解码复杂度(尤其是增加空间开销);在libogg的函数中,默认情况 每一页要满足(1)至少有4个包 (2)大小超过4096字节 (3)最后一个包不跨页 才能输出。这是一个对调用者隐藏的低级策略。只有4096这个阈值可手动调整;若上述三个条件一直不满足,页会一直装到满才输出!除非装满了,才有“不得不”跨页的包。说实话,这个实现真tm摆烂。

       

6a542e6a0c874d75be9fe07d61b8dca9.png

数据包分页(这里没有模拟大数据包)

叁:填页头【页封装】

        我们已经组装好了页的段的数据(把所有的段合成一个Byte数组即可)并把每个段的长度写进了段表。我们是按 逻辑流的顺序 分包,按包的顺序分段,按段的顺序分页,这时页已是有序的。只要加上页头即可 —-

        且慢,页头可不简单。

        看看,捕获域、版本是固定的;页号按顺序标(从0开始哦);流序列号找个随机数就行;CRC校验码的话,页头还没填完,算不了。那么只有 头部类型 和 绝对颗粒位置 了。

        BOS页是流的第一页,Ogg要求 BOS页只能有一个包 — 初始标头【initial header】 ,标识包,不含数据,通过它必须能得到整个逻辑流的数据类型,从而确定对应的解码器(尤其是确定颗粒位置如何转换);还必须能确定流的连续性 (见 高级-缓冲)。所以我们回到第二步数据包分页的时候,把第一个包的段单独装进第一页里,BOS位标 1。

        同时,Ogg规定:逻辑流可以有辅助标头【auxiliary header】,辅助标头可以由n个包组成,用来提供额外信息或者为后续解码提供初始化参数(例如Vorbis的注释标头和设置标头)。辅助标头必须紧跟在初始标头后面,所以辅助标头的页必须紧跟BOS页,最后一个辅助包必须恰好在一页末尾完成(此操作叫做刷新页【Flush page】),真正的数据从一个新页开始。我们又回到第二步,调整辅助标头的页。

        EOS页是流的最后一页,EOS位标 0。一个逻辑流可以只有一页(奇怪的规定,只有BOS页就只有初始标头;只有标识没有数据有什么意义?),此时BOS  EOS都标。

        如果这一页以一个新包开始,那么Fresh位 标0;如果这一页跟上一页的最后一个包是连着的,标1。其实这个设计是多余的,因为只要看前一页的最后一段是不是255字节,是255说明数据包还没结束,下一页还有;反之说明下一页是新页。Fresh位的存在只是为了增强流的检错功能。

       因为逻辑流是多媒体数据,它一定是按时间顺序排列的;则包的绝对颗粒位置必然按照非递减【即后面的数字必须大于等于前面的数字】排序,相应的,页的颗粒位置也必然非递减排列( -1 除外)。包的时间可以为0,所以包的颗粒位置 可能和 前一个包 相等,同理,页的颗粒位置可能等于 前一页。已经说过:页的颗粒位置 是 页中最后一个<完成的包>(包的最后一段在某页里,则 包在此页上完成)的颗粒位置,如果没有完成的包就填 -1。总之,后一页的颗粒位置要么为-1,要么大于等于前一页的颗粒位置。

        不对,如果一页里有多个包,前几个包的颗粒位置不就没了吗?确实,颗粒位置的精度会下降。但Ogg认为只要控制好大小即可控制误差在容忍范围内。这一点在 高级-查找 里还要重提。

        上面这些是Ogg对数据的基本要求,我开头说的 “可以在Ogg里存任何数据” 不完全对:闲的发慌的你还需要把数据 合理地拆成包 并加入 初始标头(+ 辅助标头),还要定义一个颗粒时间出来。编解码器像你这样将逻辑流封装到Ogg流的过程叫做Ogg 映射【Ogg Mapping】或 编解码器映射【Codec’s Mapping】

        Ogg 映射包括:将逻辑流分成整数个包,再装成整数个页;提供初始标头(和辅助标头)并 按规定装页 放在流开头;定义颗粒位置如何转换,包/页 的颗粒位置必须按非递减排序,必须可以用  当前包的颗粒位置  算得  下一个包的颗粒位置。

        到此为止,我们把一个包组成的逻辑流 完美地 封装成了一个页组成基本流。接下来,我们来看看解码的注意事项。

肆:解码问题

        首先,解码就是上面的逆过程:读取页,根据段表 取出段,根据< 255的规律 合成包,然后输出包。但多了一步:检查页头、进行CRC校验等,判断是否有数据丢失。

        》》 页 丢失/错误 怎么办?

        如果丟页,你可以选择 跳过/报错 。一般来说,网络传输程序常忽略错误,或者要求重传数据;文件解析程序常抛出异常;页头错误通常可以忽略,但CRC校验错误、段表错误是不应忽略的(说明数据异常),这时又要选择 跳过/报错。

        跳过页的方法就是舍弃异常的包(而不是段),包通常是一个整体。如果异常页的 上一页有未完成的包 / 下一页有续包,都要丢弃。丢失的数据 跳过 / 用黑屏、静音代替,如果一次性太多页异常,还是报错吧。

        最恶心的情况:因为段表错误,无法正确读取异常页的数据,也就找不到下一页。这种严重错误十有八九可以被CRC校验出来(这时你已经读完错误的页了)。这就只能回移指针到 页头段表开头 处,用seek操作重新定位流的下一页。

        》》关于 捕获/定位/seek

        在捕获/定位Ogg流时,因为一页最大是65307字节,在读取65307字节后还没找到页,可以直接报错 “非法的Ogg流”;要是担心第一页异常,可以酌情继续(没有必要)。

        找到页头的捕获域“OggS”后,理应继续读完一整个页头,确定整个页是有效的;如果页异常,跳页后 酌情考虑继续定位;追求保险可以查看下一页的的页头。seek有两种实现:一种是seek操作后 流指针 就处于 页 的开头;一种是seek后返回下一页的偏移量【offset,目标位置和当前位置的距离】。我喜欢第一种:不需要读了页还要把指针移回去。

        如果你还头脑清晰,你已经可以用自己熟悉的语言写一个 Ogg 基础解析库了。需要小试牛刀的话,这里有【Vorbis测试音频】,找到后缀.ogg的文件,测试你的程序吧。

        脑细胞充足的,往下看。

【原作者:神武竹 • 未经允许,禁止转载】


「高级」

       这一节讲解了在 基础编解码 单个逻辑流 的基础上,Ogg容器的高级功能。官方liboggz 库在libogg基础上实现了这些功能,代码参考gitlab-liboggz。

>> 多路复用

        我也不知道我在前文写了几个“见多路复用”了,Ogg用了整整一篇文档描述它,但千万不要把它想高级了:Ogg追求简单!

        多路复用【Multiplex,动词/形容词 多路复用。或译“多路传输”,相应的有multiplexing、multiplexed】。指把多个 基本流 按时间顺序混合、交错成为 复合流的过程。使得视频、音频、字幕等可以被混合在一起,解码时可以同时播放。

        在多路复用中,交错的是页,所以页是保持不变的。可以把页想象成扑克牌,几副 花色不同的牌 混在一起,每张牌没有变化。要想把混合的牌还原成几副牌,只要挨个看牌,把同一花色(流序列号)的牌按顺序放一起即可。多路复用 / 多路分解【Demultiplex,即multiplex的逆过程,或译“多路分配”】对于计算来说很简单。如图:

        f72dc08d0b9e4cf29867d4ee888a3b6d.png

基本流(逻辑流)的多路复用;“OggS”表示页

         综上所述,页是多路复用的最小单位。所以我们要搞懂的,就是怎么将页混合。多路复用和多路分解 操作简称为“mux”和“demux”。

         首先,回到上面的那张图:

d561e21319e84bb99e2a4deca51e4f8a.png

       我们可以看到 :红、蓝、绿 三个基本流的第一页(图上initial)都在复合流的最前面,而后面的页没有规律地排列,三个流的最后一页(图上term)也不在一起。

       多路复用一:基本流的BOS页一同放在复合流最前面。BOS页之间不能有非BOS页。无特定顺序。

       BOS页只有初始标头包,标记逻辑流的数据类型。所以解码时,只需读完所有BOS页,就可知 是否是复合流、复合流中有几个基本流 ,以及每个流的数据类型 和 流序列号,查找对应的编解码器。

       多路复用二:复合流中基本流的EOS页 不一定 要放在一起。因为没有必要。

        如果在上图中,去掉蓝绿色,只看一个红色的页,从前到后是红0、红1、红2、红3 ……红n。每个基本流的页依然保持原始先后顺序。

       多路复用三:同一基本流的页,彼此之间保持原先后顺序,即时间顺序。

       已知逻辑流还可能有辅助标头。

       多路复用四:(如果有)所有基本流的所有 辅助标头组成的页 紧随最后一个BOS页后,互相交错无特定顺序。辅助标头页之间不能出现非辅助标头页。辅助标头页 全部结束后 数据页开始。

        初始标头和辅助标头只提供编解码信息而非原始数据,所以它们的颗粒位置理应为0(非硬性要求)。

       多路复用五:数据页根据颗粒位置,按时间顺序交错。把页的颗粒位置转换为绝对时间,按时间顺序依次插入(同时间的随机插入)。例如两个流混合:(字母表示页,紧跟数字表示页的颗粒位置)

       视频流|帧率  60帧/秒       |A 0   B 24   C 33   D 60   E 84

       音频流|样本率  44K个/秒 |a 0   b 8800   c 16500  d 26400  e 39600

        每页的颗粒位置转换为统一单位毫秒:

       A 0    B  400    C 550    D 1000    E 1400

       a 0     b  200    c 375     d  600     e  900

       按时间排序:0 – 0 – 200 – 375 – 400 – 550 – 600 – 900 – 1000 – 1400

       混合结果:A a b B c C d e D E

       如果你需要复合流来测试程序的话,抱歉找不到较小的ogv视频(目前只有Theora视频是复合流),官方提供较小的英文宣传片是《Digital Show & Tell》。包含一个复合流,由十个基本流构成(已知包括视频、音频、字幕、索引)。下载目录在这里,最小的是360P分辨率、117MB大小。

>> 查找

        查找【seek】即跳转功能,转到某个时间重新开始解析,直观感受就是 视频/音乐 转到某一秒开始播放。或者叫随机访问【Random Access,根据具体位置访问数据的任意一部分】。

        查找分为低精度【或译 粗精度】和高精度【或译 细精度】。

        低精度查找:将指针向前/后移动一段距离(估算大致字节数),然后定位(捕获),检查页的颗粒位置是否和目标位置对应,若相差较大,重复上述过程直至基本符合。(太tm简单粗暴了,翻译时愣是半天没看懂,以为很高级)

        高精度查找:二分查找算法。(更tm简单粗暴)如果追求更高精度,你可以在找到的页里 按包查找。按包查找时,只能使用同一逻辑流的页。(liboggz库没有实现按包查找,由具体编解码器实现)

        如上,可见seek操作的本质就是:乱猜 + 硬找。不骗你,liboggz库(官方高级Ogg编解码库)里的seek操作主要调用的函数就叫 oggz_seek_guess() …… 至少这种算法和Ogg容器完美契合。下面解答常见问题:

> FAQ

        Q :我找到了相邻的两页,前一页颗粒位置与目标误差 -500,后一页误差为 +700 ,是不是选误差小的那一页跳转?

        A :官方文档认为应该找前一页,主要是为了保险起见。我认为看情况而定,连续流【见缓冲】可以找后一页(误差为0也用下一页);非连续流【见缓冲】找前一页(误差为0可太好啦);如果是视频(Thoera)复合流只能找前一页,见下文。

        Q :如何在复合流里查找?

        A :因为多路复用是通过转换颗粒位置为统一时间单位,按时间顺序混合,查找时不需要多路分解,复合流的官方解法:

        首先忽略复用正常「查找」到目标页,然后向前扫描页,直到复合流中每个基本流都出现了至少一页。然后比较这些页的颗粒位置,跳转到其中时间最早的一页(如果某页有依赖页,该页的时间 = 依赖页时间)。

        为了部分流的页间隙太大,Ogg允许设置一个最大扫描页数。(我觉得直接不管非连续流更好)

        Q:Thoera是视频格式,需要关键帧,如何查找?

        A:好问!: ) 目标时间转换为帧数,左移n位就是目标位置。调用liboggz(或者自己写的代码)「查找」,如果恰好找到最后是关键帧的页,直接跳转到关键帧处;找到中间帧则 取颗粒位置前6个字节(所依赖关键帧的<总帧数>)作为目标再「查找」,恰好碰到关键帧(页的最后一个包)同上;否则,切换为按包查找,向后搜索找到关键帧。统共需要「查找」两次,叫做双重查找【double seek】。

        Q:颗粒位置为-1的页怎么办?

        A:无视。

        补充:对于Ogg解码器而言,最理想的数据源是本地Ogg文件;可靠的流媒体传输(基于TCP的Http协议)次之,注意:网络传输Ogg时不会发送EOS页,且EOS页一定是Nil页;不稳定可靠的流媒体传输又次之(基于UDP的RTP协议),因为意味着有可能丢失数据;无反馈传输最烂(如卫星广播),服务端只管发送,不管数据丢失,客户端也不能向服务端通讯。网络传输都不一定能够实现查找功能,只有本地Ogg文件100%允许查找。

> 二分查找

        高精度查找使用二分查找算法,其实精度不比低精度高多少,但是平均速度略胜一筹。

        1:寻找流的两端,开始为B,结束为E

        2:(指针)跳转到B和E的字节中间位置

        3:定位,读取页的颗粒位置。

        4:switch

case 颗粒位置 == -1,  读取下一页;  回到第3步

case 颗粒位置 < 目标,E = 当前坐标;回到第2步

case 颗粒位置 > 目标,B = 当前坐标;回到第2步

case 颗粒位置 == 目标,结束。

        这个算法非常高效,索引的设计确实多余。当然算法也可以进一步改进:当指针已经很靠近目标时,可以直接读取。(但效率提升不大);更高级的算法,即 计算页平均大小 和 页平均颗粒位置 得到平均比特率,就能更加精准地猜测位置,然后再用二分查找。

>> 缓冲

连续性

       Ogg将逻辑流分为连续【Continuous】流非连续【Discontinuous】流 两类。

       像音频、视频这样,时间上连续不断的,是连续流。连续流的数据必须是不间断的,只要流没有结束,一定能读取到更多的数据。

       像 字幕/弹幕 这样,时间不连续的,为非连续流。因为字幕、弹幕之类的有以下特征:1,不规则性。一个视频里 有时有,有时没有。2,弹幕/字幕 是分散、彼此独立的,而不是前后紧密相连(像音视频一样)。3,随机性。尤其是弹幕,完全取决于用户操作。

        总而言之,它们本身不属于流式数据,但必须作为流放在Ogg容器里。这样的流就是非连续流。

        补充:虽然 连续流和非连续流 组成的复合流中可以直接进行查找操作(见查找),但是如果选择精度高的按包查找,不能使用非连续流的页。

绝对颗粒位置

        前面我们说:页 的绝对颗粒位置 是 该页上最后一个完成的包 的颗粒位置。现在要补充:这种“末时间【end-time】”是针对连续流而言的;如果是非连续流,页 的颗粒位置 是<上一个包的总时间>,而不是<上一个包的总时间> + <此包的时间>,这种颗粒位置叫“初时间【start-time】”。每个包的初时间 = 上一个包的末时间。如果一页没有完成的包(非连续流几乎不可能使用续页,未完成的包极罕见),颗粒位置仍然用 -1。

        多路复用时,忽略 末时间/初时间 的差异,直接按时间顺序混合。

        下面的内容充满歧义,均以下划线标出。如果你对任何一处有异议,一定要自己去看官方Ogg文档(如果你搞懂了,务必评论区指出本人谬误)。

        节选自原文《Ogg多路复用》-缓冲【buffering】

Ogg缓冲基于一个简单的前提:在解码期间,不允许任何活跃连续流要求得到【stave for。应为 <编解码器 要求得到 数据>,是否为stave(缺乏)之误?】数据;缓冲继续进行,直到【proceed ahead until。proceed为“继续进行”;ahead可作“提前”或“向前”,我倾向“向前”】物理流中的所有连续流都准备好数据 可以按需求解码。

非连续流的数据可能经常出现,例如,在大多数字幕系统中,特定字幕的时间无法确定。因此,缓冲系统应该 读取到非连续数据时 才进行处理,而不是提前(在可能无限长的时间里)寻找将来(不一定存在)的非连续数据。因此缓冲时,不连续流会被忽略;当连续流的页得到正确处理时,非连续流的页只会“掉出”【fall out】(复合)流。

缓冲需求 【buffer requirement】不需要显式声明或管理流;解码器只需读取尽可能多的数据,保证所有连续流不间断and no more/further【是“连续流数据不再进一步”还是“解码器不再进一步”?】,也确保不连续的数据及时到达。缓冲对于给定流是隐式最优的。因为所有数据类型的页都在流中有绝对计时信息【指绝对颗粒位置】,因此流间同步计时总是显式维护,而无需显式声明的缓冲区。

        这一段很晦涩,官方libogg库里也没有相应的实现。《Ogg比特流概述》的缓冲跟《Ogg多路复用》的缓冲基本一致,几个单词不同。大致解释一下即可:

        解码复合流时,缓冲区由连续流使用,非连续流没有缓冲区;当解码器读取到非连续流数据(页)时,自行处理;缓冲应是隐式的,对调用者透明;(待续,估计续不成)

>> Skeleton

        待续


「实现细节」

         本节不参考Ogg文档,而是根据 官方代码库 及 个人经验 提出 Ogg编解码器实现 的建议。这不是Ogg规格的要求。

>> mux表

        mux/demux时,每个逻辑流(基本流)都有对应的序列号。为了更方便的mux和demux,liboggz库实现了一个表【table】。表是给调用者使用的,它把 流序列号 – 数据 以 键值对【key-value】的形式存成一张表,这里的数据可以是页(demux用)、包(mux用)、基本流。liboggz中 表用两个OggVector(Vector向量,自动扩容的集合)实现,显然,哈希表的效率更高。

        如果要实现更简单的API,可以把多路复用的过程也对调用者隐藏,这时候流表就可以派上用场了。

(待续)

【原作者:神武竹 • 未经允许,禁止转载】


「其它」

>> MIME

       Ogg根据其存储的数据分三种:音频、视频、其它(拓展)。

       其它Ogg文件是指:首先必须是多路复用Ogg流,可包含任何可复用的逻辑流;其次必须包含Skeleton。当程序解码ogx文件时,能解码的逻辑流就解码,不能解码的简单忽略。其它Ogg文件的主要作用是混合数据,例如把多首音乐组成的一首专辑 多路复用 成一个ogx文件。没有自己拓展名的文件可以加上Skeleton后存储为ogx,比如Ogg Kate。(ogx绝对不是应用程序)

        与之对应,音频Ogg文件的MIME类型是audio/ogg(Mac文件类型码OggV);视频Ogg文件是video/ogg(Mac码OggA);其它是application/ogg(Mac码OggX)。视频Ogg文件拓展名为.ogv;音频Ogg文件拓展名可能为.ogg(Vorbis)、.opus(Opus)、.spx(Speex)、.oga(通常指FLAC)、.ogm(已淘汰的MNG);其它Ogg文件拓展名为.ogx。参见【XiphWiki MIME和拓展名】。

        Ogg Skeleton可以添加在任何一类Ogg文件中,在音频、视频文件中可有可无,但在其它文件中必须存在(因为ogx必然是复杂大文件)。

>> 初始标头规范

        上文:必须能够通过初始标头确定编解码器。所以每个初始标头必须存在幻数,以确定数据类型。Xiph明确了部分数据类型的初始标头的幻数和版本字段(如果有)。一般来说幻数的长度是8字节(年代早的Vorbis和Theora是7字节,不属于Xiph的Dirac和原本不属于Xiph的FLAC是5字节),具体规范本文章不便列出,参见MIME类型和编解码器。


「不足」

        作者并没有资格来评论Ogg,我不是它的开发者,也没有亲自测试过。但我有责任把对Ogg的指责列出来,留给诸君评判。

        对Ogg最刺骨的指责莫过于 2010年3月3日,FFmpeg程序员Mans Rullgard在个人博客上发表的《Ogg异议》了。这篇文章从头到脚地痛骂Ogg,最后直言“烂是对Ogg唯一合适的描述”。此前,他还有一篇文章指责Ogg的颗粒位置设计。引起轩然大波(以至于中网有此新闻介绍)。

        看看这篇文章的评论,“虚伪的自由软件倡导者,暗地操纵互联网控制权” “我们为什么要为了开源选择Ogg这样的东西” “以前坚决反对索引,现在又加,呵,真香” “Matroska也开源,不比它好?”“Xiph压根不解决问题,而是高呼专利专利专利”。我觉得咱们的网络环境也不是那么……

        这惹恼了Monty,Monty于4月27日发表了一篇文章“逐字逐句”地反驳(无考)。

        摘录Mans反对的理由如下:

1 浪费空间。32位的流序列号 、 64 位的颗粒位置、 32位页号 简直“不可理喻”,你用得了那么多吗?不用网络传输,强制的32位CRC有用吗?Ogg开销高达1%,MP4才0.05%!(简单的计算可以得出开销至少是0.4%)

2 随机访问。Ogg反对索引,怎么在网络中定位?seek的效率您考虑了么?一个10GB的文件你要二分操作50次!Ogg根本就不是流媒体用的东西!

3 这“映射”麻烦的要死,真的考虑了通用性吗?哪里比得上Matroska的简洁?Ogg里能装什么?还不是你自己那几个破玩意(指Vorbis和Thoera)

4 多路复用是什么玩意?你知道延迟有多大吗?页这么大,活该延迟高。

5 颗粒位置。你设计这么麻烦给谁看?还转换?为什么是结束时间,开始时间不更容易播放吗?Theora的颗粒位置还更复杂!

6 复杂性。把数据包拆成碎片,要耗多少内存?物理流串联成链,这设计根本不会有人用。

        对于以上指责,Monty一一驳斥。说到底Ogg是用来存储文件和流媒体传输的,使得Mans的一些论点显然不成立(比如“Ogg为什么不能用UDP传输”)。Ogg当然不是完美的,单位Mans后来直接针对Xiph基金会进行道德谴责和引战,显然是在给自己挖坑。


「没了」

• 写作不易,感谢你的支持与指正。所有图片来源于Xiph官方或维基百科。

• 此篇仓促之中,尚有未完之处,除标注“待续”外,实例代码亦未添加。本人学生党,精力着实有限,偶执闲笔,玩笑之作,不得已随缘更新,烦请谅解。

<更新时间2022-8-22>

【原作者:神武竹 • 未经允许,禁止转载】

今天的文章Ogg 格式_ogg转mp3的免费软件分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/63362.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注