浅谈对于 mp3 文件中 VBR 对比 CBR 的一些基本差异

导语

从比特率编码方式的角度来看,目前其中一种最常见的音频文件格式MP3,可以再分为两种类型:一种是恒定比特率CBR(Constant Bit-Rate),这种类型的mp3每一帧的比特率都是恒定唯一的;另外一种就是可变比特率VBR(Variable Bit-Rate),这种类型就跟CBR相反,每一帧的比特率是不固定的,帧与帧之间的比特率可能一样也可能不一样。由于存在这样两种类型,播放mp3文件时需要做的一些工作,比如获取音频信息和播放进度控制,就需要分开处理。

一些基本概念的介绍

要明确理解CBR和VBR的具体差异,就需要先了解音频文件的一个重要属性:比特率(Bitrate),比特率又称码率或者位率,是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,传送数据速度越快。音频中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标。

音频文件的比特率单位一般是kbps,1 kbps = 1000 bps。而mp3的比特率默认是128 kbps,但是目前网络下载到的mp3更为常见的是192 kbps,而如果要获取更加好的音质的高清mp3,比特率通常都要到达320 kbps,通常来讲,比特率越高,音质就越好,但占用磁盘的空间就越大。

一般来说,声音片段的音调越高,就需要更多的空间去存储,比特率就越高。传统的mp3文件是CBR编码的,也就是每一帧的比特率都是相同的,这样就带来了一个问题:如果每一帧的比特率是相同的,那么每一帧的数据大小都是一样的,无论这一帧的音调是高还是低,都是使用整段音频中音调比较高的音频帧的存储空间的大小来存储这一帧,但是对于音调低的音频帧,其实并不需要这么大的存储空间。这样就会造成存储空间的浪费,无形中增大了mp3文件的大小。

VBR编码技术的出现,就是为了解决这个空间浪费的问题。VBR技术对每个音频帧选择最适合这一帧的比特率,对于音调比较低的音频帧,比特率会比较低,数据大小就比较小,音调比较高的则比特率就会比较高,数据大小就比较大。这样就能在不损失音频质量的前提下,节省音频数据的存储空间,进一步压缩mp3的文件大小。

上图简要对比了CBR和VBR两种类型的mp3文件的数据内容上的差别。可以看到,VBR编码的mp3,帧与帧之间由于数据内容的差异,比特率不一定相同,通常VBR技术会在8~320 kbps这个范围压缩编码,所以相比整个文件中比特率都是恒定的CBR编码,VBR编码在整个文件中比特率是浮动可变的,这也是VBR(可变比特率)这个名字的由来。

除了CBR和VBR这两种编码,还存在一种ABR类型(Average Bit-Rate,平均比特率)的编码,它与CBR基本相同,大多数音频帧以指定的比特率编码,但会在个别适当的内容使用高于指定的比特率编码,但是通常这种内容很短,所以在文件大小上跟CBR相比没有太大的差异,因此这种类型并不常见。

VBR技术对比CBR技术存在的缺点

使用VBR技术来编码压缩mp3文件,诚然可以优化文件的大小,但同时在音频信息的获取和播放进度的控制也带来了一些新的问题。

首先是音频时间长度的计算。如果是CBR编码,由于比特率恒定,所有音频帧的数据大小是固定的,所以每一秒播放所需解码的数据大小都是相同的,这样计算音频的时间长度就非常简单。使用以下公式即可:

时间长度(s)=(文件总长度(Byte)- id3字段总大小(如果存在))* 8 /(比特率(kbps) * 1000)

公式中,id3字段是指放在mp3文件开头或末尾的基本信息字段,通常用来记录音频文件的名字,歌手名,专辑名这三个信息,id3分v1和v2两个版本,v1只记录上述的三种信息,且大小固定,一般放在文件末尾;v2则比v1灵活,记录的信息类型不限于上述三种,且大小不固定,一般放在文件开头。id3字段是可选字段,mp3文件不一定有,所以计算mp3的音频时间,需要先读取获知id3是否存在。

对于VBR编码的mp3文件,由于每一帧的比特率是不固定的,所以每一帧的数据大小是任意的。显然这样每秒播放的数据大小都不一样。这样整个音频的时间长度就不能以上述公式计算,需要借助其他的数据字段,这是VBR技术的其中一个缺点:计算音频时长相对困难复杂。

