一.认识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

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

  1. Android学习探索之Java 8 在Android 开发中的应用

    前言: Java 8推出已经将近2年多了,引入很多革命性变化,加入了函数式编程的特征,使基于行为的编程成为可能,同时减化了各种设计模式的实现方式,是Java有史以来最重要的更新.但是Android上, ...

  2. android开发中fragment获取context

    在用到fragment时无法使用.this来指定当前context内容,android开发中fragment获取context,可以使用getActivity().getApplicationCont ...

  3. java中的反射机制在Android开发中的用处

    JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反 ...

  4. Android开发中的输入合法性检验

    Why ? 合法性检查对于程序的健壮性具有重要作用.在Android开发中,良好的合法性检查设计机制可以使程序更加清晰,产生bug更少,交互更加友好. What ? 合法性检查的目的在于确定边界.对于 ...

  5. 在android开发中使用multdex的方法-IT蓝豹为你整理

    Android系统在安装应用时,往往需要优化Dex,而由于处理工具DexOpt对id数目的限制,导致其处理的数目不能超过65536个,因此在Android开发中,需要使用到MultiDex来解决这个问 ...

  6. 怎样实现了捕获应用中的日志在android开发中

    怎样实现了捕获应用中的日志在android开发中,大家可研究一下. Process mLogcatProc = null; BufferedReader reader = null; try { mL ...

  7. Android开发中Eclispe相关问题及相应解决(持续更新)

    1.Eclipse项目中的Android Private Libraries没有自动生成. 一般而言,在Android开发中,项目中引用到的jar包会放到项目目录中的libs中,引入库会放到Andro ...

  8. Android开发中的问题及相应解决(持续更新)

    最近博客写的少了,以后还得经常更新才行. ------------------------------------------------------------ 1.特定业务需求下try cath ...

  9. 关于Android开发中的证书和密钥等问题

    关于Android开发中的证书和密钥等问题 引言 除了Android发布应用签名时需要用到证书外,在进行google Map Api开发和Facebook SDK API开发等时都需要申请API Ke ...

  10. Android开发中Bundle用法包裹数据(转)

    Android开发中Bundle用法包裹数据 Bundle的经典用法,包裹数据放入Intent中,目的在于传输数据. SDK 里是这样描述: A mapping from String values ...

随机推荐

  1. [转帖]nginx配置文件中对于if条件语句的写法(附nginx跨域文件配置)

    前言 在nginx配置文件中,可以使用if语句,但是对于else语句其实是不支持的,并且and条件和or条件也是不支持的 实现 else条件的写法 新建一个开关变量flag,初始值为0,如果为1说明进 ...

  2. [转帖]Export Prometheus metrics from SQL queries

    https://github.com/albertodonato/query-exporter query-exporter is a Prometheus exporter which allows ...

  3. [转帖]使用 TiUP bench 组件压测 TiDB

    https://docs.pingcap.com/zh/tidb/stable/tiup-bench 在测试数据库性能时,经常需要对数据库进行压测,为了满足这一需求,TiUP 集成了 bench 组件 ...

  4. [转帖]Linux系统NVME盘分区和挂载

    https://www.jianshu.com/p/04327f1b97cb 查看系统里面识别到的硬盘和分区的信息 $ sudo fdisk -l Disk /dev/nvme1n1: 1.8 TiB ...

  5. [转帖]Linux内核映像vmlinux、Image、zImage、uImage区别

    https://zhuanlan.zhihu.com/p/466226177 本文介绍几种常用的Linux内核映像的区别. 一.vmlinux vmlinux:Linux内核编译出来的原始的内核文件, ...

  6. [转帖]Docker、containerd的关系

    Docker.containerd的关系 containerd囊括了单机运行一个容器时所需要的一切: 为了能够支持多种OCI Runtime,containerd 内部使用containerd-shi ...

  7. [转帖]计算机体系结构-重排序缓存ROB

    https://zhuanlan.zhihu.com/p/501631371 在现代处理器中,重排序缓存(Reorder Buffer,即ROB)是一个至关重要的概念,一个标准的乱序执行处理器在其多个 ...

  8. Linux KVM网络处理过程

    Linux KVM网络处理过程 总体解决方法 本次遇到的问题是KVM的网桥处理不小心导致系统无法连接.处理简要总结: 进入机房,给IPMI插上网线, 开机点 Del 进入bios 设置IMPI的地址 ...

  9. js中forEach的用法、forEach如何跳出循环、forEach与for之间的区别

    定义和用法 forEach() 调用数组的每个元素,并将元素传递给回调函数. 注意: forEach() 对于空数组是不会执行回调函数的. 用法: array.forEach(function(cur ...

  10. vue3动态路由的addRoute和removeRoute使用

    为什么需要有动态路由 有些时候,我们不同的身份角色,我们希望可以展示不同的菜单. 比如说:普通用户只有展示A菜单,管理员有A,B,C菜单 这个时候,我们就需要动态路由了! Vue2和vue3的区别 V ...