本文重点关注如何在Android平台上采集一帧音频数据。阅读本文之前,建议先读一下我的上一篇文章《Android音频开发(1):基础知识》,因为音频开发过程中,经常要涉及到这些基础知识,掌握了这些重要的概念后,开发过程中的很多参数和流程就会更加容易理解。


Android SDK 提供了两套音频采集的API,分别是:MediaRecorder 和 AudioRecord,前者是一个更加上层一点的API,它可以直接把手机麦克风录入的音频数据进行编码压缩(如AMR、MP3等)并存成文件,而后者则更接近底层,能够更加自由灵活地控制,可以得到原始的一帧帧PCM音频数据。


如果想简单地做一个录音机,录制成音频文件,则推荐使用 MediaRecorder,而如果需要对音频做进一步的算法处理、或者采用第三方的编码库进行压缩、以及网络传输等应用,则建议使用 AudioRecord,其实 MediaRecorder 底层也是调用了 AudioRecord 与 Android Framework 层的 AudioFlinger 进行交互的。


音频的开发,更广泛地应用不仅仅局限于本地录音,因此,我们需要重点掌握如何利用更加底层的 AudioRecord API 来采集音频数据(注意,使用它采集到的音频数据是原始的PCM格式,想压缩为mp3,aac等格式的话,还需要专门调用编码器进行编码)。


1. AudioRecord 的工作流程


首先,我们了解一下 AudioRecord 的工作流程:


(1) 配置参数,初始化内部的音频缓冲区

(2) 开始采集

(3) 需要一个线程,不断地从 AudioRecord 的缓冲区将音频数据“读”出来,注意,这个过程一定要及时,否则就会出现“overrun”的错误,该错误在音频开发中比较常见,意味着应用层没有及时地“取走”音频数据,导致内部的音频缓冲区溢出。

(4) 停止采集,释放资源


2. AudioRecord 的参数配置


Android音频开发(2):如何采集一帧音频_第1张图片


上面是 AudioRecord 的构造函数,我们可以发现,它主要是靠构造函数来配置采集参数的,下面我们来一一解释这些参数的含义(建议对照着我的上一篇文章来理解):


(1) audioSource


该参数指的是音频采集的输入源,可选的值以常量的形式定义在 MediaRecorder.AudioSource 类中,常用的值包括:DEFAULT(默认),VOICE_RECOGNITION(用于语音识别,等同于DEFAULT),MIC(由手机麦克风输入),VOICE_COMMUNICATION(用于VoIP应用)等等。


(2) sampleRateInHz


采样率,注意,目前44100Hz是唯一可以保证兼容所有Android手机的采样率。


(3) channelConfig


通道数的配置,可选的值以常量的形式定义在 AudioFormat 类中,常用的是 CHANNEL_IN_MONO(单通道),CHANNEL_IN_STEREO(双通道)


(4) audioFormat


这个参数是用来配置“数据位宽”的,可选的值也是以常量的形式定义在 AudioFormat 类中,常用的是 ENCODING_PCM_16BIT(16bit),ENCODING_PCM_8BIT(8bit),注意,前者是可以保证兼容所有Android手机的。


(5) bufferSizeInBytes


这个是最难理解又最重要的一个参数,它配置的是 AudioRecord 内部的音频缓冲区的大小,该缓冲区的值不能低于一帧“音频帧”(Frame)的大小,而前一篇文章介绍过,一帧音频帧的大小计算如下:


int size = 采样率 x 位宽 x 采样时间 x 通道数


采样时间一般取 2.5ms~120ms 之间,由厂商或者具体的应用决定,我们其实可以推断,每一帧的采样时间取得越短,产生的延时就应该会越小,当然,碎片化的数据也就会越多。


在Android开发中,AudioRecord 类提供了一个帮助你确定这个 bufferSizeInBytes 的函数,原型如下:


int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);


不同的厂商的底层实现是不一样的,但无外乎就是根据上面的计算公式得到一帧的大小,音频缓冲区的大小则必须是一帧大小的2~N倍,有兴趣的朋友可以继续深入源码探究探究。


实际开发中,强烈建议由该函数计算出需要传入的 bufferSizeInBytes,而不是自己手动计算。


3. 音频的采集线程


当创建好了 AudioRecord 对象之后,就可以开始进行音频数据的采集了,通过下面两个函数控制采集的开始/停止:


AudioRecord.startRecording();

AudioRecord.stop();


一旦开始采集,必须通过线程循环尽快取走音频,否则系统会出现 overrun,调用的读取数据的接口是:


AudioRecord.read(byte[] audioData, int offsetInBytes, int sizeInBytes);


4. 示例代码


我将 AudioRecord 类的接口简单封装了一下,提供了一个 AudioCapturer 类,可以到我的Github下载:https://github.com/Jhuster/Android/blob/master/Audio/AudioCapturer.java


这里也贴出来一份:


