经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
在Android开发中如何使用OpenSL ES库播放解码后的pcm音频文件?
来源:cnblogs  作者:故乡的樱花开了  时间:2024/2/23 8:57:37  对本文有异议

一.认识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音频数据的步骤

  开发步骤如下:

  1. 创建引擎对象和接口
  2. 创建混音器对象和接口
  3. 创建播放器对象和接口
  4. 创建缓冲队列接口并给缓冲队列注册回调函数
  5. 设置播放状态,手动调用回调函数

  下面给出代码:

  1. //opensles.cpp
    #include<cstdint>
  2. #include<iostream>
  3. #include<jni.h>
  4.  
  5. extern "C"{
  6. #include<SLES/OpenSLES.h>
  7. #include<android/log.h>
  8. #include<SLES/OpenSLES_Android.h>
  9. }
  10. #define TAG "jni" // 这个是自定义的LOG的标识
  11. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
  12. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
  13. #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
  14. #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
  15. #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型
  16. using namespace std;
  17. //engine interface
  18. static SLObjectItf engineObject= nullptr;
  19. static SLEngineItf engineEngine= nullptr;
  20. //output mix interfaces
  21. static SLObjectItf outputMixObject= nullptr;
  22. static SLEnvironmentalReverbItf outputMixEnvironmentalReverb= nullptr;
  23. //player interface
  24. static SLObjectItf pcmPlayerObject= nullptr;
  25. static SLPlayItf pcmPlayerplay= nullptr;
  26. //buffer queue
  27. static SLAndroidSimpleBufferQueueItf pcmBufferQueue= nullptr;
  28. //pcm file
  29. FILE *pcmFile= nullptr;
  30. void *buffer= nullptr;
  31. uint8_t *out_buffer= nullptr;
  32. static const SLEnvironmentalReverbSettings reverbSettings=SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
  33. //播放回调
  34. void playerCallback(SLAndroidSimpleBufferQueueItf bufferQueueItf,void *context){
  35. if(bufferQueueItf!=pcmBufferQueue){
  36. LOGI("SLAndroidSimpleBufferQueueItf is not equal");
  37. return;
  38. }
  39. while(!feof(pcmFile)){
  40. size_t size=fread(out_buffer,44100*2*4,1,pcmFile);
  41. if(out_buffer== nullptr||size==0){
  42. LOGI("read end %ld",size);
  43. }else{
  44. LOGI("reading %ld",size);
  45. }
  46. buffer=out_buffer;
  47. break;
  48. }
  49. if(buffer){
  50. LOGI("buffer is not null");
  51. SLresult result=(*pcmBufferQueue)->Enqueue(pcmBufferQueue,buffer,44100*2*4);
  52. if(result!=SL_RESULT_SUCCESS){
  53. LOGI("pcmBufferQueue error %ld",result);
  54. }
  55. }
  56. }
  57. jint playPcmBySL(JNIEnv *env,jobject thiz,jstring pcm_path){
  58. const char *pcmPath=env->GetStringUTFChars(pcm_path, nullptr);
  59. pcmFile=fopen(pcmPath,"r");
  60. env->ReleaseStringUTFChars(pcm_path,pcmPath);
  61. if(pcmFile== nullptr){
  62. LOGI("open pcmFile error");
  63. return -1;
  64. }
  65. out_buffer=(uint8_t *)malloc(44100*2*4);
  66. //创建引擎对象
  67. SLresult result=slCreateEngine(&engineObject,0,nullptr,0,nullptr,nullptr);
  68. if(result!=SL_RESULT_SUCCESS){
  69. LOGI("slCreateEngine failed %ld",result);
  70. return -1;
  71. }
  72. //实例化引擎
  73. result=(*engineObject)->Realize(engineObject,SL_BOOLEAN_FALSE);
  74. if(result!=SL_RESULT_SUCCESS){
  75. LOGI("engine realize failed %ld",result);
  76. return -1;
  77. }
  78. //获取引擎接口SLEngineItf
  79. result=(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineEngine);
  80. if(result!=SL_RESULT_SUCCESS){
  81. LOGI("GetInterface SLEngineItf failed %ld",result);
  82. return -1;
  83. }
  84. //创建输出混音器
  85. const SLInterfaceID ids[1]={SL_IID_ENVIRONMENTALREVERB};
  86. const SLboolean req[1]={SL_BOOLEAN_FALSE};
  87. result=(*engineEngine)->CreateOutputMix(engineEngine,&outputMixObject,1,ids,req);
  88. if(result!=SL_RESULT_SUCCESS){
  89. LOGI("CreateOutputMix failed %ld",result);
  90. return -1;
  91. }
  92. //实例化混音器
  93. result=(*outputMixObject)->Realize(outputMixObject,SL_BOOLEAN_FALSE);
  94. if(result!=SL_RESULT_SUCCESS){
  95. LOGI("Realize outputMixObject failed %ld",result);
  96. return -1;
  97. }
  98. //获取混音器接口SLEnvironmentalReverbItf
  99. result=(*outputMixObject)->GetInterface(outputMixObject,SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);
  100. if(result!=SL_RESULT_SUCCESS){
  101. LOGI("GetInterface SLEnvironmentalReverbItf failed %ld",result);
  102. return -1;
  103. }
  104. //给混音器设置环境混响属性
  105. (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb,&reverbSettings);
  106. //设置输入 SLDataSource
  107. SLDataLocator_AndroidSimpleBufferQueue loc_bufq={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
  108. SLDataFormat_PCM formatPcm={
  109. SL_DATAFORMAT_PCM,
  110. 2,
  111. SL_SAMPLINGRATE_44_1,
  112. SL_PCMSAMPLEFORMAT_FIXED_32,
  113. SL_PCMSAMPLEFORMAT_FIXED_32,
  114. SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,
  115. SL_BYTEORDER_LITTLEENDIAN
  116. };
  117. SLDataSource slDataSource={&loc_bufq,&formatPcm};
  118. //设置输出SLDataSink
  119. SLDataLocator_OutputMix loc_outmix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};
  120. SLDataSink audioSnk={&loc_outmix, nullptr};
  121. //创建音频播放器对象
  122. const SLInterfaceID ids2[1] = {SL_IID_BUFFERQUEUE};
  123. const SLboolean req2[1] = {SL_BOOLEAN_TRUE};
  124. result=(*engineEngine)->CreateAudioPlayer(engineEngine,&pcmPlayerObject,&slDataSource,&audioSnk,1,ids2,req2);
  125. if(result!=SL_RESULT_SUCCESS){
  126. LOGI("CreateAudioPlayer failed %ld",result);
  127. return -1;
  128. }
  129. //实例化音频播放器对象
  130. result=(*pcmPlayerObject)->Realize(pcmPlayerObject,SL_BOOLEAN_FALSE);
  131. if(result!=SL_RESULT_SUCCESS){
  132. LOGI("Realize pcmPlayerObject failed %ld",result);
  133. return -1;
  134. }
  135. //获取音频播放器接口pcmPlayerplay
  136. result=(*pcmPlayerObject)->GetInterface(pcmPlayerObject,SL_IID_PLAY,&pcmPlayerplay);
  137. if(result!=SL_RESULT_SUCCESS){
  138. LOGI("GetInterface pcmPlayerplay failed %ld",result);
  139. return -1;
  140. }
  141. //获取音频播放的buffer接口SLAndroidSimpleBufferQueueItf
  142. result=(*pcmPlayerObject)->GetInterface(pcmPlayerObject,SL_IID_BUFFERQUEUE,&pcmBufferQueue);
  143. if(result!=SL_RESULT_SUCCESS){
  144. LOGI("GetInterface pcmBufferQueue failed %ld",result);
  145. return -1;
  146. }
  147. //注册回调RegisterCallback
  148. result=(*pcmBufferQueue)->RegisterCallback(pcmBufferQueue,playerCallback, nullptr);
  149. if(result!=SL_RESULT_SUCCESS){
  150. LOGI("RegisterCallback failed %ld",result);
  151. return -1;
  152. }
  153. //设置播放状态为playing
  154. result=(*pcmPlayerplay)->SetPlayState(pcmPlayerplay,SL_PLAYSTATE_PLAYING);
  155. if(result!=SL_RESULT_SUCCESS){
  156. LOGI("SetPlayState failed %ld",result);
  157. return -1;
  158. }
  159. //触发回调
  160. playerCallback(pcmBufferQueue, nullptr);
  161. return 0;
  162. }

  CMakeLists.txt文件:

  1. cmake_minimum_required(VERSION 3.22.1)
  2. project("mediaplayer")
  3. add_library(${CMAKE_PROJECT_NAME} SHARED
  4. # 将自己写的cpp源文件编译成动态库
  5. opensles.cpp)
  6. target_link_libraries(${CMAKE_PROJECT_NAME}
  7. # List libraries link to the target library
  8. android
  9. log
  10. OpenSLES
  11. )

  在java层只需获取到要播放的pcm文件的位置,然后传入native层即可,代码如下:

  1. val pcmPath=getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)?.absolutePath+File.separator+"input.pcm"
  2. 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

原文链接:https://www.cnblogs.com/luqman/p/18027627

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号