简介
NDK开发OpenSL ES跨平台高效音频解决方案.pngOpenSL ES全称为Open Sound Library for Embedded Systems,即嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API。它为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现方法,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。
OpenSL ES里面有对象和接口的概念:
对象:类似于C++中类用来提供一组资源及其状态的抽象,也就是我们可以根据特定类型type来获取一个音频录制的对象,但是对于这个对象我们并不能直接操作。
接口:接口是对象提供一组特定功能方法的抽象,也就是可以从对象中获取接口,然后通过接口来改变对象的状态以便使用对象的功能。
使用:
1.导入OpenSL ES库
因为opensl是内嵌在android系统里的,所以我们需要连接到我们工程使用
#找打Android lib库里面的libOpenSLES.so的库
find_library( OpenSLES-lib
OpenSLES )
#链接到你的native工程的库
target_link_libraries( your-native.so
${OpenSLES-lib}
)
然后引入头文件
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
2.创建OpenSL ES引擎并初始化
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf; //缓冲器队列接口
SLPlayItf audioPlayInterface; //播放接口
SLEngineOption options[] = {
{(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
};
slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口
//实例化对象
//象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
RealizeObject(engineObject);
关于slCreateEngine()这个全局方法:
SL_API SLresult SLAPIENTRY slCreateEngine(
SLObjectItf *pEngine, //对象地址,用于传出对象
SLuint32 numOptions, //配置参数数量
const SLEngineOption *pEngineOptions, //配置参数,为枚举数组
SLuint32 numInterfaces, //支持的接口数量
const SLInterfaceID *pInterfaceIds, //具体的要支持的接口,是枚举的数组
const SLboolean *pInterfaceRequired //具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
);
3.获取引擎接口
(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);
4.实例化混音器
//4.创建输出混音器
(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口
//实例化混音器
RealizeObject(outputMixObject);
5.创建音频播放对象
// Android针对数据源的简单缓冲区队列定位器
SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // 定位器类型
1 // 缓冲区数
};
// PCM数据源格式
SLDataFormat_PCM dataSourceFormat = {
SL_DATAFORMAT_PCM, // 格式类型
wav_get_channels(wav), // 通道数
wav_get_rate(wav) * 1000, // 毫赫兹/秒的样本数
wav_get_bits(wav), // 每个样本的位数
wav_get_bits(wav), // 容器大小
SL_SPEAKER_FRONT_CENTER, // 通道屏蔽
SL_BYTEORDER_LITTLEENDIAN // 字节顺序
};
// 数据源是含有PCM格式的简单缓冲区队列
SLDataSource dataSource = {
&dataSourceLocator, // 数据定位器
&dataSourceFormat // 数据格式
};
// 针对数据接收器的输出混合定位器
SLDataLocator_OutputMix dataSinkLocator = {
SL_DATALOCATOR_OUTPUTMIX, // 定位器类型
outputMixObject // 输出混合
};
// 数据定位器是一个输出混合
SLDataSink dataSink = {
&dataSinkLocator, // 定位器
0 // 格式
};
// 需要的接口
SLInterfaceID interfaceIds[] = {
SL_IID_BUFFERQUEUE
};
// 需要的接口,如果所需要的接口不要用,请求将失败
SLboolean requiredInterfaces[] = {
SL_BOOLEAN_TRUE // for SL_IID_BUFFERQUEUE
};
(*engineInterface)->CreateAudioPlayer(
engineInterface,
&audioPlayerObject,
&dataSource,
&dataSink,
ARRAY_LEN(interfaceIds),
interfaceIds,
requiredInterfaces);
//实例化音频播放器
RealizeObject(audioPlayerObject);
6.获得缓冲区队列接口Buffer Queue Interface
//通过缓冲区队列接口对缓冲区进行排序播放
(*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);
7.注册音频播放器回调函数
当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
//缓冲区的大小
bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
buffer = new unsigned char[bufferSize];
PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
(*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);
RegisterCallback函数的原型:
SLresult (*RegisterCallback) (
SLAndroidSimpleBufferQueueItf self,
slAndroidSimpleBufferQueueCallback callback,
void* pContext
);
第一个参数是SLAndroidSimpleBufferQueueItf对象,第二个参数是回调函数,第三个参数是一个void,第三个参数一般指向一个封装的类或者结构体,保存一些回调函数中可能用到的信息,回调函数的规范是必须有两个参数,且第一个参数SLAndroidSimpleBufferQueueItf类型,第二个参数是void,指向封装的结构体
PlayerContext:
struct PlayerContext{
WAV wav;
unsigned char *buffer;
size_t bufferSize;
PlayerContext(WAV wav,
unsigned char *buffer,
size_t bufferSize){
this->wav = wav;
this->buffer = buffer;
this->bufferSize = bufferSize;
}
};
PlayerCallBack:
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
PlayerContext* ctx = (PlayerContext*)context;
//读取数据
ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
if(0 < readSize){
(*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
}else{
//destroy context
CloseWaveFile(ctx->wav); //关闭文件
delete ctx->buffer; //释放缓存
}
}
8.获取Play Interface
通过对SetPlayState函数来启动播放音乐,一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
(*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
//设置播放状态
(*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);
9.开始
第一个缓冲区入队
PlayerCallBack(andioPlayerBufferQueueItf,ctx);
网友评论