回 帖 发 新 帖 刷新版面

主题:OGG文件的数据结构以及读取其注释信息的代码

OGG文件的数据结构以及读取其注释信息的代码

笔者的手机原配的铃声都是ogg文件,所以笔者研究了一下这种文件的数据结构。
Vorbis是一种有损音频压缩格式,通常以Ogg作为容器格式,所以常合称为Ogg Vorbis,所形成的
文件后缀是Ogg。

一、OGG 文件的组织形式
ogg文件解码后,按应用要求的时序关系合成若干物理流,一个物理流由若干逻辑流组成,一个逻
辑流由若干包(Packet)组成。
但ogg文件本身是由页(page)组成的,这样,在形成ogg文件的时候,就要将逻辑流的各个包分割
为若干区段(segment)后再进行页封装,每页都加上页头。一个区段的长度最多为255字节,一页最多
封装255个区段。如果几个包的总长度≤255个区段,那么这几个包的区段可以封装在一页;如果一个包
长度>255个区段,那么就会被被封装成两页或多页,下一个包必须用新的页开始封装。
OGG 文件的基本组织形式见表1:

表1 OGG 文件流的基本组织形式
----------------------------------------------------------------------
A*  B*  C*  …  A#  B#  C#  D*   D#
----------------------------------------------------------------------
bos  bos  bos       eos  eos   eos    bos  eos
----------------------------------------------------------------------
说明:bos为开始流,eos为结束流。

可以看出,文件链接了两个物理流,A、B和C三个逻辑流组成一个物理流,逻辑流D单独是一个物理
流。一个物理流中的所有逻辑流的bos页都必须在物理位置上相邻,如表1所示*A*、*B*、*C*三个bos页
的位置。
逻辑流包括有语音流、文本流、图片流、音频流、视频流等。


二、OGG 页结构
每页之间相互独立,都包含了各自应有的信息,页的大小是可变的,通常为4-8KB,最大值不超过
65307字节(27+255+255×255=65307)。页由页头部(pageheader)和页数据(pagedata)组成,
页头部格式见表2。注:ogg文件中有关长度和大小的计算均使用小端字节序列格式。

表2 OGG 页结构
--------------------------------------------------------------------------------
域名称      占用字节  描述
--------------------------------------------------------------------------------
capture_pattern    4  页标识,"OggS"的ASCII字符 4F 67 67 53
structure_version   1  版本ID,当前版本默认=0
Header_type_flag   1  页头部类型
Granule_position   8  区段位置
Serial_number     4  逻辑流的序列号
Page_seguence_number 4  本页在逻辑流的序号,OGG解码器据此识别有无页丢失。
CRC_cbecksum     4  循环冗余校验码校验和
Number_page_segments 1  本页的区段数量,指明区段表中有多少个区段长度,≤255
Segment_table    ≤255 区段长度表,每个字节表示一个区段的长度
--------------------------------------------------------------------------------
说明:
①页标识:标识着一个页的开始。其作用是分离Ogg封装格式还原媒体编码时识别新页。
②头部类型1字节8位值前3位的意义:
第1位=1:本页的媒体编码数据与前一页属于同一个逻辑流的同一个包,=0:表示本页是新包。
第2位=1:本页为逻辑流的第一页bos;=0:不是第一页。
第3位=1:本页为逻辑流的最后一页eos;=0:不是最后一页。
③区段位置不是指区段在文件中的位置,而是指区段在逻辑流中的位置。它存储了媒体编码相关的参数
信息,对于音频流来说,它存储着到本页为止逻辑流在PCM输出中采样码的数目,可以由它来算得时
间戳。对于视频流来说,它存储着到本页为止视频帧编码的数目。若此值=0,表示截止到本页,逻
辑流的包未结束。
④流序列号,即本页所在的流ID,它是区分本页所属逻辑流与其他逻辑流的序号,可以通过这个值来划
分流。
⑤循环冗余校验码校验和包含页的32位CRC校验(头部零CRC校验、页数据校验)的产生多项式为:
0x04c11db7。
⑥区段长度表记录着逻辑流中的每个包中每个区段的长度值,取值范围是0-255。包中的最后一个区段
长度<255,其它区段长度都=255。这些值以区段出现的先后顺序排列。此域的字节数是区段数量域
所表示的数字,即在0-255字节之间。从区段长度表中可以计算出每个包的长度,例如:区段表中的
值为 4D FF 45 FF FF FF 40 FF FF 66,那么:
第一包有1个区段,总长度=4D
第二包有2个区段,总长度=FF+45
第三包有4个区段,总长度=FF+FF+FF+40
第四包有3个区段,总长度=FF+FF+66
⑦页头部的长度和整个页的长度计算:
页头部长度=27+区段数量
页长度=页头长度+区段长度表中每个区段的大小=页头部长度+所有区段长度之和。
⑧页头部后面紧接着页数据,页数据包括本页所有的区段数据。