VBR技术还有另外一个缺点,播放音频文件的时候不可避免会有跳到指定时间的位置播放的操作(也就是常说的seek操作),这时就需要把目标的时间位置换算成文件位置,再跳转到这个文件位置偏移读取解码,如果是网络播放的边下载边播放的模式,在seek操作的时候还需要先计算出这个文件位置,跳转到这个位置先下载一段才能继续播放。对于CBR编码,换算成文件位置偏移也很简单,使用的是以下公式:

文件位置(byte) =  目标时间位置(s)*  比特率(kbps)*  1000   /  8  +  id3v2字段大小(如果存在)

但是对于VBR编码,显然也是不能使用这个公式换算出文件位置的。原因也很简单:每一帧的比特率不固定,每秒的数据长度不平均。所以跟计算时长一样,需要借助其他数据字段。

VBR编码计算音频时长和实现seek操作的方法

为了解决上述的两个问题,VBR编码增加了一些数据字段。目前VBR编码技术主要有两种,一种是Xing公司提出的Xing规范,一种是Fraunhofer编码器的VBRI规范,由于使用VBRI规范的VBR编码不常见,大多数VBR编码都是采用Xing规范,所以本文只对Xing规范如何解决音频长度计算和seek操作的实现作介绍。

Xing规范的主要内容是Xing头,这是指VBR编码的mp3的开头第一个音频帧不用来存储具体的音频数据,而是用来存储一些额外的音频信息。这些信息以“Xing”这四个字符作为字段开头的标记(也有部分文件以“Info”这四个字符作为Xing头的开头标记)。

Xing头在第一个音频帧中的位置,是在标准的4个byte的mp3音频帧帧头之后,在帧头和Xing头之间,会有一段数据内容全是0的空白部分,这个空白部分的长度是指定的。解码器在解析到第一个音频帧的帧头之后,就是通过跳过这段指定长度的空白部分,然后判断接下来的内容是否就是‘Xing’或者‘Info’这四个字符,来判断音频是否VBR编码。

空白部分的长度有mpeg版本和声道数决定,具体如下表(单位为byte):

MPEG版本

单声道

非单声道

MPEG 1

18

32(最常见)

MPEG 2

9

18

下图是VBR编码的mp3的第一帧数据的字段结构的例子:

Xing头的字段结构和存储的信息内容如下表:

现在首先看看如何利用Xing头中的信息来计算VBR编码中音频时间的长度。

如果Xing头中包含总帧数这个信息,那么就先把总帧数读出来,假设文件如上表中举例,总帧数是7344。然后乘以每帧采样数,得到总采样数。每帧采样数是mp3文件的固定属性,这个数值由MPEG版本和layer版本决定,与编码类型是CBR还是VBR无关。

每帧采样数详细如下表:

MPEG版本

Layer 1

Layer 2

Layer 3

MPEG 1

384

1152

1152

MPEG 2

384

1152

576

文件是mp3格式,也就是MPEG 1 Layer 3,每帧采样数就是1152,那么总采样数就是

7344 * 1152 = 8460288。

现在得到了总采样数,那么总的音频时间就不难得出了,由于采样率也是音频文件的固定属性,假设是44.1 kHz,所以总的音频时间就是总采样数除以采样率,也就是

8460288 / 44100 = 191 (s)

因此,只要VBR编码的Xing头里带有包含总帧数这个字段(一般都会有),就能计算得出音频时长。

下面再来看看VBR编码如何利用Xing头的信息实现Seek操作。

VBR编码的seek操作主要是利用Xing头中的TOC表(如果这个表存在),TOC表(Table Of Contents)是一个长度为100的byte数组,数组中每个元素都代表在音频时长内的一个特定的时间点对应的文件的相对位置。 简单的说,TOC表的组成,就是把整个文件平均分成256段,每一段代表一个文件位置,再把总的音频时长平均分为100段,每一段代表一个时间点,然后对这100个时间点,每一个都找出256个文件位置中其对应的那个位置,这个位置是这256个位置中的相对位置,取值在0~255,放在TOC表中。

如何使用TOC表实现时间点到文件位置的映射,算法如下:

假设文件持续240秒,现在需要跳到60秒,文件长度为5000000 byte,那么先用以下公式计算出60秒对应TOC表的哪个元素:

TOC[(60 / 240) * 100] = TOC[25]

然后在根据以下公式算出文件位置:

(TOC[25] / 256) * 5000000

