美文网首页
pcm编码方式-G711a

pcm编码方式-G711a

作者: 笑而不语ii | 来源:发表于2019-04-18 15:41 被阅读0次

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

    相关文章

      网友评论

          本文标题:pcm编码方式-G711a

          本文链接:https://www.haomeiwen.com/subject/joctgqtx.html