/**COPYRIGHTNOTICE*Copyright(C)2016,Jhuster<lujun.hust@gmail.com>*https://github.com/Jhuster/Android**@licenseundertheApacheLicense,Version2.0**@fileAudioCapturer.java**@version1.0*@authorJhuster*@date2016/03/10*/importandroid.media.AudioFormat;importandroid.media.AudioRecord;importandroid.media.MediaRecorder;importandroid.util.Log;publicclassAudioCapturer{privatestaticfinalStringTAG="AudioCapturer";privatestaticfinalintDEFAULT_SOURCE=MediaRecorder.AudioSource.MIC;privatestaticfinalintDEFAULT_SAMPLE_RATE=44100;privatestaticfinalintDEFAULT_CHANNEL_CONFIG=AudioFormat.CHANNEL_IN_MONO;privatestaticfinalintDEFAULT_AUDIO_FORMAT=AudioFormat.ENCODING_PCM_16BIT;privateAudioRecordmAudioRecord;privateintmMinBufferSize=0;privateThreadmCaptureThread;privatebooleanmIsCaptureStarted=false;privatevolatilebooleanmIsLoopExit=false;privateOnAudioFrameCapturedListenermAudioFrameCapturedListener;publicinterfaceOnAudioFrameCapturedListener{publicvoidonAudioFrameCaptured(byte[]audioData);}publicbooleanisCaptureStarted(){returnmIsCaptureStarted;}publicvoidsetOnAudioFrameCapturedListener(OnAudioFrameCapturedListenerlistener){mAudioFrameCapturedListener=listener;}publicbooleanstartCapture(){returnstartCapture(DEFAULT_SOURCE,DEFAULT_SAMPLE_RATE,DEFAULT_CHANNEL_CONFIG,DEFAULT_AUDIO_FORMAT);}publicbooleanstartCapture(intaudioSource,intsampleRateInHz,intchannelConfig,intaudioFormat){if(mIsCaptureStarted){Log.e(TAG,"Capturealreadystarted!");returnfalse;}mMinBufferSize=AudioRecord.getMinBufferSize(sampleRateInHz,channelConfig,audioFormat);if(mMinBufferSize==AudioRecord.ERROR_BAD_VALUE){Log.e(TAG,"Invalidparameter!");returnfalse;}Log.d(TAG,"getMinBufferSize="+mMinBufferSize+"bytes!");mAudioRecord=newAudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,mMinBufferSize);if(mAudioRecord.getState()==AudioRecord.STATE_UNINITIALIZED){Log.e(TAG,"AudioRecordinitializefail!");returnfalse;}mAudioRecord.startRecording();mIsLoopExit=false;mCaptureThread=newThread(newAudioCaptureRunnable());mCaptureThread.start();mIsCaptureStarted=true;Log.d(TAG,"Startaudiocapturesuccess!");returntrue;}publicvoidstopCapture(){if(!mIsCaptureStarted){return;}mIsLoopExit=false;try{mCaptureThread.interrupt();mCaptureThread.join(1000);}catch(InterruptedExceptione){e.printStackTrace();}if(mAudioRecord.getRecordingState()==AudioRecord.RECORDSTATE_RECORDING){mAudioRecord.stop();}mAudioRecord.release();mIsCaptureStarted=false;mAudioFrameCapturedListener=null;Log.d(TAG,"Stopaudiocapturesuccess!");}privateclassAudioCaptureRunnableimplementsRunnable{@Overridepublicvoidrun(){while(!mIsLoopExit){byte[]buffer=newbyte[mMinBufferSize];intret=mAudioRecord.read(buffer,0,mMinBufferSize);if(ret==AudioRecord.ERROR_INVALID_OPERATION){Log.e(TAG,"ErrorERROR_INVALID_OPERATION");}elseif(ret==AudioRecord.ERROR_BAD_VALUE){Log.e(TAG,"ErrorERROR_BAD_VALUE");}elseif(ret==AudioRecord.ERROR_INVALID_OPERATION){Log.e(TAG,"ErrorERROR_INVALID_OPERATION");}else{if(mAudioFrameCapturedListener!=null){mAudioFrameCapturedListener.onAudioFrameCaptured(buffer);}Log.d(TAG,"OK,Captured"+ret+"bytes!");}}}}}


使用前要注意,添加如下权限:


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


5. 小结


音频开发的知识点其实挺多的,一篇文章也无法详细地展开叙述,因此,不够全面和详尽的地方,请大家搜索专业的资料进行深入了解。文章中有不清楚的地方欢迎留言或者来信 lujun.hust@gmail.com 交流,或者关注我的新浪微博 @卢_俊 或者 微信公众号 @Jhuster 获取最新的文章和资讯。

Android音频开发(2):如何采集一帧音频_第2张图片

更多相关文章

  1. Android之怎么使用SQLite数据库(增、删、改、查、分页等)以及Lis
  2. Android--创建和使用数据库详细指南
  3. Android adapter 数据适配器
  4. 初学Android,数据存储之使用SQLite数据库(四十六)
  5. Android 采用HttpClient提交数据到服务器
  6. Android 数据存储五种方式使用与总结

随机推荐

  1. Android(安卓)开机启动过程
  2. Android计时器和倒计时
  3. Android(安卓)使用BottomNavigationView
  4. Avoiding Memory Leaks
  5. android 自定义时间控件
  6. 【北亚数据恢复】重装系统后磁盘分区丢失
  7. 漫画人物如何上色?赛璐璐上色步骤
  8. 使用 Docker 一键启动环境安装 ModStart
  9. Linux-Centos7学习笔记
  10. android 自定义checkbox (转)