前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在Android开发中如何使用OpenSL ES库播放解码后的pcm音频文件?

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

作者头像
故乡的樱花开了
发布2024-02-23 08:06:03
2130
发布2024-02-23 08:06:03
举报
文章被收录于专栏:Android技术专栏

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

  下面给出代码:

代码语言:javascript
复制
//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文件:

代码语言:javascript
复制
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层即可,代码如下:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-02-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档