博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据
阅读量:5975 次
发布时间:2019-06-19

本文共 8649 字,大约阅读时间需要 28 分钟。

一、实现说明

OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了。在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以动态创建一个二维数组,里面有2个buffer,然后每次录音取出一个,录制好后再写入文件就可以了,2个buffer依次来存储PCM数据,这样就可以连续录制流式音频数据了,二维数组里面自己维护了一个索引,来标识当前处于哪个buffer录制状态,暴露给外部的只是调用方法而已,细节对外也是隐藏的。

二、编码实现

1、编写缓存buffer队列:RecordBuffer.h、RecordBuffer.cpp

#ifndef OPENSLRECORD_RECORDBUFFER_H#define OPENSLRECORD_RECORDBUFFER_H class RecordBuffer { public:    short **buffer;    int index = -1;public:    RecordBuffer(int buffersize);    ~RecordBuffer();    /**     * 得到一个新的录制buffer     * @return     */    short* getRecordBuffer();    /**     * 得到当前录制buffer     * @return     */    short* getNowBuffer();}; #endif //OPENSLRECORD_RECORDBUFFER_H
#include "RecordBuffer.h" RecordBuffer::RecordBuffer(int buffersize) {    buffer = new short *[2];    for(int i = 0; i < 2; i++)    {        buffer[i] = new short[buffersize];    }} RecordBuffer::~RecordBuffer() {} short *RecordBuffer::getRecordBuffer() {    index++;    if(index > 1)    {        index = 0;    }    return buffer[index];} short *RecordBuffer::getNowBuffer() {    return buffer[index];}

这个队列其实就是PCM存储的buffer,getRecordBuffer()为即将要录入PCM数据的buffer,getNowBuffer()是当前录制好的PCM数据的buffer,可以写入文件,即我们得到的PCM数据。

2、使用OpenSL ES录制PCM数据

过程分为:创建引擎->初始化IO设备(自动检测麦克风等音频输入设备)->设置缓存队列->设置录制PCM数据规格->设置录音器接口->设置队列接口并设置录音状态为录制->开始录音。

const char *path = env->GetStringUTFChars(path_, 0);    /**     * PCM文件     */    pcmFile = fopen(path, "w");    /**     * PCMbuffer队列     */    recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2);    SLresult result;    /**     * 创建引擎对象     */    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);     /**     * 设置IO设备(麦克风)     */    SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,                                      SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};    SLDataSource audioSrc = {&loc_dev, NULL};    /**     * 设置buffer队列     */    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};    /**     * 设置录制规格:PCM、2声道、44100HZ、16bit     */    SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,                                   SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,                                   SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};    SLDataSink audioSnk = {&loc_bq, &format_pcm};     const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};    const SLboolean req[1] = {SL_BOOLEAN_TRUE};     /**     * 创建录制器     */    result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc,                                                  &audioSnk, 1, id, req);    if (SL_RESULT_SUCCESS != result) {        return;    }    result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);    if (SL_RESULT_SUCCESS != result) {        return;    }    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);    result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,                                             &recorderBufferQueue);    finished = false;    result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(),                                             recorderSize);    result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL);    LOGD("开始录音");    /**     * 开始录音     */    (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);    env->ReleaseStringUTFChars(path_, path);

录音回调如下:

void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){    // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill    // but instead, this is a one-time buffer so we stop recording    LOGD("record size is %d", recorderSize);     fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile);     if(finished)    {        (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);        fclose(pcmFile);        LOGD("停止录音");    } else{        (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(),                                        recorderSize);    }}

这样就完成了OPenSL ES的PCM音频数据录制,我们这里拿到了录制的PCM数据可以用mediacodec或ffmpeg来编码成aac格式的音频,也可以直接用推流到服务器来实现音频直播。

完整代码如下:

#include 
#include
#include "AndroidLog.h"#include "RecordBuffer.h"#include "unistd.h" extern "C"{#include
#include
} //引擎接口static SLObjectItf engineObject = NULL;//引擎对象static SLEngineItf engineEngine; //录音器接口static SLObjectItf recorderObject = NULL;//录音器对象static SLRecordItf recorderRecord;//缓冲队列static SLAndroidSimpleBufferQueueItf recorderBufferQueue; //录制大小设为4096#define RECORDER_FRAMES (2048)static unsigned recorderSize = RECORDER_FRAMES * 2; //PCM文件FILE *pcmFile;//录音bufferRecordBuffer *recordBuffer; bool finished = false; void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context){ // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill // but instead, this is a one-time buffer so we stop recording LOGD("record size is %d", recorderSize); fwrite(recordBuffer->getNowBuffer(), 1, recorderSize, pcmFile); if(finished) { (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); fclose(pcmFile); LOGD("停止录音"); } else{ (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); }} extern "C"JNIEXPORT void JNICALLJava_com_renhui_openslrecord_MainActivity_rdSound(JNIEnv *env, jobject instance, jstring path_) { const char *path = env->GetStringUTFChars(path_, 0); /** * PCM文件 */ pcmFile = fopen(path, "w"); /** * PCMbuffer队列 */ recordBuffer = new RecordBuffer(RECORDER_FRAMES * 2); SLresult result; /** * 创建引擎对象 */ result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); /** * 设置IO设备(麦克风) */ SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; SLDataSource audioSrc = {&loc_dev, NULL}; /** * 设置buffer队列 */ SLDataLocator_AndroidSimpleBufferQueue loc_bq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; /** * 设置录制规格:PCM、2声道、44100HZ、16bit */ SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN}; SLDataSink audioSnk = {&loc_bq, &format_pcm}; const SLInterfaceID id[1] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE}; const SLboolean req[1] = {SL_BOOLEAN_TRUE}; /** * 创建录制器 */ result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, id, req); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); if (SL_RESULT_SUCCESS != result) { return; } result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue); finished = false; result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(), recorderSize); result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, NULL); LOGD("开始录音"); /** * 开始录音 */ (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); env->ReleaseStringUTFChars(path_, path);}extern "C"JNIEXPORT void JNICALLJava_com_renhui_openslrecord_MainActivity_rdStop(JNIEnv *env, jobject instance) { // TODO if(recorderRecord != NULL) { finished = true; }}

三、验证录制成果

有两种方法:

1. 使用的demo进行播放。

2. 使用 ffplay 命令播放,命令为:ffplay -f s16le -ar 44100 -ac 2 temp.pcm (命令由来:在录制代码里的参数为录制规格:PCM、2声道、44100HZ、16bit)

四、参考源码

 

转载地址:http://lqbox.baihongyu.com/

你可能感兴趣的文章
SurfControl人工智能新突破 领跑反垃圾邮件
查看>>
一个动态ACL的案例
查看>>
linux基础中的基础
查看>>
jquery 表单验证
查看>>
openstack 之 windows server 2008镜像制作
查看>>
VI快捷键攻略
查看>>
Win server 2012 R2 文件服务器--(三)配额限制
查看>>
卓越质量管理成就创新高地 中关村软件园再出发
查看>>
linux rsync 远程同步
查看>>
httpd的manual列目录漏洞
查看>>
myeclipse2014破解过程
查看>>
漫谈几种反编译对抗技术
查看>>
VS 编译错误
查看>>
Timer 和 TimerTask 例子
查看>>
Spring BOOT 集成 RabbitMq 实战操作(一)
查看>>
安装python3.5注意事项及相关命令
查看>>
进程通信之无名信号量
查看>>
并发串行调用接口
查看>>
C# 视频监控系列 序 [完]
查看>>
Mongodb3.0.5副本集搭建及spring和java连接副本集配置
查看>>