OpenSLES播放PCM音频数据

如何在项目中引入OpenSL ES

在android ndk相关目录下已经引入了opensles.so。
在这里插入图片描述
因此可以在 CMakeList.txt中加入 直接使用。

cmake_minimum_required(VERSION 3.4.1)

add_library(
        native-lib
        SHARED
        native-lib.cpp)

target_link_libraries(
        android
        native-lib
        log
        OpenSLES)

Objects 和 Interfaces

OpenSL ES 有两个必须理解的概念,就是 Object (对象)和 Interface(接口) :

  • 对象是一组功能的抽象,每个对象都包含了许多的接口,由这些接口提供具体的功能。

  • 每个 Object 对象都提供了一些最基础的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用该对象支持的功能函数,则通过其 GetInterface 函数拿到 接口,然后通过接口来访问具体功能。

在 “OpenSLES.h” 文件可以看到 OpenSL ES 定义的所有 Object 对象的 ID,通过这些Object ID 来创建对应的对象实例。

/* Objects ID's */

#define SL_OBJECTID_ENGINE			((SLuint32) 0x00001001)
#define SL_OBJECTID_LEDDEVICE		((SLuint32) 0x00001002)
#define SL_OBJECTID_VIBRADEVICE		((SLuint32) 0x00001003)
#define SL_OBJECTID_AUDIOPLAYER		((SLuint32) 0x00001004)
#define SL_OBJECTID_AUDIORECORDER	((SLuint32) 0x00001005)
#define SL_OBJECTID_MIDIPLAYER		((SLuint32) 0x00001006)
#define SL_OBJECTID_LISTENER		((SLuint32) 0x00001007)
#define SL_OBJECTID_3DGROUP			((SLuint32) 0x00001008)
#define SL_OBJECTID_OUTPUTMIX		((SLuint32) 0x00001009)
#define SL_OBJECTID_METADATAEXTRACTOR	((SLuint32) 0x0000100A)

同样,“OpenSLES.h” 文件中还定义了所有的 Interface ID,通过 Interface ID 可以从对象中获取到对应的功能接口。

/* Standard Object Interface */
extern SL_API const SLInterfaceID SL_IID_OBJECT;
/** Output Mix Interface */
extern SL_API const SLInterfaceID SL_IID_OUTPUTMIX;
extern SL_API const SLInterfaceID SL_IID_PLAY;
/**Buffer Queue Interface*/
extern SL_API const SLInterfaceID SL_IID_BUFFERQUEUE;
extern SL_API const SLInterfaceID SL_IID_ENVIRONMENTALREVERB;
/** Engine Interface*/
extern SL_API const SLInterfaceID SL_IID_ENGINE;
//....只列出部分

OpenSL ES 的状态机制

在这里插入图片描述

任何一个 OpenSL ES 的对象,创建成功后,都进入 SL_OBJECT_STATE_UNREALIZED 状态,这种状态下,系统不会为它分配任何资源。

调用Realize 后的对象, 会分配相关的资源,进入 SL_OBJECT_STATE_REALIZED 状态,这是一种“可用”的状态,只有在这种状态下,对象的各个功能才能正常地访问。

当一些系统事件发生后,比如出现错误或者 Audio 设备被其他应用抢占,OpenSL ES 对象会进入 SL_OBJECT_STATE_SUSPENDED 状态, 调用Resume()可以恢复到 SL_OBJECT_STATE_REALIZED 状态。

当调用对象的 Destroy 函数后,则会释放资源,回到 SL_OBJECT_STATE_UNREALIZED 状态。

Engine Object

OpenSL ES 里面最核心的对象就是:Engine Object(音频引擎对象),它主要提供如下功能:

  • 提供引擎接口: SLEngineItf,该接口可以用来创建所有其他的 Object 对象

Engine Objec创建 &初始化

SLObjectItf engineObject;
 slCreateEngine(&engineObject,0, NULL, 0, NULL, NULL);
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);

获取SLEngineItf接口,获取后 就可以使用 engineEngine 来创建 OpenSL ES 的其他对象了。

SLEngineItf engineEngine;
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));

最后不用了就销毁

(*engineObject)->Destroy(engineObject);

使用流程

  • 1、获取引擎接口
  • 2、创建输出混音器
  • 3、创建播放器,并获取播放接口
  • 4、获取缓冲队列接口并给缓冲队列注册回调函数
  • 5、设置播放状态、手动调用回调函数

1、获取引擎接口

slCreateEngine(&engineObject,0, NULL, 0, NULL, NULL);
(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);

获取引擎接口至关重要,后面所有的对象都是通过引擎接口创建的。

OpenSLES接口的获取都是遵循这三步,先创建对象、再初始化对象、最后通过接口id从对象获取对应功能的接口。最后不用的时候可以调用对象的Destroy函数进行销毁。

