目录

 

播放流程和条件

Opengl SLObjectItf 对象创建的四板斧

播放pcm的流程


播放流程和条件

android自带的openSL库,可用来解码音频,也可以来播放音频,以及录音。要在jni层调用:

1、cmakeList 中target_link_libraries 内引入库:OpenSLES

2、引入头文件:

#include "SLES/OpenSLES.h"
#include "SLES/OpenSLES_Android.h"

播放流程

android 音频详解 android音频src_回调函数

播放队列

只要往播放队列中压入数据,就会播放。这里采用的是回调函数,当取出buf播放完毕就会清理buf,让队列空出,这时候就push数据叫你去,从而播放。

android 音频详解 android音频src_回调函数_02

OPengl SLObjectItf 对象创建的四板斧

一般对象创建完毕后,用到的就是 SLObjectItf的对象 interface接口功能对象

1、定义 SLObjectItf

Opensl机构体SLObjectItf可以定义大部分结构体,此篇文章结构体对象都是用它初始化的。结构体 SLObjectItf 类似于java的Object类,属于一个基结构体。音频的大多数结构体都继承于它。

typedef const struct SLObjectItf_ * const * SLObjectItf;

从结构以定义看出,它是一个结构以指针,因此,定义一个SLObjectItf 变量,其实是定义一个 SLObjectItf的指针

2、创建 slCreate

因为SLObjectItf是指针对象,为了更好管理内存,会将SLObjectItf地址传入opensl中,来创建。下面为创建Engine引擎的例子:

SLObjectItf  engineSL = NULL;
re = slCreateEngine(&engineSL,0,0,0,0,0);
  • 1、对象空间就会在 opensl内部创建,方便利用opensl内部释放
  • 2、后面参数为支持创建的接口数量,和注册接口,方便后面getInterface获得接口功能。不注册后面会后去失败。

3、实例化 Realize

SLresult (*Realize) (
		SLObjectItf self,
		SLboolean async
	);// 第二个参数,是否等待创建好再返回

这个方法会将对象的一些参数分配好。

4、获得功能接口 GetInterface

SLresult (*GetInterface) (
		SLObjectItf self,
		const SLInterfaceID iid,
		void * pInterface
	);

参数:

SLObjectItf self 获取结构对象

const SLInterfaceID iid, 获取的结构功能id

void * pInterface 存储接口功能的对象,即接口对象。

下面为创建一个音频引擎的方法例子,大家就可以看出来:

//定义
static SLObjectItf  engineSL = NULL;
SLEngineItf CreateSL(){
    SLresult  re;
    SLEngineItf en;

    // 创建
    re = slCreateEngine(&engineSL,0,0,0,0,0);
    if(re != SL_RESULT_SUCCESS)
        return NULL;

    // 实例化
    re= (*engineSL)->Realize(engineSL, SL_BOOLEAN_FALSE);// 第二个参数,是否等待创建好再返回
    if(re != SL_RESULT_SUCCESS) return NULL;

    // 获取引擎接口,并存入en功能接口对象
    re = (*engineSL)->GetInterface(engineSL,SL_IID_ENGINE, &en);

    if(re != SL_RESULT_SUCCESS) return NULL;
    return en;

}

播放pcm的流程

1、创建播放引擎

上面的代码已经给出了,创建音频播放引擎对象,和获得 播放引擎接口对象。这里介绍下函数

SL_API SLresult SLAPIENTRY slCreateEngine(
	SLObjectItf             *pEngine,
	SLuint32                numOptions,
	const SLEngineOption    *pEngineOptions,
	SLuint32                numInterfaces,
	const SLInterfaceID     *pInterfaceIds,
	const SLboolean         * pInterfaceRequired
);
参数:
SLObjectItf             *pEngine, 对象
SLuint32                numOptions, 选择项数量
const SLEngineOption    *pEngineOptions,  具体的选择项
SLuint32                numInterfaces,  支持接口的数量
const SLInterfaceID     *pInterfaceIds, 支持的接口
const SLboolean         * pInterfaceRequired 接口是关闭还是打开

2、创建输出设备

SLEngineItf  eng = CreateSL();
    // 创建 混音器
    SLObjectItf mix = NULL;
    re = (*eng)->CreateOutputMix(eng,&mix,0,0,0);// 后面配置项,做混音特效
    if(re != SL_RESULT_SUCCESS){
        LOGE("CreateOutputMix  failed");
    }
    //实例化 mix
    re = (*mix)->Realize(mix, SL_BOOLEAN_FALSE);
    if(re != SL_RESULT_SUCCESS){
        LOGE("mix Realize  failed");
    }
    // 创建一个输出接口,给播放器调用
    SLDataLocator_OutputMix outMix = {SL_DATALOCATOR_OUTPUTMIX, mix};
    SLDataSink audioSink= {&outMix,0};