三、OGG Vorbis 比特流的结构
Ogg文件解码后形成比特流,比特流最前面是三个包头,按照在文件中的顺序依次是:标识头(id-
entification header)、注释头(comment header)和装备头(Setup Header)。标识头设置了版本和
流的简单音频特性(如采样率和声道数目等),注释头包括用户文本注释和供应商以及封装软件产生的
字符串,装备头包括所需的解码器装备信息,以及完整的VQ和译码本。通常情况下,标识头分割在ogg文
件第1页,注释头和装备头分割在ogg文件第2页,这些包头数据也就是所在页的页数据。从第3页开始的
页数据才是真正的媒体流的压缩数据。三个包头的结构分别见表3、表4、表6.

表3 标识头结构
---------------------------------------------------------------------
域名称    占用字节  描述
---------------------------------------------------------------------
header_type_flag  1 =1:包头类型为标识包
packet_pattern   6 =76 6F 72 62 69 73,包头标识,vorbis的Ascii码
vorbis_version   4 版本
audio_channels   1 声道数目,必须>0
audio_sample_rate 4 音频采样率,必须>0
bitrate_maximum  4 最大比特率
bitrate_nominal  4 标称比特率
bitrate_minimum  4 最小比特率
blocksize_0      块大小0: 占用4位,与blocksize_1共占用1字节
blocksize_1      块大小1: 占用4位,必须≥blocksize_0
framing_flag    1 =1,边界标志,表示标识头结束
---------------------------------------------------------------------
说明:
①比特率域仅作为提示。尤其是标称比特率,是纯粹VBR流,只有>0,该域才是有意义的。如果三
个比特率域设置为相同的值,意味着固定速率比特流,或者有严格边界但接近固定速率的比特流。仅设
置标称比特率意味着只有一个 VBR(可变位速率) 或 ABR(平均位速率) 流。设置最大或最小比特率
意味着一个 VBR 比特流,遵守比特率限制。没有设置表明由编码器自行处理。
②块大小域不知为何意。


表4 注释头的结构
----------------------------------------------------------------------
域名称    占用字节  描述
----------------------------------------------------------------------
header_type_flag 1  =3:包头类型为注释包
packet_pattern  6  =76 6F 72 62 69 73,包头标识,vorbis 的Ascii码
companyinfolength 4  制作软件信息所占用的字节数
companyinfo      制作软件信息
retention_byte  4  保留字节
comment[1]_length 4  注释[1]字符串所占用的字节数
comment[1]       注释[1]内容
……
comment[N]长度  4  注释[N]字符串所占用的字节数
comment[N]       注释[N]内容
framing_flag   1  =1,边界标志,表示注释头结束
----------------------------------------------------------------------
说明:
①注释名称后面用等号连接注释内容。
②常用的注释名称见表5。
③注释名称是可以重复的。例如:如果一个曲目由三个艺术家共同演唱,那么以下情况是允许的:
ARTIST=张三
ARTIST=李四
ARTIST=王五


表5 常用注释名称
---------------------------------
注释名称    中译义
---------------------------------
ALBUM      专辑
ARTIST     艺术家
COPYRIGHT    版权
DATE      日期
DESCRIPTION   描述
GENRE      风格
CONTACT     联系人
ISRC      国际标准记录代码
LICENSE     许可证
LOCATION    声道位置
ORGANIZATION  公司名
PERFORMER    表演者
TITLE      标题
TRACKNUMBER   曲目号
TYER      年代
VERSION     版本
---------------------------------
说明:用户也可以自己创新注释名称。


