Android/Java 实现PCM与G.711编码互转

来源:互联网 发布:蓝牙车载充电器 知乎 编辑:程序博客网 时间:2024/06/05 02:14

背景

最近做一个UDP局域网通讯的项目,是 Android 手机和一个室内硬件外设通讯,因为这个外设的音频传输采用的是 G.711 a率 编码,Android 的 AudioRecord 又不能直接采集 G.711 a率 编码的音频,所以就想采集之后转成 G.711 来传输。

但是网络上搜了好几圈,都是使用的 Android jni 去调用 C++ 代码实现,我去尝试调用了几次都没成功,所以自己仿照着写了一个,经测试可以使用,特此分享。

格式参数

PCM:采样率:8000采样大小:16 BIT 声道:双声道 G.711采样率:8000 采样大小:16 BIT 声道:单声道 

转换工具类: G711Code

    /**     * 核心转换     * Created by onlygx     */    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;            }else{                mask = 0x55;                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 {                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]);            }        }        /**         * 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]);            }        }    }

附录1 音频采集工具类

工具类不是我原创的,但是百度太多已经忘记是在哪里看到的,在此感谢那位作者。
(貌似有好几个都是这么写的,不知道谁是原作者。)

简单给小白说一下,这个工具类需要先初始化,然后设置一个回调来处理采集到的数据。
实现 OnAudioFrameCapturedListener 接口,重写 onAudioFrameCaptured 方法,这是个很重要的回调。
如果不用了,要停止采集。

    import android.media.AudioFormat;    import android.media.AudioRecord;    import android.media.MediaRecorder;    import android.os.SystemClock;    import android.util.Log;    public class AudioCapturer {        private static final String TAG = "AudioCapturer";        private static final int DEFAULT_SOURCE = MediaRecorder.AudioSource.MIC;        private static final int DEFAULT_SAMPLE_RATE = 8000;        private static final int DEFAULT_CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;        private static final int DEFAULT_AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;        private AudioRecord mAudioRecord;        private int mMinBufferSize = 0;        private Thread mCaptureThread;        private boolean mIsCaptureStarted = false;        private volatile boolean mIsLoopExit = false;        private OnAudioFrameCapturedListener mAudioFrameCapturedListener;        public interface OnAudioFrameCapturedListener {            public void onAudioFrameCaptured(short[] audioData);        }        public boolean isCaptureStarted() {            return mIsCaptureStarted;        }        public void setOnAudioFrameCapturedListener(OnAudioFrameCapturedListener listener) {            mAudioFrameCapturedListener = listener;        }        public boolean startCapture() {            return startCapture(DEFAULT_SOURCE, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNEL_CONFIG,                    DEFAULT_AUDIO_FORMAT);        }        public boolean startCapture(int audioSource, int sampleRateInHz, int channelConfig, int audioFormat) {            if (mIsCaptureStarted) {                Log.e(TAG, "Capture already started !");                return false;            }            mMinBufferSize = AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);            if (mMinBufferSize == AudioRecord.ERROR_BAD_VALUE) {                Log.e(TAG, "Invalid parameter !");                return false;            }            Log.d(TAG , "getMinBufferSize = "+mMinBufferSize+" bytes !");            mAudioRecord = new AudioRecord(audioSource,sampleRateInHz                ,channelConfig,audioFormat,mMinBufferSize);            if (mAudioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {                Log.e(TAG, "AudioRecord initialize fail !");                return false;            }            mAudioRecord.startRecording();            mIsLoopExit = false;            mCaptureThread = new Thread(new AudioCaptureRunnable());            mCaptureThread.start();            mIsCaptureStarted = true;            Log.d(TAG, "Start audio capture success !");            return true;        }        public void stopCapture() {            if (!mIsCaptureStarted) {                return;            }            mIsLoopExit = true;            try {                mCaptureThread.interrupt();                mCaptureThread.join(1000);            }            catch (InterruptedException e) {                e.printStackTrace();            }            if (mAudioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {                mAudioRecord.stop();            }            mAudioRecord.release();            mIsCaptureStarted = false;            mAudioFrameCapturedListener = null;            Log.d(TAG, "Stop audio capture success !");        }        private class AudioCaptureRunnable implements Runnable {            @Override            public void run() {                while (!mIsLoopExit) {                    short[] buffer = new short[320];                    int ret = mAudioRecord.read(buffer, 0, 320);                    if (ret == AudioRecord.ERROR_INVALID_OPERATION) {                        Log.e(TAG , "Error ERROR_INVALID_OPERATION");                    }                     else if (ret == AudioRecord.ERROR_BAD_VALUE) {                        Log.e(TAG , "Error ERROR_BAD_VALUE");                    }                    else {                       if (mAudioFrameCapturedListener != null) {                            mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);                        }                        Log.d(TAG , "OK, Captured "+ret+" bytes !");                    }                    SystemClock.sleep(10);                }            }        }    }

附录2 音频播放工具类

    import android.media.AudioFormat;    import android.media.AudioManager;    import android.media.AudioTrack;    import android.util.Log;    public class AudioReader {        private int mFrequency;// 采样率        private int mChannel;// 声道        private int mSampBit; // 采样精度        private AudioTrack mAudioTrack;        public AudioReader(){            mFrequency = 8000;            mChannel = AudioFormat.CHANNEL_OUT_MONO;            mSampBit = AudioFormat.ENCODING_PCM_16BIT;        }        public void init(){            if (mAudioTrack != null){                release();            }            // 获得构建对象的最小缓冲区大小            int minBufSize = AudioTrack.getMinBufferSize(mFrequency,mChannel, mSampBit);            //STREAM_ALARM:警告声            //STREAM_MUSCI:音乐声,例如music等            //STREAM_RING:铃声            //STREAM_SYSTEM:系统声音            //STREAM_VOCIE_CALL:电话声音            mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,                    mFrequency,mChannel,mSampBit,minBufSize, AudioTrack.MODE_STREAM);            //AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。            //STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。            //这个和我们在socket中发送数据一样,应用层从某个地方获取数据,            //例如通过编解码得到PCM数据,然后write到audiotrack。            //这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。            //而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,            //后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。            //这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。            mAudioTrack.play();        }        public void release(){            if (mAudioTrack != null){                mAudioTrack.stop();                mAudioTrack.release();            }        }        public void playAudioTrack(byte []data, int offset, int length){            if (data == null || data.length == 0){return ;}            try {                mAudioTrack.write(data, offset, length);            } catch (Exception e) {                Log.i("MyAudioTrack", "catch exception...");            }        }        public void playAudioTrack(short []data, int offset, int length){            if (data == null || data.length == 0){return ;}            try {                mAudioTrack.write(data, offset, length);            } catch (Exception e) {                Log.i("MyAudioTrack", "catch exception...");            }        }        public int getPrimePlaySize(){            int minBufSize = AudioTrack.getMinBufferSize(mFrequency,                    mChannel, mSampBit);            return minBufSize * 2;        }    }
4 0
原创粉丝点击