从代码可以看出,SLObjectItf 对象获取功能接口用getInterface,而其他对象并不是,这里目的:

  • 创建混音器 mix
  • 创建一个混音器输出对象audioSink, 而混音的输出对象outmix,就存在audioSink中初始化的。

3、配置pcm音频格式信息

// 配置音频信息
    //缓冲队列
    SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE};
    //音频格式配置
    SLDataFormat_PCM pcm={
            SL_DATAFORMAT_PCM,
            2,// 声道数
            SL_SAMPLINGRATE_44_1,//采用率44100
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,
            SL_BYTEORDER_LITTLEENDIAN// 字节序,小端
    };
    // 构建结构体给 播放器使用
    SLDataSource ds={&que, &pcm};

这里由代码可以看出,

  1. 创建了一个播放的音频的队列que。
  2. 创建配置音频pcm格式信息的对象pcm。
  3. 将播放队列和音频格式组件一个结构体ds,方便给播放器使用。

4、创建播放器

// 创建播放器
    SLObjectItf player=NULL;
    SLPlayItf playInterface;
    SLAndroidSimpleBufferQueueItf pcmQue = NULL; // 播放队列

    const SLInterfaceID  ids[]={SL_IID_BUFFERQUEUE};// 播放队列接口参数
    const SLboolean req[]={SL_BOOLEAN_TRUE};//表示接口是否开放
    // 这里必须注册 SL_BOOLEAN_TRUE,SL_IID_BUFFERQUEUE 否则后面无法GetInterface到接口
    //sizeof(ids)/ sizeof(SLInterfaceID) 播放队列参数个数
    re = (*eng)->CreateAudioPlayer(eng, &player, &ds, &audioSink, sizeof(ids)/ sizeof(SLInterfaceID) , ids,req );
    if(re != SL_RESULT_SUCCESS){
        LOGE("CreateAudioPlayer  failed");
    } else{
        LOGW("CreateAudioPlayer  success");
    }
    //实例化 播放器
    (*player)->Realize(player,SL_BOOLEAN_FALSE);
    // 获得播放器接口
    re = (*player)->GetInterface(player, SL_IID_PLAY,&playInterface);
    if(re != SL_RESULT_SUCCESS){
        LOGE("GetInterface  failed");
    }
    re = (*player)->GetInterface(player,SL_IID_BUFFERQUEUE, &pcmQue);
    if(re != SL_RESULT_SUCCESS){
        LOGE("GetInterface SL_IID_BUFFERQUEUE  failed");
    }

创建播放器对象,从代码看主要干了:

  1. 创建CreateAudioPlayer播放器对象,并注册了接口:SL_IID_BUFFERQUEUE ,具体参数可以看上面四板斧中create的介绍。并且将上马的混音器输出设备对象audioSink 和  输出队列音频格式对象 ds传给播放器。
  2. 实例化播放器,并且获得播放器功能接口playInterface

4、设置播放回调函数,启动引擎播放。

  • 启动引擎播放
// 设置回调函数,播放队列为空调用
    (*pcmQue)->RegisterCallback(pcmQue, PcmCall,0);

    (*playInterface)->SetPlayState(playInterface, SL_PLAYSTATE_PLAYING);
    // 启动队列回调,传递一个空字符串来启动
    (*pcmQue)->Enqueue(pcmQue,"",1);

启动了引擎后,就不会不停的执行回调函数,因此只需要在回调函数中压入数据即可。

  • 回调函数,压入数据到播放队列
void PcmCall(SLAndroidSimpleBufferQueueItf bf, void * context){
    LOGW("PcmCall");
    static  FILE *fp = NULL;
    static  char *buf = NULL;
    if(!buf)
    {
        buf = new char[1024*1024];
    }
    if(!fp)
    {
        fp = fopen("test.pcm","rb")  ;
    }
    if(!fp) return;

    if(feof(fp)==0)// =0 表示没有到结尾
    {
       int len = fread(buf,1, 1024,fp);// 读取到buf,一个单位多少,读多大,fp
        if(len > 0)
        {
            (*bf)-> Enqueue(bf, buf, len);// 发送到的bf, 发送的buf,长度
        }
    }
}

从代码看出,是将文件test.pcm 读取出来,然后传入到播放队列,来进行播放的。播放引擎有数据就会播放,没数据就会堵塞。

到这里完整的播放pcm的功能就完成了。