Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据
一、实现说明
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 = -;
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 *[];
for(int i = ; i < ; i++)
{
buffer[i] = new short[buffersize];
}
}
RecordBuffer::~RecordBuffer() {
}
short *RecordBuffer::getRecordBuffer() {
index++;
if(index > )
{
index = ;
}
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_, );
/**
* PCM文件
*/
pcmFile = fopen(path, "w");
/**
* PCMbuffer队列
*/
recordBuffer = new RecordBuffer(RECORDER_FRAMES * );
SLresult result;
/**
* 创建引擎对象
*/
result = slCreateEngine(&engineObject, , NULL, , 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, };
/**
* 设置录制规格:PCM、2声道、44100HZ、16bit
*/
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, , 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[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req[] = {SL_BOOLEAN_TRUE}; /**
* 创建录制器
*/
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc,
&audioSnk, , 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(), , 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 <jni.h>
#include <string>
#include "AndroidLog.h"
#include "RecordBuffer.h"
#include "unistd.h" extern "C"
{
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
} //引擎接口
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 * ; //PCM文件
FILE *pcmFile;
//录音buffer
RecordBuffer *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(), , recorderSize, pcmFile); if(finished)
{
(*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
fclose(pcmFile);
LOGD("停止录音");
} else{
(*recorderBufferQueue)->Enqueue(recorderBufferQueue, recordBuffer->getRecordBuffer(),
recorderSize);
}
} extern "C"
JNIEXPORT void JNICALL
Java_com_renhui_openslrecord_MainActivity_rdSound(JNIEnv *env, jobject instance, jstring path_) {
const char *path = env->GetStringUTFChars(path_, );
/**
* PCM文件
*/
pcmFile = fopen(path, "w");
/**
* PCMbuffer队列
*/
recordBuffer = new RecordBuffer(RECORDER_FRAMES * );
SLresult result;
/**
* 创建引擎对象
*/
result = slCreateEngine(&engineObject, , NULL, , 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, };
/**
* 设置录制规格:PCM、2声道、44100HZ、16bit
*/
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, , 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[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};
const SLboolean req[] = {SL_BOOLEAN_TRUE}; /**
* 创建录制器
*/
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc,
&audioSnk, , 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 JNICALL
Java_com_renhui_openslrecord_MainActivity_rdStop(JNIEnv *env, jobject instance) { // TODO
if(recorderRecord != NULL)
{
finished = true;
}
}
三、验证录制成果
有两种方法:
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
Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据的更多相关文章
- Android OpenSL ES 开发:OpenSL ES利用SoundTouch实现PCM音频的变速和变调
缘由 OpenSL ES 学习到现在已经知道 OpenSL ES 不仅能播放和录制PCM音频数据,还能改变声音大小.设置左声道或右声道播放.还能变速播放,可谓是播放音频的王者.但是变速有一点不好的就是 ...
- Android OpenSL ES 开发:Android OpenSL 介绍和开发流程说明
一.Android OpenSL ES 介绍 OpenSL ES (Open Sound Library for Embedded Systems)是无授权费.跨平台.针对嵌入式系统精心优化的硬件音频 ...
- Android OpenGL ES 开发教程 从入门到精通
感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...
- 使用AudioTrack播放PCM音频数据(android)
众所周知,Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的.MediaPl ...
- JavaCV FFmpeg采集麦克风PCM音频数据
前阵子用一个JavaCV的FFmpeg库实现了YUV视频数据地采集,同样的采集PCM音频数据也可以采用JavaCV的FFmpeg库. 传送门:JavaCV FFmpeg采集摄像头YUV数据 首先引入 ...
- Android OpenSL ES 开发:使用 OpenSL 播放 PCM 数据
OpenSL ES 是基于NDK也就是c语言的底层开发音频的公开API,通过使用它能够做到标准化, 高性能,低响应时间的音频功能实现方法. 这次是使用OpenSL ES来做一个音乐播放器,它能够播放m ...
- Android OpenGL ES 开发(三): OpenGL ES 定义形状
在上篇文章,我们能够配置好基本的Android OpenGL 使用的环境.但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的.所以能够在Ope ...
- Android OpenGL ES 开发(二): OpenGL ES 环境搭建
零:环境搭建目的 为了在Android应用程序中使用OpenGL ES绘制图形,必须要为他们创建一个视图容器.其中最直接或者最常用的方式就是实现一个GLSurfaceView和一个GLSurfaceV ...
- [Android]使用Kotlin开发Android(二)
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/4829007.html [TOC] 使用Kotlin+OkHtt ...
随机推荐
- 一丶Http协议
一 HTTP概述 HTTP(hypertext transport protocol),即超文本传输协议.这个协议详细规定了浏览器和万维网服务器之间互相通信的规则. HTTP就是一个通信规则,通信规则 ...
- MyBatisPlus的通用查询,简直可以丢弃Dao和mapper.xml了
/** * * @author shenjing * @date 2018/6/20 */ @Service public class LocalDispatchServiceImpl extends ...
- [转]centos7 安装jdk11 并设置默认java版本
https://www.server-world.info/en/note?os=CentOS_7&p=jdk11&f=2 OpenJDK 11 : Install 2018/10/1 ...
- 【java】-- 多线程之间实现通讯
1.多线程之间如何实现通讯 1.1.什么是多线程之间通讯? 多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同. 画图演示 1.2.多线程之间通讯需求 需求:第一个线程写入(inpu ...
- react组件中刷新组件小技巧
在开发过程中,经常遇到组件数据无法更新,例如:当你用同一个表格展示不同数据的时候,当点击第5页后,再点击另外一份数据时发现还在第五页,并没有回到第一页. 怎么能让一个组件每次数据不一样时都重新加载呢, ...
- webpack 4+ vue-loader 配置 (完善中...)
webpack 4+ vue-loader 配置 写的demo,clone下来后,npm run dev即可,(此demo并未加入router) 可能会由于版本问题,配置会有些许改动,暂时都是可用的 ...
- jmeter数据库,charles抓包,Python循环语句
jmeter数据库,charles抓包,Python循环语句 一.Jemeter数据库 添加jar包数据库 jemeter=>浏览 添加JDBC Connection Configuration ...
- [zt+总结]wpf 应用权限问题
一.Inno Setup打包添加和去除管理员权限 转载:https://www.cnblogs.com/walker-lc/articles/3470679.html 添加管理员权限 1.在[Setu ...
- NetToPLCSIM 连接PLCSIM 和Kepware 联合仿真
之前只知道PLCSIM 单独仿真调试PLC的程序,后来接触KepwareOPC,想着如果能够仿真PLC和Kepware的通信,更加方便调试.于是在网上搜索了一下,发现NetToPLCSIM这个软件. ...
- Gradle 学习二
按照本指南,您将创建一个简单的Gradle项目,调用一些基本的Gradle命令,并了解Gradle如何管理项目 1.初始化项目创建项目目录 ❯ mkdir basic-demo ❯ cd basic- ...