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 ...
随机推荐
- selenium数据驱动模式实现163邮箱的登录及添加联系人自动化操作
项目结构如下: 要求python3.0 selenium3.0 下面是代码: appModubles:addContactPersonActtion.py和LoginAction.py addCont ...
- 防XSS攻击解决方法
1.web.xml文件中新增filter配置 <!-- URL请求参数字符过滤或合法性校验 --> <filter> <filter-name>XssFilter& ...
- python3 字典(dictionary)(二)
内置函数: len(dict):计算字典元素个数,即键的总数 str(dict):输出字典,以可打印的字符串表示. type(variable):返回输入的变量类型,如果变量是字典就返回字典类型. ...
- jmeter获取请求信息和响应信息
String tmp = prev.getUrlAsString(); String tmp = prev.getRequestHeaders(); String tmp = prev.getResp ...
- SpringBoot的@Enable*注解的使用介绍
@EnableAsync或@EnableConfigurationProperties背后的运行原理,是使用了@Import注解. @Import({User.class,Role.class,MyC ...
- html中div使用CSS实现水平/垂直居中的多种方式
CSS中的居中,在工作中,会经常遇到.它可以分为水平居中和垂直居中,以下是几种实现居中的方式. git 查看源码 配合在线预览,效果更佳 以下例子中,涉及到的CSS属性值. .parent-frame ...
- Java 后台POST模拟文件上传
概述 废话不多说,直接撸代码 代码 1.引入Maven包 <dependency> <groupId>org.apache.httpcomponents</groupId ...
- ubuntu配置小飞机
现在有两种方式在ubuntu配置本地shadowsocks(前提已经在服务器上搭建好了ss) windows,ios和安卓配置都是gui,没什么好说的.然后前期工作什么买vps啊,比较无脑,不想记录这 ...
- Linux中伪分布的搭建
一伪分布模式 特点:在单机上,模拟一个分布式的环境,具备Hadoop的所有功能 HDFS:NameNode + DataNode + S ...
- Archiver 3 for Mac(解压缩工具) ,想压缩解压慢一点就这么难!
Archiver 3 for Mac是一款分割合并解压缩工具,简单实用且功能齐全,你只需简单的拖放文件就可以进行压缩,还可以设定解压密码,从而保护自己的隐私.如果文件很大你还可以切割文件.Archiv ...