pcm编码方式-G711a
前面的学习内容到了第四课就没走了,因为之前的内容还不是很熟悉,再加上公司项目需求就放了一下,回过头折腾了一下音频录制后的pcm编码问题。
项目中音视频的实时传播中,一开始的时候我采用pcm原始数据的直接传输,结果发现数据体很大,很费流量,然后就考虑采用aac或者g711a编码方式进行原始pcm编码。这儿简单讲一下g711a的编码方式,其实网上有很多的讲解,我就说下自己的理解。
什么是g711a
对于g711,它是国际电信联盟ITU-T定制出来的一套语音压缩标准,它代表了对数PCM(logarithmic pulse-code modulation)抽样标准,主要用于电话。它主要用脉冲编码调制对音频采样,采样率为8k每秒。它利用一个 64Kbps 未压缩通道传输语音讯号。 起压缩率为1:2, 即把16位数据压缩成8位。G711是主流的波形声音编解码器。
G711标准下面有两种压缩算法,一种是u-law algorithm(又称offien u-law ,ulaw,mu-law),主要运用于北美和日本;另一种是A-law algorithm,主要运用于欧洲和世界其他地区。其中,后者是特别设计用来方便计算机处理的。
G711的内容是将14bit(uLaw)或者13bit(aLaw)采样的PCM数据编码成8bit的数据流,播放的时候在将此8bit的数据还原成14bit或者13bit进行播放,不同于MPEG这种对于整体或者一段数据进行考虑再进行编解码的做法,G711是波形编解码算法,就是一个sample对应一个编码,所以压缩比固定为:
8/14 = 57% (uLaw)
8/13 = 62% (aLaw)
G.711就是语音模拟信号的一种非线性量化, bitrate 是64kbps. 详细的资料可以在ITU 上下到相关的spec,下面主要列出一些性能参数:
G.711(PCM方式)
• 采样率:8kHz
• 信息量:64kbps/channel
• 理论延迟:0.125msec
• 品质:MOS值4.10
原理
至于原理就不多做说明,都推出高数了,在下早就晕了,忽略原理,有兴趣的同学可以直接查一查
G711A (a-law)压缩十三折线法
g711a输入的是13位(S16的高13位),这种格式是经过特别设计的,便于数字设备进行快速运算。
1.取符号位并取反得到s
2.获取强度位eee,获取方法如下图所示
3.获取高位样本位wxyz
4.组合为seeewxyz,将seeewxyz逢偶数位取补数,编码就完成了
A-law如下表计算,第一列是采样点,共13bit,最高位为符号位。对于前两行,折线斜率均为1/2,跟负半段的相应区域位于同一段折线上,对于3到8行,斜率分别是1/4到1/128,共6段折线,加上负半段对应的6段折线,总共13段折线,这就是所谓的A-law十三段折线法。
image.png接下来做个简单的算法步骤演示:获取到数值为1234的pcm数据
二进制表示为:0000 0100 1101 0010
二进制变换下排列组合方式:0 00001 0011 010010
1.取符号位取反得到 s=1
2.获取强度位00001 查上表得到eee=011
3.获取高位样本位wxyz:0011
4.组合为seeewxyz就是10110011,逢偶取反为:11100110。编码完成
下面贴上android编码的代码,注释都有,就不一一说了。
package com.liu.autiorecord.utils;
import android.util.Log;
public class G711Code {
private final static int SIGN_BIT = 0x80;
private final static int QUANT_MASK = 0xf;
private final static int SEG_SHIFT = 4;
private final static int SEG_MASK = 0x70;
static short[] seg_end = {0xFF, 0x1FF, 0x3FF, 0x7FF,0xFFF, 0x1FFF, 0x3FFF, 0x7FFF};
static short search(short val,short[] table,short size){
for (short i = 0 ; i < size; i++) {
if(val <= table[i]){
return i;
}
}
return size;
}
static byte linear2alaw(short pcm_val){
short mask;
short seg;
char aval;
if(pcm_val >= 0){
mask = 0xD5; //* sign (7th) bit = 1 二进制的11010101
}else{
mask = 0x55; //* sign bit = 0 二进制的01010101
pcm_val = (short) (-pcm_val - 1);//负数转换为正数计算
if(pcm_val < 0){
pcm_val = 32767;
}
}
/* Convert the scaled magnitude to segment number. */
seg = search(pcm_val, seg_end, (short) 8); //查找采样值对应哪一段折线
/* Combine the sign, segment, and quantization bits. */
if (seg >= 8) /* out of range, return maximum value. */
return (byte) (0x7F ^ mask);
else {
//以下按照表格第一二列进行处理,低4位是数据,5~7位是指数,最高位是符号
aval = (char) (seg << SEG_SHIFT);
if (seg < 2)
aval |= (pcm_val >> 4) & QUANT_MASK;
else
aval |= (pcm_val >> (seg + 3)) & QUANT_MASK;
return (byte) (aval ^ mask);
}
}
static short alaw2linear(byte a_val){
short t;
short seg;
a_val ^= 0x55;
t = (short) ((a_val & QUANT_MASK) << 4);
seg = (short) ((a_val & SEG_MASK) >> SEG_SHIFT);
switch (seg) {
case 0:
t += 8;
break;
case 1:
t += 0x108;
break;
default:
t += 0x108;
t <<= seg - 1;
}
return (a_val & SIGN_BIT) != 0 ? t : (short) -t;
}
/**
* pcm 转 G711 a率
* @param pcm
* @param code
* @param size
*/
public static void G711aEncoder(short[] pcm,byte[] code,int size){
for(int i=0;i<size;i++){
code[i]=linear2alaw(pcm[i]);
Log.e("-------------","数据编码");
}
}
/**
* G.711 转 PCM
* @param pcm
* @param code
* @param size
*/
public static void G711aDecoder(short[] pcm,byte[] code,int size)
{
for(int i=0;i<size;i++){
pcm[i]=alaw2linear(code[i]);
}
}
}
至于声音pcm数据的获取,前面写的关于AudioRecord的文章有所,不懂得可以去看看。
之前采用的是
int read = audioRecord.read(data,0,minBufferSize);
这儿因为需要short[] 则使用另外一个read方法:
short[] inG711Buffer = new short[minBufferSize];
byte[] outG711Buffer = new byte[minBufferSize];
int nReadBytes = audioRecord.read(inG711Buffer,0,inG711Buffer.length);
调用上面G711Code工具类里面的方法进行编码,其中outG711Buffer既我们编码后需要的数据
G711Code.G711aEncoder(inG711Buffer,outG711Buffer,nReadBytes);
写本地检测
try {
//写pcm本地
//os.write(data);
//写G711本地
os.write(outG711Buffer);
} catch (IOException e) {
e.printStackTrace();
}
代码已上传git仓库 demo地址
有的小伙伴可能不知道这类的原始数据要怎么播放,这儿推荐一个音频播放器 Audacity,需要的可以去找一下。