pcm编码方式-G711a

2019-04-18  本文已影响0人  笑而不语ii

前面的学习内容到了第四课就没走了,因为之前的内容还不是很熟悉,再加上公司项目需求就放了一下,回过头折腾了一下音频录制后的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,需要的可以去找一下。

上一篇下一篇

猜你喜欢

热点阅读