版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huanghuangjin/article/details/81948994
OpenSL ES 播放声音的流程:
代码
#include "common.hpp"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
static SLObjectItf slObject = NULL; // 引擎的上下文
static SLEngineItf createSLEngine()
{
SLresult ret;
SLEngineItf en; // SLEngineItf 本身是指针,不用担心局部变量内存被回收了
/*
SL_API SLresult SLAPIENTRY slCreateEngine( // 创建OpenSLES引擎
SLObjectItf *pEngine, // 上下文
SLuint32 numOptions, // 选择项的数量
const SLEngineOption *pEngineOptions, // 具体的选择项
SLuint32 numInterfaces, // 支持的接口的数量
const SLInterfaceID *pInterfaceIds, // 具体的要支持的接口,是枚举的数组
const SLboolean * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
);
*/
ret = slCreateEngine(&slObject, 0, NULL, 0, NULL, 0);
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("slCreateEngine failed.");
return NULL;
}
/*
SLresult (*Realize) ( // 实例化,将内部的存储做好
SLObjectItf self, // 相当于上下文
SLboolean async // SL_BOOLEAN_FALSE 表示阻塞知道初始化完成,设置为 SL_BOOLEAN_TURE 时立即返回成功,这个时候初始化的执行是异步的
);
*/
ret = (*slObject)->Realize(slObject, SL_BOOLEAN_FALSE);
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*slEngine)->Realize failed.");
return NULL;
}
/*
SLresult (*GetInterface) ( // 获取接口
SLObjectItf self, // 上下文
const SLInterfaceID iid, // 要获取的接口类型
void * pInterface // 获取到的接口存储到这里面
);
*/
ret = (*slObject)->GetInterface(slObject, SL_IID_ENGINE, &en); // SL_IID_ENGINE 为要获取的具体的接口,是一个对象,不是宏,获取后存放到 en 中
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*slEngine)->GetInterface SL_IID_ENGINE failed.");
return NULL;
}
return en;
}
// 缓冲队列接口的回调函数,在播放队列为空的时候调用, 要确保此函数中的代码能快速执行完,如果此函数处理速度很慢,会造成播放声音时,听起来感觉声音断掉了
static void pcmCall(SLAndroidSimpleBufferQueueItf bf, void *contex)
{
static FILE * fp = NULL;
static char * buf = NULL;
if (!fp)
{
char * path = (char *) contex;
LOGD("path=%s", path);
fp = fopen(path, "rb");
buf = new char[1024*1024];
}
if (!fp)
{
LOGE("打开音频文件失败.");
delete [] buf;
return;
}
if (feof(fp) != EOF)
{
int len = fread(buf, 1, 1024, fp); // 读1024次,每次读1个字节
if (len>0)
{
(*bf)->Enqueue(bf, buf, len); // 给队列传递数据
}
}
else
{
fclose(fp);
delete [] buf;
}
}
JNIEXPORT void JNICALL Java_hankin_hjmedia_mpeg_Mp7_11Activity_play(JNIEnv *env, jobject instance, jstring path_)
{
const char * path = env->GetStringUTFChars(path_, NULL);
// 1、创建OpenSLES引擎
SLEngineItf eng = createSLEngine();
if (eng==NULL) return;
// 2、创建混音器,即创建输出设备的信息
SLObjectItf mix = NULL;
/*
SLresult (*CreateOutputMix) ( // 创建混音器
SLEngineItf self, // 引擎
SLObjectItf * pMix, // 混音器,输入输出参数
SLuint32 numInterfaces, // 支持的接口的数量
const SLInterfaceID * pInterfaceIds, // 具体的要支持的接口,是枚举的数组
const SLboolean * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
);
*/
SLresult ret = (*eng)->CreateOutputMix(eng, &mix, 0, 0, 0); // 后面的参数是做音效特效的
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*eng)->CreateOutputMix failed.");
return;
}
/*
SLresult (*Realize) ( // 实例化混音器
SLObjectItf self, // 混音器本身
SLboolean async // SL_BOOLEAN_FALSE 表示阻塞知道初始化完成,设置为 SL_BOOLEAN_TURE 时立即返回成功,这个时候初始化的执行是异步的
);
*/
ret = (*mix)->Realize(mix, SL_BOOLEAN_FALSE); // SL_BOOLEAN_FALSE 表示阻塞等待混音器创建完毕
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*mix)->Realize failed.");
return;
}
SLDataLocator_OutputMix outmix = {SL_DATALOCATOR_OUTPUTMIX, mix}; // SLDataLocator_OutputMix 表示输出的对象,SL_DATALOCATOR_OUTPUTMIX是固定的传这个
SLDataSink audioSink = {&outmix, NULL}; // audioSink 表示声音的对象,播放的时候传进去,表示播放的是哪个混音设备
// 3、配置输入的音频的信息(数据源是pcm)
// 缓冲队列的配置信息,用队列的方式在cpu与音频播放设备之间来交互,SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE 是固定传入,10表示队列的长度
SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 10};
SLDataFormat_PCM pcm = { // 输入的音频格式,已知的 test.pcm 就是以下配置
SL_DATAFORMAT_PCM, // 输入的音频格式,这里只能是SL_DATAFORMAT_PCM,表示PCM格式的原始未压缩的数据,其他格式的用其他格式对应的结构体?
2, // 2个声道数(立体声)
SL_SAMPLINGRATE_44_1, // 采样率 44100hz
SL_PCMSAMPLEFORMAT_FIXED_16, // 16bit保存一个音
SL_PCMSAMPLEFORMAT_FIXED_16, // 容器大小,与 bitsPerSample 一样大可以了
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // 前左声道与前右声道, 左右声道(立体声)
SL_BYTEORDER_LITTLEENDIAN // 字节序, SL_BYTEORDER_LITTLEENDIAN 表示小端 ,播放的声音有问题的时候,这个是一个排查的方向
};
SLDataSource ds = {&que, &pcm}; // 数据源信息的结构体,创建播放器的时候使用
// 4、创建播放器
SLObjectItf player = NULL; // 播放器
SLPlayItf playerItf = NULL; // 播放器的接口
SLAndroidSimpleBufferQueueItf pcmQueItf = NULL; // 缓冲队列的接口,通过此接口往队列写数据,发送的数据播放完后会从缓冲队列中删掉
const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE}; // SL_IID_BUFFERQUEUE 表示缓冲队列的接口
const SLboolean req[] = {SL_BOOLEAN_TRUE}; // 表示ids中需要获取的接口是开放还是关闭,这两个数组要长度一致
/*
SLresult (*CreateAudioPlayer) ( // 创建播放器
SLEngineItf self, // 引擎
SLObjectItf * pPlayer, // 创建的播放器,输入输出参数
SLDataSource *pAudioSrc, // 数据源的配置信息
SLDataSink *pAudioSnk, // 输出的设备的配置信息
SLuint32 numInterfaces, // 支持的接口的数量
const SLInterfaceID * pInterfaceIds, // 具体的要支持的接口,是枚举的数组
const SLboolean * pInterfaceRequired // 具体的要支持的接口是开放的还是关闭的,也是一个数组,这三个参数长度是一致的
);
*/
ret = (*eng)->CreateAudioPlayer(eng, &player, &ds, &audioSink, sizeof(ids)/sizeof(SLInterfaceID), ids, req);
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*eng)->CreateAudioPlayer failed.");
return;
}
(*player)->Realize(player, SL_BOOLEAN_FALSE); // 阻塞等待实例化完成
/*
SLresult (*GetInterface) ( // 获取接口
SLObjectItf self, // 上下文
const SLInterfaceID iid, // 要获取的接口类型
void * pInterface // 获取到的接口存储到这里面
);
*/
ret = (*player)->GetInterface(player, SL_IID_PLAY, &playerItf); // SL_IID_PLAY 表示要获取播放接口,CreateAudioPlayer函数中不需要设置支持它
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*player)->GetInterface SL_IID_PLAY failed.");
return;
}
ret = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &pcmQueItf); // 获取播放的缓冲队列接口,用于注册缓冲队列接口的回调函数
if (ret!=SL_RESULT_SUCCESS)
{
LOGE("(*player)->GetInterface SL_IID_BUFFERQUEUE failed.");
return;
}
/*
SLresult (*RegisterCallback) ( // 设置缓冲接口的回调函数,在播放队列为空的时候调用
SLAndroidSimpleBufferQueueItf self,
slAndroidSimpleBufferQueueCallback callback, // 回调函数
void* pContext // 给回调函数传的参数,看情况设置
);
*/
(*pcmQueItf)->RegisterCallback(pcmQueItf, pcmCall, (void *) path); // env->ReleaseStringUTFChars(path_, path); 可能会在回调函数前调用,需要注意
/*
SLresult (*SetPlayState) ( // 设置播放状态
SLPlayItf self, // 播放接口
SLuint32 state // 播放的状态,有 SL_PLAYSTATE_PLAYING SL_PLAYSTATE_PAUSED SL_PLAYSTATE_STOPPED 三种状态
);
*/
(*playerItf)->SetPlayState(playerItf, SL_PLAYSTATE_PLAYING); // 设置播放状态,播放中,这个时候播放队列的回调函数并不会被调用
/*
SLresult (*Enqueue) ( // 给队列传递数据
SLAndroidSimpleBufferQueueItf self, // 队列
const void *pBuffer, // 要传送的数据
SLuint32 size // 数据的长度
);
*/
(*pcmQueItf)->Enqueue(pcmQueItf, "", 1); // 调用一次播放队列回调函数, "", 1 表示传递了一个 \0 ,\0在声音中表示无声
/*
void (*Destroy) ( // 释放内存,播放音频的内存为全局变量只会创建一次,而且要贯穿应用进程,所以可以不用考虑释放的问题
SLObjectItf self
);
*/
// (*slObject)->Destroy(slObject);
// env->ReleaseStringUTFChars(path_, path);
}