博客主页:https://blog.csdn.net/wkd_007
博客内容:嵌入式开发、Linux、C语言、C++、数据结构、音视频
本文内容:介绍Ogg文件格式
金句分享:子曰:见贤思齐焉,见不贤而内自省也。——《论语·里仁篇》。意思是,看见德才兼备的人就向他学习,希望能向他看齐;看见不贤的人,就反省自己有没有和他一样的缺点,有要改正。
文章未经允许,不许转载 !!!
Ogg
是一个自由且开放标准的多媒体文件格式,由Xiph.Org基金会所维护。Ogg格式并不受到软件专利的限制,并设计用于有效率地流媒体和处理高质量的数字多媒体。
与大多数容器格式一样,它封装了原始的压缩数据,并允许在一种方便的格式中交错存储音频和视频数据。
Ogg
这个词汇通常意指Ogg Vorbis
此一音频文件格式,也就是将Vorbis编码的音效包含在Ogg的容器中所成的格式。在以往,.ogg
此一扩展名曾经被用在任何Ogg支持格式下的内容,但在2007年,Xiph.Org
基金会为了向后兼容的考虑,提出请求,将.ogg
只留给Vorbis格式来使用。 Xiph.Org
基金会决定创造一些新的扩展名和媒体格式来描述不同类型的内容,像是只包含音效所用的.oga
,包含或不含声音的影片(涵盖 Theora)所用的.ogv
和程序所用的.ogx
。
更多关于Ogg的资料可以到Ogg官网学习:https://xiph.org/ogg/
描述Ogg封装格式的 RFC3533
文档:https://www.xiph.org/ogg/doc/rfc3533.txt 或 https://datatracker.ietf.org/doc/html/rfc3533
原文链接:https://blog.csdn.net/wkd_007/article/details/134150061
Ogg
意指一种文件格式,可以纳入各式各样自由和开放源代码的编解码器,包含音效、视频、文字(像字幕)与元数据的处理。
下面是Ogg支持的编码格式:
音频
视频
文本
物理(Ogg)比特流
,也就是说.ogg
文件属于一个物理比特流。逻辑比特流
,例如:Vorbis编码的流,Theora编码的流等。看下图例子加深理解:在这个例子中,有两个链接的物理比特流,第一个是由三个逻辑比特流A、B和C组成的分组流。第二个物理比特流D链接在分组比特流的末尾之后,分组比特流在其所有分组逻辑比特流的最后一个eos页之后结束。
Ogg 封装过程:
1、编码器提供的比特流作为“分包”(Packets)移交给Ogg,分包边界(packet boundaries)取决于编码格式;
2、Ogg封装过程将数据包分割成若干段;
3、将一组连续的段(segments)包装成一个可变长度的页面(page);
4、最后将各个页(page)混合组成一个Ogg物理比特流。
从上面内容可知,Ogg文件也属于一个物理比特流。是由一个个页(page)组成的。首先,我们要了解一个页的内容,然后找到Ogg文件的各个页就可以解析Ogg文件了。
Ogg的页(page)由页面头部(page header) 加上该页的各个段(segments)的数据组成。
页面头部(page header)中的9个字段具有以下含义:
1、capture_pattern:表示页面开始的4字节字段。它包含4个字符:O
、g
、g
、S
。它可以帮助解码器找到页面边界,并在解析损坏的流后重新获得同步。一旦发现捕获模式,解码器就通过计算和比较校验和来验证页面同步和完整性。
2、stream_structure_version:1字节,表示该流中使用的Ogg文件格式的版本号(本文档指定版本0)。
3、header_type_flag:这1字节字段中的位标识该页面的特定类型。
4、granule_position:包含位置信息的8字节字段。例如,对于音频流,它可能包含在包括此页面上完成的所有帧之后编码的PCM样本的总数。对于视频流,它可能包含在此页面之后编码的视频帧的总数。这是对解码器的提示,并给它一些定时和位置信息。其含义取决于该逻辑比特流的编解码器,并在特定媒体映射中指定。特殊值-1(以2的补码表示)表示此页上没有数据包结束。
5、bitstream_serial_number:包含唯一序列号的4字节字段,通过该唯一序列号来识别逻辑比特流。
6、page_sequence_number:包含页面序列号的4字节字段,使得解码器可以识别页面丢失。该序列号在每个逻辑比特流上分别增加。
7、CRC_checksum:包含页面的32位CRC校验和的4字节字段(包括具有零CRC字段的报头和页面内容)。生成多项式为0x04c11db7。
8、number_page_segments:1字节,给出分段表(segment table)中编码的分段条目的数量。
9、segment_table:大小为 number_page_segments 个字节。包含此页中所有段的lacing value。每个字节包含一个 lacing value。
以字节为单位的页面头部大小(total header size)由下式给出:
header_size = number_page_segments + 27 [Byte]
以字节为单位的总页面大小由下式给出:页面头部大小
+ 所有lacing_values值之和
page_size = header_size + sum(lacing_values: 1..number_page_segments)[Byte]
这里按照上面的页的格式,演示怎样解析一个Ogg封装的文件,文件可以在这个链接下载:https://download.csdn.net/download/wkd_007/88492683 。
用Notepad打开该文件并查看十六进制模式:
- capture_pattern字段:值为
0x4f、0x67、0x67、0x53
,对应字符OggS
,表示页起始标志;- stream_structure_version字段:值为
0x00
,表示版本号;- header_type_flag字段:值为
0x02
,表示逻辑比特流(bos)的第一页;- granule_position字段:值为
0x0
,媒体编码相关的参数信息,表示到本页为止逻辑流有0个采样;- bitstream_serial_number字段:
0x23、0x49、0x02、0x11
,逻辑比特流序列号;- page_sequence_number字段:
0x00、0x00、0x00、0x00
,页面序列号;- CRC_checksum字段:
0xdf、0xe2、0x0c、0x1f
,32位CRC校验;- number_page_segments字段:
0x01
,表示后面段(segment)的个数,本页有1个段(segment);- segment_table:前面指明了只有1个段,所以segment_table大小为1个字节,值为
0x13
,表示这个段的大小为0x13
个字节。- 后面的0x13个字节就是段的内容(上图中浅蓝色背景的字节):
0x4f
开始到0x00
。
- capture_pattern字段:值为
0x4f、0x67、0x67、0x53
,对应字符OggS
,表示页起始标志;- stream_structure_version字段:值为
0x00
,表示版本号;- header_type_flag字段:值为
0x00
,表示不是逻辑比特流(bos)的第一页,也不是最后一页,也不包含从上一页继续的数据包;- granule_position字段:值为
0x0
,媒体编码相关的参数信息,表示到本页为止逻辑流有0个采样;- bitstream_serial_number字段:
0x23、0x49、0x02、0x11
,逻辑比特流唯一序列号;- page_sequence_number字段:
0x01、0x00、0x00、0x00
,页面序列号;- CRC_checksum字段:
0xd4、0x3e、0x3f、0x20
,32位CRC校验;- number_page_segments字段:
0x03
,表示后面段(segment)的个数,本页有3个段(segment);- segment_table:前面指明了3个段,所以segment_table大小为3个字节,值为
0xff、0xff、0xfe
,表示这个3个段的大小分别为为0xff、0xff、0xfe
个字节。后面三个段的总字节数为:0xff+0xff+0xfe=0x2fc
字节。- 之后的
0x2fc
个字节就是三个段的内容(上图中浅蓝色背景的字节,没显示完整):0x4f
开始到0x00
。这里的起始地址是0x4d
,加上0x2fc
,就是下一页的地址0x349
。
- capture_pattern字段:值为
0x4f、0x67、0x67、0x53
,对应字符OggS
,表示页起始标志;- stream_structure_version字段:值为
0x00
,表示版本号;- header_type_flag字段:值为
0x00
,表示不是逻辑比特流(bos)的第一页,也不是最后一页,也不包含从上一页继续的数据包;- granule_position字段:值为
0x80、0xbb、0x0、0x0、0x0、0x0、0x0、0x0
,媒体编码相关的参数信息,小端表示到本页的逻辑流有48000
帧(小端0xbb80=48000);- bitstream_serial_number字段:
0x23、0x49、0x02、0x11
,逻辑比特流唯一序列号;- page_sequence_number字段:
0x02、0x00、0x00、0x00
,页面序列号;- CRC_checksum字段:
0x98、0x9d、0xc5、0x56
,32位CRC校验;- number_page_segments字段:
0x33
,表示后面段(segment)的个数,本页有51(0x33=51)个段(segment);- segment_table:前面指明了51个段,所以segment_table大小为51个字节,值分别为图中蓝色背景部分,每个字节表示各个段的大小。这51个字节的值相加就是后面51个段总字节数:0xff + 0x06 + 0xe5 + 0xab + 0xa2 + 0xa1 + 0xa5 + 0xa3 + 0xdf + 0xab + 0xa7 + 0xa1 + 0xa3 + 0x9d + 0xec + 0xe0 + 0xa4 + 0x9e + 0xb3 + 0xa6 + 0xbe + 0xb8 + 0xb4 + 0xaa + 0xa3 + 0xa2 + 0xa4 + 0x99 + 0x9f + 0x9c + 0x96 + 0x94 + 0x94 + 0x97 + 0x99 + 0x9a + 0xa1 + 0x9e + 0xa0 + 0xa6 + 0xa1 + 0xa0 + 0xaa + 0xa9 + 0xac + 0xa4 + 0xa6 + 0x9d + 0x9a + 0x94 + 0x98 =
8484
个字节;- 之后的
8484
个字节就是51个段的内容。第一个段的起始地址是0x397
,加上8484
,就是下一页的地址0x24bb
。
- capture_pattern字段:值为
0x4f、0x67、0x67、0x53
,对应字符OggS
,表示页起始标志;- stream_structure_version字段:值为
0x00
,表示版本号;- header_type_flag字段:值为
0x00
,表示不是逻辑比特流(bos)的第一页,也不是最后一页,也不包含从上一页继续的数据包;- granule_position字段:值为
0x00、0x77、0x01、0x0、0x0、0x0、0x0、0x0
,媒体编码相关的参数信息,小端表示到本页的逻辑流有96000
帧(小端0x017700=96000);- bitstream_serial_number字段:
0x23、0x49、0x02、0x11
,逻辑比特流唯一序列号;- page_sequence_number字段:
0x03、0x00、0x00、0x00
,页面序列号;- CRC_checksum字段:
0x25、0x80、0xd7、0x7d
,32位CRC校验;- number_page_segments字段:
0x32
,表示后面段(segment)的个数,本页有50(0x32=51)个段(segment);- segment_table:前面指明了50个段,所以segment_table大小为50个字节,值分别为图中蓝色背景部分,每个字节表示各个段的大小。这50个字节的值相加就是后面50个段总字节数,这里不计算了,感兴趣自己算。
- 之后的若干个字节就是50个段的内容。
这里解析了4页,后面的页数依次类推去分析,这个文件有118页,最后一页的话,其header_type_flag字段值为0x04。
// readOggFile.c
#include
#include
#include
// 8字节数组转成 unsigned long long
unsigned long long ToULL(unsigned char num[8], int len)
{
unsigned long long ret = 0;
if(len==8)
{
int i=0;
for(i=0; i<len; i++)
{
ret |= ((unsigned long long)num[i] << (i*8));
}
}
return ret;
}
// 4字节数组转成 unsigned int
unsigned int ToUInt(unsigned char num[4], int len)
{
unsigned int ret = 0;
if(len==4)
{
int i=0;
for(i=0; i<len; i++)
{
ret |= ((unsigned int)num[i] << (i*8));
}
}
return ret;
}
int readOggPage(char *oggFile)
{
typedef struct PAGE_HEADER{
char Oggs[4];
unsigned char ver;
unsigned char header_type_flag;
unsigned char granule_position[8];
unsigned char stream_serial_num[4];
unsigned char page_sequence_number[4];
unsigned char CRC_checksum[4];
unsigned char seg_num;
unsigned char segment_table[];
}PAGE_HEADER;
FILE *fp=fopen(oggFile,"rb");
while(!feof(fp))
{
// 1、读取 page_header
PAGE_HEADER page_header;
if(1 != fread(&page_header,sizeof(page_header),1,fp))
break;
printf("page_num:%03u; ",ToUInt(page_header.page_sequence_number, 4));
printf("Oggs:%c %c %c %c; ",page_header.Oggs[0],page_header.Oggs[1],page_header.Oggs[2],page_header.Oggs[3]);
printf("type=%d, granule_position:%08llu; ", page_header.header_type_flag,ToULL(page_header.granule_position, 8));
//printf("seg_num:%d \n",page_header.seg_num);
// 2、读取 Segment_table
unsigned char *pSegment_table = (unsigned char *)malloc(page_header.seg_num);
fread(pSegment_table,sizeof(unsigned char),page_header.seg_num,fp);
// 3、计算段数据总大小
unsigned int TotalSegSize = 0;
int i=0;
for(i=0; i<page_header.seg_num; i++)
{
TotalSegSize += pSegment_table[i];
}
printf("TotalSegSize:%d \n",TotalSegSize);
// 4、读取段数据
unsigned char *pSegment_data = (unsigned char *)malloc(TotalSegSize);
fread(pSegment_data,sizeof(unsigned char),TotalSegSize,fp);
if(page_header.header_type_flag == 4)
printf("Last 4 Byte: %x %x %x %x\n",pSegment_data[TotalSegSize-4],pSegment_data[TotalSegSize-3], pSegment_data[TotalSegSize-2],pSegment_data[TotalSegSize-1]);
free(pSegment_data);
free(pSegment_table);
}
fclose(fp);
return 0;
}
int main()
{
readOggPage("48000Hz-s16le-1ch-ChengDu.opus");
return 0;
}
本文介绍了Ogg支持的编码格式,Ogg的封装过程,Ogg文件结构,以及Ogg的相关术语(物理比特流、逻辑比特流、数据包(packet)、页(page)、段(segment) )等内容。
最后,似乎有点问题,文章只讲了Ogg文件结构和页头部数据,并没有解析各个段的内容。因为段的内容是根据编码不同而变化的,需要再了解跟编码相关的文档如:opus音频编解码器的Ogg封装。这些内容后面单独去分析。
这两天发现了查看RFC文档的一个网址:https://datatracker.ietf.org/,只需要搜索想看的RFC文档,基本都可以搜到。
https://datatracker.ietf.org/doc/rfc7845/,显示格式是标题栏在上面;
https://datatracker.ietf.org/doc/html/rfc7845/,显示格式是标题栏在侧边,个人比较喜欢这个风格。
如果文章有帮助的话,点赞、收藏⭐,支持一波,谢谢
参考文档:
rfc3533:https://datatracker.ietf.org/doc/html/rfc3533
Opus从入门到精通(五)OggOpus封装器全解析:https://juejin.cn/post/6844904016254599175
https://www.cnblogs.com/dylancao/p/8303418.html