表6 装备头的结构
----------------------------------------------------------------------------------------
域名称             占用字节 描述
----------------------------------------------------------------------------------------
header_type_flag           1  =5:包头类型为装备包
packet_pattern            6  =76 6F 72 62 69 73,包头标识,vorbis的Ascii码
lists of codebook configurations     码本结构列表
time-domain transform configurations   时间戳转换配置
floor configurations           底层配置
residue configurations          剩余配置
channel mapping configurations      信道映射的配置
mode configurations           模式配置
----------------------------------------------------------------------------------------
说明:装备头后面紧接着的就是真正的媒体压缩数据流了。


四、实例解析
下面是我手机里的 Lock.ogg 的部分数据:
-------------------------------------------------------------------------
0000: 4F 67 67 53 00 02 00 00 00 00 00 00 00 00 82 78   OggS..........倄
0010: 00 00 00 00 00 00 12 85 4E 81 01 1E 01 76 6F 72   .......匩....vor
0020: 62 69 73 00 00 00 00 01 44 AC 00 00 FF FF FF FF   bis.....D.......
0030: 00 F4 01 00 FF FF FF FF B8 01 4F 67 67 53 00 00   ..........OggS..
0040: 00 00 00 00 00 00 00 00 82 78 00 00 01 00 00 00   ........倄......
0050: CC 63 C9 DB 0F 4D FF FF FF FF FF FF FF FF FF FF   蘡邵.M..........
0060: FF FF FF E8 03 76 6F 72 62 69 73 1D 00 00 00 58   .....vorbis....X
0070: 69 70 68 2E 4F 72 67 20 6C 69 62 56 6F 72 62 69   iph.Org libVorbi
0080: 73 20 49 20 32 30 30 34 30 36 32 39 01 00 00 00   s I 20040629....
0090: 1C 00 00 00 45 4E 43 4F 44 45 52 3D 41 64 6F 62   ....ENCODER=Adob
00A0: 65 28 52 29 20 41 75 64 69 74 69 6F 6E 28 52 29   e(R) Audition(R)
00B0: 01 05 76 6F 72 62 69 73 29 42 43 56 01 00 08 00   ..vorbis)BCV....
……
-------------------------------------------------------------------------
解析:
0000-0039:第一页
  0000-001B:页头部
    0000-0003=4F 67 67 53:页标识,OggS的Ascii字符
    0004=00:版本号为0
    0005=02:页头部类型:本页为逻辑流的第一页bos
    0006-000D=0:区段位置为0
    000E-0011=82 78 00 00:逻辑流ID
    0012-0015=0:本页在逻辑流中的序号为0
    0016-0019=12 85 4E 81:循环冗余校验码校验和
    001A=01:区段表中有1个区段
    001B=1E:区段表中的区段长度为 1E
  001C-0039:页数据(0039=1C+1E-1)
    001C=01:包头类型为标识包,包长度为1E(001C-0039),是区段表中区段的长度
    001D-0022=76 6F 72 62 69 73:包头标识,vorbis的Ascii码
    0023-0026=0:版本号为0
    0027=01:单声道
    0028-002B=44 AC 00 00:音频采样率为44.1KHZ(&HAC44=44100)
    002C-002F=FF FF FF FF:最大比特率未设置
    0030-0033=00 F4 01 00:标称比特率
    0034-0037=FF FF FF FF:最小比特率未设置
    0038=B8:块大小0为二进制的1011,块大小1为二进制的1000
    0039=01:标识包结束
