本文介绍一下内容:
- 介绍OpenSLES API如何使用
- 使用过程中遇到的问题解决
- Android studio的一些配置
本文是在研究了NDK官方的例子后的总结,
native-audio,官方demo中有一些问题,每个手机的支持程度有不太一样,总之这套API给我的感觉是不太稳定。将就着能用,对于纯native的项目来说,可以不用jni回调java代码是唯一的好处。因为IOS并不支持OpenSLES,而是支持OpenAL,所以跨平台还是要写不同的代码。
需要的头文件。
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
初始化声音引擎。
// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine = NULL;
// output mix interfaces
static SLObjectItf outputMixObject = NULL;
//--------------------------------------------------------------------------------------------------
struct AudioPlayer
{
SLObjectItf object;
SLPlayItf play;
SLSeekItf seek;
SLVolumeItf volume;
};
static void Init()
{
SLresult result;
// create engine
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
ALogA(result == SL_RESULT_SUCCESS, "Audio Init slCreateEngine failed");
// realize the engine
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_TRUE);
ALogA(result == SL_RESULT_SUCCESS, "Audio Init Realize engineObject failed");
// get the engine interface, which is needed in order to create other objects
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
ALogA(result == SL_RESULT_SUCCESS, "Audio Init GetInterface failed");
const SLInterfaceID ids[0];
const SLboolean req[0];
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, ids, req);
ALogA(result == SL_RESULT_SUCCESS, "Audio Init CreateOutputMix failed");
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
ALogA(result == SL_RESULT_SUCCESS, "Audio Init Realize outputMixObject failed");
}
我抽象了一个AudioPlayer对象,包含了播放器的,播放,定位,声音的控制对象。每个播放器是由声音引擎创建的。API有固定的模式,所有的对象都是通过,Create创建,Realize实现,GetINterface获取子对象,拥有哪些子对象是在Create的时候通过,SLInterfaceID和SLboolean数组申请的。
这里我们看到,我创建的engine是最简单直接的,所有的播放器通过engine来创建。这里OutputMix就是播放的混音功能,这里传入了空的interface,但是官方demo创建的时候申请了环境音效。但是真机测试的时候,我发现带有环境音效,无法正确调用播放器的loop回调函数。去掉就好了。官方demo的设置如下。
// create output mix, with environmental reverb specified as a non-required interface
const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
const SLboolean req[1] = {SL_BOOLEAN_FALSE};
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
assert(SL_RESULT_SUCCESS == result);
(void)result;
接下来,创建一个播放器。
static inline void InitPlayer(const char* filePath, AudioPlayer* player)
{
off_t start;
off_t length;
int fd = AFile->OpenFileDescriptor(filePath, &start, &length);
// configure audio source
SLDataLocator_AndroidFD locFD = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
SLDataFormat_MIME formatMME = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&locFD, &formatMME};
// configure audio sink
SLDataLocator_OutputMix locOutMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&locOutMix, NULL};
// create audio player
const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_PLAY, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
SLresult result = (*engineEngine)->CreateAudioPlayer
(
engineEngine,
&player->object,
&audioSrc,
&audioSnk,
3, ids, req
);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer CreateAudioPlayer failed");
// realize the player
result = (*player->object)->Realize(player->object, SL_BOOLEAN_FALSE);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer Realize playerObject failed");
// get the play interface
result = (*player->object)->GetInterface(player->object, SL_IID_PLAY, &player->play);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer GetInterface play failed");
// player callback
result = (*player->play)->RegisterCallback(player->play, PlayerCallback, player);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer RegisterCallback failed");
result = (*player->play)->SetCallbackEventsMask(player->play, SL_PLAYEVENT_HEADATEND);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer SetCallbackEventsMask failed");
// get the seek interface
result = (*player->object)->GetInterface(player->object, SL_IID_SEEK, &player->seek);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer GetInterface seek failed");
// disable looping
result = (*player->seek)->SetLoop(player->seek, SL_BOOLEAN_FALSE, 0, SL_TIME_UNKNOWN);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer SetLoop failed");
// get the volume interface
result = (*player->object)->GetInterface(player->object, SL_IID_VOLUME, &player->volume);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer GetInterface volume failed");
}
是从assets的音频文件创建一个播放器。播放器申请了3个功能,SL_IID_PLAY播放对象,SL_IID_SEEK定位对象,SL_IID_VOLUME声音对象。另外播放对象,注册了播放完成的回调函数。
// player callback
result = (*player->play)->RegisterCallback(player->play, PlayerCallback, player);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer RegisterCallback failed");
result = (*player->play)->SetCallbackEventsMask(player->play, SL_PLAYEVENT_HEADATEND);
ALogA(result == SL_RESULT_SUCCESS, "Audio CreatePlayer SetCallbackEventsMask failed");
#define SL_PLAYEVENT_HEADATEND // 播放结束
#define SL_PLAYEVENT_HEADATMARKER // 自定义位置标记,可以通过SetMarkerPosition设置
#define SL_PLAYEVENT_HEADATNEWPOS // 每次位置变化,这个位置间隔不确定,不同设备不一样
#define SL_PLAYEVENT_HEADMOVING // 我没用到
#define SL_PLAYEVENT_HEADSTALLED // 我没用到
static void PlayerCallback(SLPlayItf caller, void *pContext, SLuint32 event)
{
// play finish
if (event == SL_PLAYEVENT_HEADATEND)
{
AudioPlayer* player = (AudioPlayer*) pContext;
AArrayListAdd(destroyList, player); // 播放完成就放入销毁队列,直接删除无效,不能再回调函数销毁当前播放对象
(*player->play)->SetPlayState(player->play, SL_PLAYSTATE_PAUSED); // 暂定播放
}
}
设置时候循环播放。
static void SetLoop(AudioPlayer* player, bool isLoop)
{
SLresult result = (*player->seek)->SetLoop(player->seek, (SLboolean) isLoop, 0, SL_TIME_UNKNOWN);
ALogA(result == SL_RESULT_SUCCESS, "Audio SetLoop failed");
// 如果是循环播放就放入循环列表,为了在暂停的时候统一暂停。否则尝试从循环列表移除。
if (isLoop)
{
AArrayListAdd(loopList, player);
}
else
{
for (int i = 0; i < loopList->size; i++)
{
AudioPlayer* p = AArrayListGet(loopList, i, AudioPlayer*);
if (player == p)
{
AArrayList->RemoveByLast(loopList, i);
break;
}
}
}
}
设置音量。
static void SetVolume(AudioPlayer* player, int volume)
{
ALogA(volume >= 0 && volume <= 100, "Audio SetVolume volume %d not in [0, 100]", volume);
SLresult result = (*player->volume)->SetVolumeLevel(player->volume, (SLmillibel) ((1.0f - volume / 100.0f) * -5000));
ALogA(result == SL_RESULT_SUCCESS, "Audio SetVolume failed");
}
声音0是最大声音,我测试了一下大概 -5000就听不见了,这里和官方demo的例子里使用的最小音量差不多。这里我把[0, -5000]映射到了[0, 100]。
设置暂停还是播放,以及判断是否播放。
static void SetPause(AudioPlayer* player)
{
// set the player's state
SLresult result = (*player->play)->SetPlayState(player->play, SL_PLAYSTATE_PAUSED);
ALogA(result == SL_RESULT_SUCCESS, "Audio SetPause failed");
}
static bool IsPlaying(AudioPlayer* player)
{
SLuint32 state;
(*player->play)->GetPlayState(player->play, &state);
if (state == SL_PLAYSTATE_PLAYING)
{
return true;
}
else if (state == SL_PLAYSTATE_PAUSED)
{
return false;
}
else
{
return false;
}
}
创建播放器对象。
{
AudioPlayer* player = AArrayListPop(cacheList, AudioPlayer*);
if (player == NULL)
{
player = (AudioPlayer*) malloc(sizeof(AudioPlayer));
}
InitPlayer(filePath, player);
return player;
}
使用过程中,遇到的坑。
- 不是循环播放,在有些机器上,播放完成的回调函数不会调用。也就是SetCallbackEventsMash(SL_PLAYEVENT_HEADATEND)不触发。
- 不要在播放回调事件里面,destroy的播放对象,没有用的。
- 音量 0 是最大,负值是越来越小。
- 我尝试了缓存播放对象,但是整个设备是共享播放对象的,文档给是32个。真机上我测试不同的机器可以用的数量不相等,[2,20]个这个范围。后来我不在尝试缓存播放对象,用完就是destroy的范围是可行的。估计底层实现上自己会做缓存。
最后就是Android studio里面添加OpenSLES的链接。
target_link_libraries(
audioLib
android
log
z
OpenSLES
)