【android开发】实现语音数据实时采集/播放

最近做的项目是和语音实时采集并发送,对方实时接收并播放相关,下面记录下实现的核心代码。 很多android开发者应该知道android有个MediaRecorder对象和MediaPlayer对象,用于录制和播放音频。这个弊端在于他们不能实时采集并发送出去,所以,我们只能使用AudioRecord和AudioTrack来实现。 记得申明权限:

<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /><uses-permission android:name="android.permission.RECORD_AUDIO" >

一、AudioRecord实现核心代码介绍如下: 1、先申明相关录制配置参数

private AudioRecord audioRecord;// 录音对象private int frequence = 8000;// 采样率 8000private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)private byte[] buffer = null;// 录制的缓冲数组

2、在开始录制前,我们需要初始化AudioRecord类。

// 根据定义好的几个配置,来获取合适的缓冲大小// int bufferSize = 800;int bufferSize = AudioRecord.getMinBufferSize(frequence,        channelInConfig, audioEncoding);// 实例化AudioRecordaudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,        frequence, channelInConfig, audioEncoding, bufferSize);// 定义缓冲数组buffer = new byte[bufferSize];

3、准备开始录制,使用循环不断读取数据。

audioRecord.startRecording();// 开始录制isRecording = true;// 设置录制标记为true// 开始录制while (isRecording) {// 录制的内容放置到了buffer中,result代表存储长度int result = audioRecord.read(buffer, 0, buffer.length);/*.....result为buffer中录制数据的长度(貌似基本上都是640)。剩下就是处理buffer了,是发送出去还是直接播放,这个随便你。*/}//录制循环结束后,记得关闭录制!!if (audioRecord != null) {    audioRecord.stop();}

二、AudioTrack代码实现介绍如下: 1、声明播放相关配置。

private AudioTrack track = null;// 录音文件播放对象private int frequence = 8000;// 采样率 8000private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)private int bufferSize = -1;// 播放缓冲大小

2、初始化AudioTrack对象(初始化一次,该对象可重复使用)

// 获取缓冲 大小bufferSize = AudioTrack.getMinBufferSize(frequence, channelInConfig,        audioEncoding);// 实例AudioTracktrack = new AudioTrack(AudioManager.STREAM_MUSIC, frequence,        channelInConfig, audioEncoding, bufferSize,        AudioTrack.MODE_STREAM);

3、使用AudioTrack播放语音数据。

//将语音数据写入即可。track.write(dataArray, buffer, len);

问题一: 由于目前的项目是实时采集,实时发送,所以需要考虑到包的大小,经测试,我们使用160个byte作为一个包传递可以做到比较良好的播放效果(也就是将一份buffer拆分成四个发送)。处理代码如下:

// 将数据通过监听接口回调出去if (audioRecordingCallback != null) {    int offset = result % MAX_DATA_LENGTH > 0 ? 1 : 0;    //将一个buffer拆分成几份小数据包 MAX_DATA_LENGTH 为包的最大byte数    for (int i = 0; i < result / MAX_DATA_LENGTH + offset; i++) {        int length = MAX_DATA_LENGTH;        if ((i + 1) * MAX_DATA_LENGTH > result) {            length = result - i * MAX_DATA_LENGTH;        }    //写到回调接口    audioRecordingCallback.onRecording(buffer, i            * MAX_DATA_LENGTH, length);    }}

问题二: 有时候传输的过来播放声音会一卡一卡的,为了解决这样的问题,暂时使用了语音双缓冲机制来解决,问题优化很明显。代码和示意图如下:

【有朋友说要源码,那我就贴下】


【声音采集的源码】