2、创建输出混音器

const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
    (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    sLresult=(*outputMixObject)->GetInterface(outputMixObject,
    SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);
    if (sLresult==SL_RESULT_SUCCESS){
        (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                outputMixEnvironmentalReverb, &reverbSettings);
        (void)sLresult;
    }

3、创建播放器并获取播放接口:创建播放器还需要为播放器指定Data Source 和 Data Sink

  • data source 代表着输入源的信息,即数据从哪儿来、输入的数据格式是怎样的。
  • data sink 代表着输出的信息,即数据输出到哪儿、以什么样的方式输出。

data source和data sink的结构体如下:

//data source
typedef struct SLDataSource_ {
	void *pLocator;
	void *pFormat;
} SLDataSource;
//data sink
typedef struct SLDataSink_ {
	void *pLocator;
	void *pFormat;
} SLDataSink;

对于pLocator来说,通常有下面这些常用值:

#define SL_DATALOCATOR_URI			((SLuint32) 0x00000001)//数据源是URI
#define SL_DATALOCATOR_ANDROIDFD    ((SLuint32) 0x800007BC)//数据源通常是asset文件
#define SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE ((SLuint32) 0x800007BD)//数据源是缓冲队列
#define SL_DATALOCATOR_OUTPUTMIX	((SLuint32) 0x00000004)//输出到混音器
//....
//配置DataSource
 SLDataLocator_AndroidSimpleBufferQueue loc_bufq={
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,//从缓存队列获取数据
            2
    };
  //tips:数据源除了指定缓冲队列外,还有本地文件、URI
  SLDataFormat_PCM format_pcm={
            SL_DATAFORMAT_PCM,//是pcm格式的
            2,//两声道
            SL_SAMPLINGRATE_44_1,//每秒的采样率
            SL_PCMSAMPLEFORMAT_FIXED_16,//每个采样的位数
            SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一样就行
            SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,//立体声(左前和右前)
            SL_BYTEORDER_LITTLEENDIAN,//播放结束的标志
    };
  SLDataSource dataSource={&loc_bufq,&format_pcm};
  //配置DataSink,指定输出到输出混音器
  SLDataLocator_OutputMix loc_outmix={SL_DATALOCATOR_OUTPUTMIX,outputMixObject};
  SLDataSink dataSink={&loc_outmix,NULL};
  //创建音频播放器对象
  const SLInterfaceID iids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
  const SLboolean ireq[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
  (*engineEngine)->CreateAudioPlayer(engineEngine,&playerObject,
  		&dataSource,&dataSink,3,iids,ireq);
  (*playerObject)->Realize(playerObject,SL_BOOLEAN_FALSE);
   //获取播放接口
  (*playerObject)->GetInterface(playerObject,SL_IID_PLAY,&player);

4、获取缓冲队列接口并给缓冲队列注册回调函数

 (*playerObject)->GetInterface(playerObject,SL_IID_BUFFERQUEUE,&bufferQueueItf);
 (*bufferQueueItf)->RegisterCallback(bufferQueueItf,bufferQueueCallable,NULL);

5、设置播放状态、手动调用回调函数

 (*player)->SetPlayState(player,SL_PLAYSTATE_PLAYING);
 bufferQueueCallable(bufferQueueItf,NULL);

当设置了播放器为播放状态,该音频播放器开始等待缓存取队列就绪。
回调函数如下:

void bufferQueueCallable(SLAndroidSimpleBufferQueueItf caller,void *pContext){
    size_t len=getPcmData(&pcmData);
    if(pcmData&&len!=-1){
        (*bufferQueueItf)->Enqueue(bufferQueueItf,pcmData,len);
    } else{
        LOGD("读取结束");
        if(file!=NULL){
            fclose(file);
            file=NULL;
        }
    }
}

第一次我们手动调用了回调函数,然后会通过Enqueue函数将数据入队,接着播放器会从缓冲队列中取出数据进行播放,每次缓冲队列数据播放完成都会调用回调函数,回调又接着往缓冲队列中放入数据。

最后在适当的时候释放所有资源:

  if (playerObject!=NULL){
        (*playerObject)->Destroy(playerObject);
        playerObject=NULL;
        bufferQueueItf=NULL;
    }

    if (outputMixObject!=NULL){
        (*outputMixObject)->Destroy(outputMixObject);
        outputMixObject=NULL;
        outputMixEnvironmentalReverb=NULL;
    }

    if (engineObject!=NULL){
        (*engineObject)->Destroy(engineObject);
        engineObject=NULL;
        engineEngine=NULL;
    }

    if(buff!=NULL){
        free(buff);
        buff=NULL;
    }

源码地址:
github

参考资料:
谷歌ndk案例
参考博客

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值