但是上述算法,只能在这种情况有效:目标时间点是TOC时间点,也就是目标时间点在把总时长平均分成100份的100个时间点之中,对于目标时间点不在这100个时间点之中的情况,如果参考android系统在解码VBR编码的mp3时候的做法,就是在上述算法的基础上,再算出目标时间点在时间位置上处于那100个TOC时间点中的哪两个相邻的TOC时间点之间,假设这两个TOC时间点对应的相对文件位置是TOC[a]和TOC[b],通过这两个相对文件位置用线性插值的方式算出目标时间点的相对文件位置,进而算出目标文件位置。

android系统源码中利用Xing头的TOC表实现音频时间点和其对应的文件位置的换算,代码如下:

bool XINGSeeker::getOffsetForTime(int64_t *timeUs, off64_t *pos) {
if (mSizeBytes == 0 || !mTOCValid || mDurationUs < 0) {
    return false;
}

float percent = (float)(*timeUs) * 100 / mDurationUs;
float fx;
if( percent <= 0.0f ) {
    fx = 0.0f;
} else if( percent >= 100.0f ) {
    fx = 256.0f;
} else {
    int a = (int)percent;
    float fa, fb;
    if ( a == 0 ) {
        fa = 0.0f;
    } else {
        fa = (float)mTOC[a-1];
    }
    if ( a < 99 ) {
        fb = (float)mTOC[a];
    } else {
        fb = 256.0f;
    }
    fx = fa + (fb-fa)*(percent-a);
}

 *pos = (int)((1.0f/256.0f)*fx*mSizeBytes) + mFirstFramePos;

return true;
}

以上就是VBR编码的mp3播放的seek操作的具体实现原理。

结语

通过以上的分析介绍,我们可以知道,mp3的CBR和VBR两种编码类型各有优劣:在编解码的复杂程度的角度看,CBR相对简单容易操作;在存储空间的利用率的角度看,VBR利用率更高。由于mp3是目前最常见的音频格式,在做客户端的音频解码工作的时候,对这两种编码类型都要做细致的针对性的处理,这样才能减少播放mp3出现的问题,提高播放的体验。

参考资料:

MPEG音频文件格式(包括MP3格式)详解——转载:http://blog.chinaunix.net/uid-20792001-id-1841011.html

MP3 File Structure Description:http://www.multiweb.cz/twoinches/mp3inside.htm

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杂文共赏

卷积神经网络究竟做了什么?

神经学习的一种主要方式就是卷积神经网络(CNN),有许多种方法去描述CNN到底做了什么,一般通过图像分类例子通过数学的或直观的方法来介绍如何训练和使用CNN。

4948
来自专栏计算机视觉与深度学习基础

Codeforces 472D

看官方题解提供的是最小生成树,怎么也想不明白,you can guess and prove it! 看了好几个人的代码,感觉实现思路全都不一样,不得不佩服cf...

18910
来自专栏利炳根的专栏

学习笔记CB013: TensorFlow、TensorBoard、seq2seq

tensorflow基于图结构深度学习框架,内部通过session实现图和计算内核交互。

3987
来自专栏漫漫深度学习路

tensorflow学习笔记(三十六):learning rate decay

learning rate decay 在训练神经网络的时候,通常在训练刚开始的时候使用较大的learning rate, 随着训练的进行,我们会慢慢的减小le...

3786
来自专栏破晓之歌

Python实现线性回归 原

注:从笔记上copy一个网友的数据生成,列数不够,缺少y和x0部分,进行了修改,后面很多次试验用梯度下降方法求解thera都是NAN的结果,经过调试,发现可能是...

663
来自专栏desperate633

LintCode 数字三角形题目分析1 (常规的动态规划解法)分析2 (如果你只用额外空间复杂度O(n)的条件)

给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。 ** 注意事项 如果你只用额外空间复杂度O(n)的条件下完成可以获...

622
来自专栏企鹅号快讯

用keras搭建3D卷积神经网络

资源: 3D卷积神经网络相关博客:http://blog.csdn.net/lengxiaomo123/article/details/68926778 ker...

4867
来自专栏小鹏的专栏

01 TensorFlow入门(2)

Working with Matrices:         了解TensorFlow如何使用矩阵对于通过计算图理解数据流非常重要。 Getting read...

2356
来自专栏MixLab科技+设计实验室

用谷歌新开源的deeplearnJS预测互补颜色

本文翻译自deeplearnJS的示例教程,并结合了我在学习过程中的理解。 deeplearnJS简介: deeplearn.js是用于机器学习的开源WebGL...

2828
来自专栏用户2442861的专栏

Caffe学习系列(6):Blob,Layer and Net以及对应配置文件的编写

http://www.cnblogs.com/denny402/p/5073427.html

501

扫码关注云+社区