一.认识OpenSL ES
OpenSL ES的全称是Open Sound Library For Embedded Systems,即应用于嵌入式系统的开源音频库。Android从2.3版本起就开始支持OpenSL ES标准了,并且通过NDK提供相应的API开发接口。OpenSL ES有以下特性:
- 提供c语言接口,兼容c++,需要在NDK下开发,可以更好地集成于native应用
- 运行于native层,需要自己管理资源的申请和释放,没有Dalvik虚拟机垃圾回收机制
- 支持pcm数据的采集和播放
- 支持播放的音频数据来源广泛,res、assets、sdcard、在线网络音频以及代码中定义的音频二进制数据
和Android提供的AudioRecord和AudioTrack相比,OpenSL ES提供了更高的性能,更快的速度。因为AudioRecord和AudioTrack都是Android提供的Java API,无论是采集还是播放音频,都需要将音频数据从java层拷贝到native层,或从native层拷贝到java层,这无疑是十分消耗资源的。如果希望减少拷贝,开发更加高效的Android音频应用,则建议使用Android NDK提供的OpenSL ES API接口,它支持在native层直接处理音频数据。
二.使用OpenSL ES播放pcm音频数据的步骤
开发步骤如下:
- 创建引擎对象和接口
- 创建混音器对象和接口
- 创建播放器对象和接口
- 创建缓冲队列接口并给缓冲队列注册回调函数
- 设置播放状态,手动调用回调函数
下面给出代码:
//opensles.cpp
#include #include #include extern "C"{ #include #include #include } #define TAG "jni" // 这个是自定义的LOG的标识 #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型 #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型 using namespace std; //engine interface static SLObjectItf engineObject= nullptr; static SLEngineItf engineEngine= nullptr; //output mix interfaces static SLObjectItf outputMixObject= nullptr; static SLEnvironmentalReverbItf outputMixEnvir服务器托管网onmentalReverb= nullptr; //player interface static SLObjectItf pcmPlayerObject= nullptr; static SLPlayItf pcmPlayerplay= nullptr; //buffer queue static SLAndroidSimpleBufferQueueItf pcmBufferQueue= nullptr; //pcm file FILE *pcmFile= nullptr; void *buffer= nullptr; uint8_t *out_buffer= nullptr; static const SLEnvironmentalReverbSettings reverbSettings=SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR; //播放回调 void playerCallback(SLAndroidSimpleBufferQueueItf bufferQueueItf,void *context){ if(bufferQueueItf!=pcmBufferQueue){ LOGI("SLAndroidSimpleBufferQueueItf is not equal"); return; } while(!feof(pcmFile)){ size_t size=fread(out_buffer,44100*2*4,1,pcmFile); if(out_buffer== nullptr||size==0){ LOGI("read end %ld",size); }else{ LOGI("reading %ld",size); } buffer=out_buffer; break; } if(buffer){ LOGI("buffer is not null"); SLresult result=(*pcmBufferQueue)->Enqueue(pcmBufferQueue,buffer,44100*2*4); if(result!=SL_RESULT_SUCCESS){ LOGI("pcmBufferQueue error %ld",result); 服务器托管网 } } } jint playPcmBySL(JNIEnv *env,jobject thiz,jstring pcm_path){ const char *pcmPath=env->GetStringUTFChars(pcm_path, nullptr); pcmFile=fopen(pcmPath,"r"); env->ReleaseStringUTFChars(pcm_path,pcmPath); if(pcmFile== nullptr){ LOGI("open pcmFile error"); return -1; } out_buffer=(uint8_t *)malloc(44100*2*4); //创建引擎对象 SLresult result=slCreateEngine(&engineObject,0,nullptr,0,nullptr,nullptr); if(result!=SL_RESULT_SUCCESS){ LOGI("slCreateEngine failed %ld",result); return -1; } //实例化引擎 result=(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE); if(result!=SL_RESULT_SUCCESS){ LOGI("engine realize failed %ld",result); return -1; } //获取引擎接口SLEngineItf result=(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngine); if(result!=SL_RESULT_SUCCESS){ LOGI("GetInterface SLEngineItf failed %ld",result); return -1; } //创建输出混音器 const SLInterfaceID ids[1]={SL_IID_ENVIRONMENTALREVERB}; const SLboolean req[1]={SL_BOOLEAN_FALSE}; result=(*engineEngine)->CreateOutputMix(engineEngine,&outputMixObject,1,ids,req); if(result!=SL_RESULT_SUCCESS){ LOGI("CreateOutputMix failed %ld",result); return -1; } //实例化混音器 result=(*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE); if(result!=SL_RESULT_SUCCESS){ LOGI("Realize outputMixObject failed %ld",result); return -1; } //获取混音器接口SLEnvironmentalReverbItf result=(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb); if(result!=SL_RESULT_SUCCESS){ LOGI("GetInterface SLEnvironmentalReverbItf failed %ld",result); return -1; } //给混音器设置环境混响属性 (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb,&reverbSettings); //设置输入 SLDataSource SLDataLocator_AndroidSimpleBufferQueue loc_bufq={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2}; SLDataFormat_PCM formatPcm={ SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_32, SL_PCMSAMPLEFORMAT_FIXED_32, SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN }; SLDataSource slDataSource={&loc_bufq,&formatPcm}; //设置输出SLDataSink SLDataLocator_OutputMix loc_outmix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject}; SLDataSink audioSnk={&loc_outmix, nullptr}; //创建音频播放器对象 const SLInterfaceID ids2[1] = {SL_IID_BUFFERQUEUE}; const SLboolean req2[1] = {SL_BOOLEAN_TRUE}; result=(*engineEngine)->CreateAudioPlayer(engineEngine,&pcmPlayerObject,&slDataSource,&audioSnk,1,ids2,req2); if(result!=SL_RESULT_SUCCESS){ LOGI("CreateAudioPlayer failed %ld",result); return -1; } //实例化音频播放器对象 result=(*pcmPlayerObject)->Realize(pcmPlayerObject,SL_BOOLEAN_FALSE); if(result!=SL_RESULT_SUCCESS){ LOGI("Realize pcmPlayerObject failed %ld",result); return -1; } //获取音频播放器接口pcmPlayerplay result=(*pcmPlayerObject)->GetInterface(pcmPlayerObject,SL_IID_PLAY,&pcmPlayerplay); if(result!=SL_RESULT_SUCCESS){ LOGI("GetInterface pcmPlayerplay failed %ld",result); return -1; } //获取音频播放的buffer接口SLAndroidSimpleBufferQueueItf result=(*pcmPlayerObject)->GetInterface(pcmPlayerObject,SL_IID_BUFFERQUEUE,&pcmBufferQueue); if(result!=SL_RESULT_SUCCESS){ LOGI("GetInterface pcmBufferQueue failed %ld",result); return -1; } //注册回调RegisterCallback result=(*pcmBufferQueue)->RegisterCallback(pcmBufferQueue,playerCallback, nullptr); if(result!=SL_RESULT_SUCCESS){ LOGI("RegisterCallback failed %ld",result); return -1; } //设置播放状态为playing result=(*pcmPlayerplay)->SetPlayState(pcmPlayerplay,SL_PLAYSTATE_PLAYING); if(result!=SL_RESULT_SUCCESS){ LOGI("SetPlayState failed %ld",result); return -1; } //触发回调 playerCallback(pcmBufferQueue, nullptr); return 0; }
CMakeLists.txt文件:
cmake_minimum_required(VERSION 3.22.1) project("mediaplayer") add_library(${CMAKE_PROJECT_NAME} SHARED # 将自己写的cpp源文件编译成动态库 opensles.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library android log OpenSLES )
在java层只需获取到要播放的pcm文件的位置,然后传入native层即可,代码如下:
val pcmPath=getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath+File.separator+"input.pcm" playPcmBySL(pcmPath)
需要注意的是,pcm文件可以通过使用ffmpeg解码mp3文件得到,但是在解码的时候需要注意的是:解码时位深别用32位浮点型,播放出来会有很大的噪音,最好用有符号的32位整型。原因尚未找到,可能是opensl es不支持32位浮点型位深吧。
可以用以下命令解码得到pcm文件:ffmpeg -i input.mp3 -acodec pcm_s32le -f s32le -ac 2 -ar 44100 -y output.pcm
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: “2023 年度金猿季”七大榜单公布,TDengine 荣登两大榜单
近日,“第六届金猿季 & 魔方论坛 —— 大数据产业发展论坛” 在上海市四行仓库成功举行。本次论坛以 “小趋势・大未来” 为主题,围绕大数据产业的各个领域展开深入讨论,政服务器托管网产学研各界嘉宾、媒体,与直播间上万网友共同见证了此次盛会。在活动的高潮…