解决A/libc Fatal signal 11 (SIGSEGV)错误,这可能是目前最鲁棒的Android声音录制和播放封装库了
原文出处:http://blog.piasy.com/2016/02/24/Robust-Android-Audio-encapsulation/
安卓开发过程中一旦开始和硬件打交道,以及涉及到一定的native代码之后,各种闪退就开始浮出水面了,声音录制和播放当然不例外,其中最摸不着头脑的就是A/libc: Fatal signal 11 (SIGSEGV) at了。本文总结了YOLO安卓客户端大半年来的安卓音频实践,整理出一套系统API的封装,命名为RxAndroidAudio。
概览
安卓平台和声音录制与播放相关的主要是4个类:MediaRecorder,MediaPlayer,AudioRecord和AudioTrack。
MediaRecorder可以录制视频和音频到文件,MediaPlayer可以播放视频和音频文件,AudioRecord可以提供接口读取音频流数据(byte数组或者short数组),AudioTrack提供接口用于播放音频流数据。
小结一下,其中MediaRecorder和AudioRecord用于声音录制,MediaPlayer和AudioTrack用于声音播放。AudioRecord和AudioTrack用于操作音频流数据,操作对象是byte数组(或者short数组),而MediaRecorder和MediaPlayer提供了经过更高层抽象和封装接口,直接对文件进行操作,而且他俩功能更丰富,同时支持音频和视频。
本文会涉及到部分关于声音录制和播放更底层的实现,详细的分析并不是本文的内容范围了,此外本文主要目标并不是声音录制和播放的使用教程,本文主要关注的是上述4个类的使用过程中需要注意的地方。
基于文件的操作
-
mRecorder.prepare()调用需要捕获IOException和RuntimeException,注意需要捕获RuntimeException而不是IllegalStateException,尽管Java doc中只声明了会抛出IllegalStateException,但是查看jni层代码可以看到,prepare的调用也是可能会触发RuntimeException的;
-
mRecorder.start(),mRecorder.stop(),mRecorder.reset()调用也需要捕获RuntimeException,理由同上;
-
无论是prepare还是start,抛出异常之后都需要reset和release;
-
需要保证不会对jni层进行多线程的调用,以免出现下面这样的“静默闪退”(参考资料),RxAndroidAudio通过单例和synchronized方法来保证这一点:
~~~ A/libc: Fatal signal 11 (SIGSEGV) at 0x00000010 (code=1), thread 9302 (RxComputationTh) ~~~
-
当用户录完声音,需要停止录音,调用stop的时候,需要sleep一段时间,以免最后几百毫秒录不上,这有可能是安卓系统音频编码器的bug,参考资料;
-
当prepare返回后,有些低端的设备需要再延迟一段时间开始说话,以免开头几百毫秒录不上,可以在prepare返回后延迟几百毫秒(例如300ms)再显示初始化完毕的UI,原因还需要继续寻找;
-
和MediaRecorder一样,prepare,start,stop,reset,release函数的调用都需要捕获异常;
-
和MediaRecorder一样,需要保证不会对jni层进行多线程的调用;
-
MediaPlayer提供了两种音频文件播放方式:通过文件绝对路径指定播放文件,或者使用资源文件id指定;绝对路径的方式需要调用mPlayer.prepare(),而资源文件id方式不需要;
-
在MediaPlayer.OnCompletionListener的onCompletion回调中,需要延迟一定时间再释放MediaPlayer,否则可能导致下次紧接着的播放无法成功(静默失败,不会抛出异常),原因还需要继续寻找;
基于数据流的操作
-
和MediaRecorder一样,startRecording需要捕获异常;
-
和MediaRecorder一样,需要保证不会对jni层进行多线程的调用;
-
mAudioRecord.read的返回值需要进行错误检查;
-
mAudioRecord.read传入的参数类型需要进行区分,ENCODING_PCM_16BIT格式的录音需要传入short数组,ENCODING_PCM_8BIT格式的录音需要传入byte数组,尽管在jni层的实现都是一样的,但是[Java doc](http://developer.android.com/reference/android/media/AudioRecord.html#read(byte[], int, int))明确说明了这一注意事项,理应遵循;
-
RxAndroidAudio使用ExecutorService来执行异步任务(从AudioRecord中循环读取数据);
-
和MediaRecorder一样,write需要捕获异常;
-
和MediaRecorder一样,需要保证不会对jni层进行多线程的调用;
Bonus Part
Reactive!
RxAndroidAudio之所以叫Rx,就是因为它尽可能的提供了Reactive的API。
RxAudioPlayer播放声音文件:
public Single<Boolean> play(@NonNull final File audioFile);public Single<Boolean> play(final Context context, @RawRes final int audioRes);
当然RxAudioPlayer也提供了传统的API:
public boolean playNonRxy(@NonNull final File audioFile,
final MediaPlayer.OnCompletionListener onCompletionListener,
final MediaPlayer.OnErrorListener onErrorListener);
public boolean playNonRxy(final Context context, @RawRes final int audioRes,
final MediaPlayer.OnCompletionListener onCompletionListener,
final MediaPlayer.OnErrorListener onErrorListener);
RxAmplitude获取当前说话音量等级:
public static Observable<Integer> from(@NonNull AudioRecorder audioRecorder);
当然RxAmplitude使用的是AudioRecorder的传统API:
public synchronized int getMaxAmplitude();
使用
详情请见Github主页以及demo app工程。demo apk下载地址。