003A-0E8B:第二页
  003A-0063:页头部
    003A-003D=4F 67 67 53:页标识,OggS的Ascii字符
    003E=00:版本号为0
    003F=00:页头部类型:本页为新包,不是逻辑流的第一页,也不是最后一页
    0040-0047=0:区段位置为0
    0048-004B=82 78 00 00:逻辑流ID
    004C-004F=01 00 00 00:本页在逻辑流中的序号为1
    0050-0053=CC 63 C9 DB:循环冗余校验码校验和
    0054=0F:区段表中有15个区段
    0055-0063=4D FF FF FF FF FF FF FF FF FF FF FF FF FF E8:区段表中15个区段的长度
  0064-0E8B:页数据(0E8B=64+4D+FF*D+E8-1)
    0064=03:包头类型为注释包,包长度为4D(0064-00B0),是区段表中第1个区段的长度
    0065-006A=76 6F 72 62 69 73:包头标识,vorbis的Ascii码
    006B-006E=1D 00 00 00:制作软件信息的长度为29字节
    006F-008B=制作软件信息字符串:Xiph.Org libVorbis I 20040629
    008C-008F=01 00 00 00:保留字节
    0090-0093=1C 00 00 00:注释[1]长度为28字节
    0094-00AF=注释[1]字符串:ENCODER=Adobe(R) Audition(R)
    00B0=01:注释包结束
    00B1=05:包头类型为装备包
    00B2-00B7=76 6F 72 62 69 73:包头标识,vorbis的Ascii码
……


五、提取注释信息的代码

Private Sub Command1_Click()
On Error GoTo 100
Dim oggData() As Byte, teme() As Byte, i As Integer, k As Integer, tLen As Integer
Dim OpenName As String, z As String, st As String

OpenName="(全路径ogg文件名)"

ReDim oggData(FileLen(OpenName) - 1)
Open OpenName For Binary As #1
Get #1, , oggData

teme = StrConv("vorbis", vbFromUnicode)
k = InStrB(InStrB(oggData, teme) + 6, oggData, teme) - 1 '查找第2个vorbis位置
tLen = oggData(k + 6) - 1 '获取制作软件信息长度
k = k + 10
GoSub 200
st = "SOFTWAREinfo=" & z
k = k + 4

Do
  tLen = oggData(k) - 1    '获取注释长度
  If tLen = 0 Then Exit Do '如果是边界标志,退出
  k = k + 4
  GoSub 200
  st = st & vbCrLf & z
Loop Until k > 255

Text1 = st
100
Close
Exit Sub

200
ReDim teme(tLen)
For i = 0 To tLen: teme(i) = oggData(k): k = k + 1: Next
z = IIf(IsTextUTF8(teme), UTF_8ToTxt(teme), StrConv(teme, vbUnicode))
Return
End Sub

Private Function IsTextUTF8(bytSrc() As Byte) As Boolean '有的ogg文件的注释信息是UTF-8编码,必须加以判断
Dim i As Integer, AscN As Integer, n As Integer
n = UBound(bytSrc)
Do While i <= n
  If bytSrc(i) < 128 Then 'ascii字符
    i = i + 1: AscN = AscN + 1
  ElseIf (bytSrc(i) And &HF0) = &HE0 Then '3个字节的UTF-8
    If (bytSrc(i + 1) And &HC0) = &H80 Then
      If (bytSrc(i + 2) And &HC0) = &H80 Or (bytSrc(i + 2) And &HC0) = 0 Then i = i + 3 Else Exit Function
    Else
      Exit Function
    End If
  Else
    Exit Function
  End If
Loop
IsTextUTF8 = (AscN <> n + 1)
End Function

Private Function UTF_8ToTxt(bytSrc() As Byte) As String 'UTF_8编码转换为普通文本
On Error GoTo 100
Dim tem() As Byte, L As Integer, k As Integer, i As Integer
k = UBound(bytSrc)
ReDim tem(k * 2) As Byte
For i = 0 To k
  If bytSrc(i) < 128 Then
    tem(L) = bytSrc(i)
  Else
    tem(L + 1) = ((bytSrc(i) And 15) * 16 + (bytSrc(i + 1) And 60) / 4)
    tem(L) = (bytSrc(i + 1) And 3) * 64 + (bytSrc(i + 2) And 63)
    i = i + 2
  End If
  L = L + 2
Next
ReDim Preserve tem(L - 1) As Byte
UTF_8ToTxt = tem
100
End Function

回复列表 (共2个回复)

沙发

秋水老师还是一如既往的喜爱着文件格式研究啊。

板凳

我感觉文件数据处理迫切需要有一个处理二进制数据的函数库。包含一些可能常用的功能函数。

我来回复

您尚未登录,请登录后再回复。点此登录或注册