Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据

一、实现说明

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)

四、参考源码

https://github.com/renhui/OpenSLRecord

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据 - HelloWorld