/** * 实时音频录制处理类<br/> * 记得申明系统权限:MODIFY_AUDIO_SETTINGS、RECORD_AUDIO<br/> * 使用实例代码:<br/> *  * <pre> * audioRecoderHandler = new AudioRecoderHandler(this); * audioRecoderHandler.startRecord(new AudioRecordingCallback() { *  @Override *  public void onStopRecord(String savedPath) { *  *  } *  *  @Override *  public void onRecording(byte[] data, int startIndex, int length) { *      // TODO 录制监听。处理data即可。立即播放or发送出去,随你。 *  } * }); * </pre> *  * @author 李长军 *  */@SuppressWarnings("deprecation")public class AudioRecoderHandler {    /**     * 录音数据单次回调数组最大为多少     */    private static int MAX_DATA_LENGTH = 160;    private AudioRecord audioRecord;// 录音对象    private boolean isRecording = false;// 标记是否正在录音中    private int frequence = 8000;// 采样率 8000    private int channelInConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;// 定义采样通道(过时,但是使用其他的又不行    private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)    private byte[] buffer = null;// 录制的缓冲数组    private File lastCacheFile = null;// 记录上次录制的文件名    public CacheCleanManager cacheManager;    public AudioRecoderHandler(Context context) {        if (context == null) {            throw new RuntimeException("Context could not be null!");        }    }    /**     * 开始录制音频     *      * @param callBackListener     *            录制过程中的回调函数     */    public void startRecord(AudioRecordingCallback audioRecordingCallback) {        RecordTask task = new RecordTask(audioRecordingCallback);        task.execute();// 开始执行    }    /**     * 停止录制     */    public void stoppRecord() {        isRecording = false;    }    /**     * 删除上次录制的文件(一般是用户取消发送导致删除上次录制的内容)     *      * @return true表示删除成功,false表示删除失败,一般是没有上次录制的文件,或者文件已经被删除了     */    public boolean deleteLastRecordFile() {        boolean success = false;        if (lastCacheFile != null && lastCacheFile.exists()) {            success = lastCacheFile.delete();        }        return success;    }    /**     * 录制音频的任务类     *      * @author 李长军     *      */    private class RecordTask extends AsyncTask<String, Integer, String> {        private AudioRecordingCallback audioRecordingCallback = null;        public RecordTask(AudioRecordingCallback audioRecordingCallback) {            this.audioRecordingCallback = audioRecordingCallback;        }        @Override        protected void onPreExecute() {            // 根据定义好的几个配置,来获取合适的缓冲大小            // int bufferSize = 800;            int bufferSize = AudioRecord.getMinBufferSize(frequence,                    channelInConfig, audioEncoding);            // 实例化AudioRecord            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,                    frequence, channelInConfig, audioEncoding, bufferSize);            // 定义缓冲数组            buffer = new byte[bufferSize];            MAX_DATA_LENGTH = bufferSize / 2;            audioRecord.startRecording();// 开始录制            isRecording = true;// 设置录制标记为true        }        @Override        protected void onPostExecute(String result) {            audioRecord = null;            if (result == null) {                lastCacheFile = null;            } else {                lastCacheFile = new File(result);            }            if (audioRecordingCallback != null) {                audioRecordingCallback.onStopRecord(result);            }        }        @Override        protected String doInBackground(String... params) {            String tempFileName = null;            // 开始录制            while (isRecording) {                // 录制的内容放置到了buffer中,result代表存储长度                int result = audioRecord.read(buffer, 0, buffer.length);                // 将数据回调出去                if (audioRecordingCallback != null) {                    int offset = result % MAX_DATA_LENGTH > 0 ? 1 : 0;                    for (int i = 0; i < result / MAX_DATA_LENGTH + offset; i++) {                        int length = MAX_DATA_LENGTH;                        if ((i + 1) * MAX_DATA_LENGTH > result) {                            length = result - i * MAX_DATA_LENGTH;                        }                        audioRecordingCallback.onRecording(buffer, i                                * MAX_DATA_LENGTH, length);                    }                }            }            if (audioRecord != null) {                audioRecord.stop();            }            return tempFileName;        }    }    /**     * 监听录制过程,用于实时获取录音数据     *      * @author 李长军     *      */    public static interface AudioRecordingCallback {        /**         * 录音数据获取回调         *          * @param data         *            数据数组对象         * @param startIndex         *            数据其开始         * @param length         *            数据的结尾         */        public void onRecording(byte[] data, int startIndex, int length);        /**         * 录音结束后的回调         *          * @param savedPath         *            录音文件存储的路径         */        public void onStopRecord(String savedPath);    }    /**     * 释放资源     */    public void release() {        if (audioRecord != null) {            audioRecord.release();            audioRecord = null;        }    }}

【声音播放的源码】

/** * 实时音频播放处理类<br/> * 使用示例代码如下:<br/> *  * <pre> * audioPlayerHandler = new AudioPlayerHandler(); * audioPlayerHandler.prepare();// 播放前需要prepare。可以重复prepare * // 直接将需要播放的数据传入即可 * audioPlayerHandler.onPlaying(data, 0, data.length); * </pre> *  * @author 李长军 *  */@SuppressWarnings("deprecation")public class AudioPlayerHandler implements Runnable {    private AudioTrack track = null;// 录音文件播放对象    private boolean isPlaying = false;// 标记是否正在录音中    private int frequence = 8000;// 采样率 8000    private int channelInConfig = AudioFormat.CHANNEL_OUT_MONO;// 定义采样为双声道(过时,但是使用其他的又不行    private int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;// 定义音频编码(16位)    private int bufferSize = -1;// 播放缓冲大小    private LinkedBlockingDeque<Object> dataQueue = new LinkedBlockingDeque<>();    // 互斥信号量    private Semaphore semaphore = new Semaphore(1);    // 是否释放资源的标志位    private boolean release = false;    public AudioPlayerHandler() {        // 获取缓冲 大小        bufferSize = AudioTrack.getMinBufferSize(frequence, channelInConfig,                audioEncoding);        // 实例AudioTrack        track = new AudioTrack(AudioManager.STREAM_MUSIC, frequence,                channelInConfig, audioEncoding, bufferSize,                AudioTrack.MODE_STREAM);        track.setStereoVolume(AudioTrack.getMaxVolume(),                AudioTrack.getMaxVolume());        try {            // 默认需要抢占一个信号量。防止播放进程执行            semaphore.acquire();        } catch (InterruptedException e) {            e.printStackTrace();        }        // 开启播放线程        new Thread(this).start();    }    /**     * 播放,当有新数据传入时,     *      * @param data     *            语音byte数组     * @param startIndex     *            开始的偏移量     * @param length     *            数据长度     */    public synchronized void onPlaying(byte[] data, int startIndex, int length) {        if (AudioTrack.ERROR_BAD_VALUE == bufferSize) {// 初始化错误            return;        }        try {            dataQueue.putLast(data);            semaphore.release();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * 准备播放     */    public void prepare() {        if (track != null && !isPlaying) {            track.play();            isPlaying = true;        }    }    /**     * 停止播放     */    public void stop() {        if (track != null) {            track.stop();            isPlaying = false;        }    }    /**     * 释放资源     */    public void release() {        release = true;        semaphore.release();        if (track != null) {            track.release();            track = null;        }    }    @Override    public void run() {        while (true) {            if (release) {                return;            }            if (dataQueue.size() > 0) {                byte[] data = (byte[]) dataQueue.pollFirst();                track.write(data, 0, data.length);            } else {                try {                    semaphore.acquire();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

那么前世我的目光一定一刻都没从你身上离开过吧!

【android开发】实现语音数据实时采集/播放

相关文章:

你感兴趣的文章:

标签云: