3

在Android开发中如何使用OpenSL ES库播放解码后的pcm音频文件? - 故乡的樱花开了

 6 months ago
source link: https://www.cnblogs.com/luqman/p/18027627
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在Android开发中如何使用OpenSL ES库播放解码后的pcm音频文件?

一.认识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. 设置播放状态,手动调用回调函数

  下面给出代码:

//opensles.cpp
#include<cstdint> #include<iostream> #include<jni.h> extern "C"{ #include<SLES/OpenSLES.h> #include<android/log.h> #include<SLES/OpenSLES_Android.h> } #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 outputMixEnvironmentalReverb= 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK