一、实现说明
OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了。在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以动态创建一个二维数组,里面有2个buffer,然后每次录音取出一个,录制好后再写入文件就可以了,2个buffer依次来存储PCM数据,这样就可以连续录制流式音频数据了,二维数组里面自己维护了一个索引,来标识当前处于哪个buffer录制状态,暴露给外部的只是调用方法而已,细节对外也是隐藏的。
二、编码实现
1、编写缓存buffer队列:RecordBuffer.h、RecordBuffer.cpp
1#ifndef OPENSLRECORD_RECORDBUFFER_H 2#define OPENSLRECORD_RECORDBUFFER_H 3 4class RecordBuffer { 5 6public: 7 short **buffer; 8 int index = -1; 9public: 10 RecordBuffer(int buffersize); 11 ~RecordBuffer(); 12 /** 13 * 得到一个新的录制buffer 14 * @return 15 */ 16 short* getRecordBuffer(); 17 /** 18 * 得到当前录制buffer 19 * @return 20 */ 21 short* getNowBuffer(); 22}; 23 24#endif //OPENSLRECORD_RECORDBUFFER_H 25 26#include "RecordBuffer.h" 27 28RecordBuffer::RecordBuffer(int buffersize) { 29 buffer = new short *[2]; 30 for(int i = 0; i < 2; i++) 31 { 32 buffer[i] = new short[buffersize]; 33 } 34} 35 36RecordBuffer::~RecordBuffer() { 37} 38 39short *RecordBuffer::getRecordBuffer() { 40 index++; 41 if(index > 1) 42 { 43 index = 0; 44 } 45 return buffer[index]; 46} 47 48short *RecordBuffer::getNowBuffer() { 49 return buffer[index]; 50}
这个队列其实就是PCM存储的buffer,getRecordBuffer()为即将要录入PCM数据的buffer,getNowBuffer()是当前录制好的PCM数据的buffer,可以写入文件,即我们得到的PCM数据。
2、使用OpenSL ES录制PCM数据
过程分为:创建引擎->初始化IO设备(自动检测麦克风等音频输入设备)->设置缓存队列->设置录制PCM数据规格->设置录音器接口->设置队列接口并设置录音状态为录制->开始录音。
1const char *path = env->GetStringUTFChars(path_, 0); 2 /** 3 * PCM文件 4 */ 5 pcmFile = fopen(path, "w"); 6 /** 7 * PCMbuffer队列 8 */ 9 recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2); 10 SLresult result; 11 /** 12 * 创建引擎对象 13 */ 14 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); 15 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); 16 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); 17 18 /** 19 * 设置IO设备(麦克风) 20 */ 21 SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, 22 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; 23 SLDataSource audioSrc = {&loc_dev, NULL}; 24 /** 25 * 设置buffer队列 26 */ 27 SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; 28 /** 29 * 设置录制规格:PCM、2声道、44100HZ、16bit 30 */ 31 SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, 32 SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, 33 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; 34 SLDataSink audioSnk = {&loc_bq, &format_pcm}; 35 36 const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; 37 const SLboolean req[1] = {SL_BOOLEAN_TRUE}; 38 39 /** 40 * 创建录制器 41 */ 42 result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, 43 &audioSnk, 1, id, req); 44 if (SL_RESULT_SUCCESS != result) { 45 return; 46 } 47 result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); 48 if (SL_RESULT_SUCCESS != result) { 49 return; 50 } 51 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); 52 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, 53 &recorderBufferQueue); 54 finished = false; 55 result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), 56 recorderSize); 57 result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); 58 LOGD("开始录音"); 59 /** 60 * 开始录音 61 */ 62 (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); 63 env->ReleaseStringUTFChars(path_, path);
录音回调如下:
1void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) 2{ 3 // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill 4 // but instead, this is a one-time buffer so we stop recording 5 LOGD("record size is %d", recorderSize); 6 7 fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile); 8 9 if(finished) 10 { 11 (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); 12 fclose(pcmFile); 13 LOGD("停止录音"); 14 } else{ 15 (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), 16 recorderSize); 17 } 18}
这样就完成了OPenSL ES的PCM音频数据录制,我们这里拿到了录制的PCM数据可以用mediacodec或ffmpeg来编码成aac格式的音频,也可以直接用推流到服务器来实现音频直播。
完整代码如下:
1#include <jni.h> 2#include <string> 3#include "AndroidLog.h" 4#include "RecordBuffer.h" 5#include "unistd.h" 6 7extern "C" 8{ 9#include <SLES/OpenSLES.h> 10#include <SLES/OpenSLES_Android.h> 11} 12 13//引擎接口 14static SLObjectItf engineObject = NULL; 15//引擎对象 16static SLEngineItf engineEngine; 17 18//录音器接口 19static SLObjectItf recorderObject = NULL; 20//录音器对象 21static SLRecordItf recorderRecord; 22//缓冲队列 23static SLAndroidSimpleBufferQueueItf recorderBufferQueue; 24 25//录制大小设为4096 26#define RECORDER_FRAMES (2048) 27static unsigned recorderSize = RECORDER_FRAMES * 2; 28 29//PCM文件 30FILE *pcmFile; 31//录音buffer 32RecordBuffer *recordBuffer; 33 34bool finished = false; 35 36void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) 37{ 38 // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill 39 // but instead, this is a one-time buffer so we stop recording 40 LOGD("record size is %d", recorderSize); 41 42 fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile); 43 44 if(finished) 45 { 46 (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); 47 fclose(pcmFile); 48 LOGD("停止录音"); 49 } else{ 50 (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), 51 recorderSize); 52 } 53} 54 55extern "C" 56JNIEXPORT void JNICALL 57Java_com_renhui_openslrecord_MainActivity_rdSound(JNIEnv *env, jobject instance, jstring path_) { 58 const char *path = env->GetStringUTFChars(path_, 0); 59 /** 60 * PCM文件 61 */ 62 pcmFile = fopen(path, "w"); 63 /** 64 * PCMbuffer队列 65 */ 66 recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2); 67 SLresult result; 68 /** 69 * 创建引擎对象 70 */ 71 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); 72 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); 73 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); 74 75 /** 76 * 设置IO设备(麦克风) 77 */ 78 SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, 79 SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; 80 SLDataSource audioSrc = {&loc_dev, NULL}; 81 /** 82 * 设置buffer队列 83 */ 84 SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; 85 /** 86 * 设置录制规格:PCM、2声道、44100HZ、16bit 87 */ 88 SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, 89 SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, 90 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; 91 SLDataSink audioSnk = {&loc_bq, &format_pcm}; 92 93 const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; 94 const SLboolean req[1] = {SL_BOOLEAN_TRUE}; 95 96 /** 97 * 创建录制器 98 */ 99 result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, 100 &audioSnk, 1, id, req); 101 if (SL_RESULT_SUCCESS != result) { 102 return; 103 } 104 result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); 105 if (SL_RESULT_SUCCESS != result) { 106 return; 107 } 108 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); 109 result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, 110 &recorderBufferQueue); 111 finished = false; 112 result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), 113 recorderSize); 114 result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); 115 LOGD("开始录音"); 116 /** 117 * 开始录音 118 */ 119 (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); 120 env->ReleaseStringUTFChars(path_, path); 121}extern "C" 122JNIEXPORT void JNICALL 123Java_com_renhui_openslrecord_MainActivity_rdStop(JNIEnv *env, jobject instance) { 124 125 // TODO 126 if(recorderRecord != NULL) 127 { 128 finished = true; 129 } 130}
三、验证录制成果
有两种方法:
1. 使用Android OpenSL ES 开发:使用 OpenSL 播放 PCM 数据的demo进行播放。
2. 使用 ffplay 命令播放,命令为:ffplay -f s16le -ar 44100 -ac 2 temp.pcm (命令由来:在录制代码里的参数为录制规格:PCM、2声道、44100HZ、16bit)