Android OpenSLES error “too many objects” 和 “Error after prepare: 1” 问题的解决方式

在使用NDK直接调用OpenSLES实现音频的时候,很容易出现以下两种错误:

(1) E/libOpenSLES(25131): Too many objects
(2) E/libOpenSLES: Error after prepare: 1

第一个,SLPlayItf对象没有及时销毁,不同的平台SLPlayItf同时支持的数量不同,大约是2-10左右,所以音频播放完成之后,需要及时销毁。否则,出现(1)的错误,就会播放不出来声音,到一定程度就会导致崩溃。

第二个, 是因为播放文件的格式OpenSLES不支持,或是文件有问题,无法正确播放。并且出现这种问题后,会让SLPlayItf的回调函数无法正确执行,最终导致SLPlayItf越来越多,从而出现(1)的情况和结局。

那么,解决(1)的问题,就是在音频播放完成后及时销毁,我们可以使用回调来实现,但是(2)的问题又会导致回调不正确。所以,如果要同时解决,以上两个问题,我们就需要一切技巧。

其思路就是:

  • 音频正确回调,那么在回调函数里销毁SLPlayItf
  • 通过定时器检测,音频不能正确回调,那么即刻销毁SLPlayItf

解决方法实现:

struct AudioPlayer
{SLObjectItf object;SLPlayItf   play;SLSeekItf   seek;SLVolumeItf volume;const char* filePath;int         waitCallbackCount;
};// player is AudioPlayer(*player->play)->RegisterCallback(player->play, PlayerCallback, player);
(*player->play)->SetCallbackEventsMask(player->play,  SL_PLAYEVENT_HEADATEND | SL_PLAYEVENT_HEADATNEWPOS);

首先,我们需要注册,SLPlayItf的回调函数,但又两个参数。

  • SL_PLAYEVENT_HEADATEND 表示音频播放结束回调,但如果发生错误(2)就会不回调。
  • SL_PLAYEVENT_HEADATNEWPOS 表示音频播放到每一个新位置的时候回调,但如果发生错误(2)就会不回调。
static void PlayerCallback(SLPlayItf caller, void *pContext, SLuint32 event)
{AudioPlayer* player = (AudioPlayer*) pContext;if (event == SL_PLAYEVENT_HEADATEND){// play finishAArrayList_Add(destroyList, player);(*player->play)->SetPlayState(player->play, SL_PLAYSTATE_PAUSED);player->waitCallbackCount = AudioPlayer_WaitOver;}else if (event == SL_PLAYEVENT_HEADATNEWPOS){// play normalplayer->waitCallbackCount = AudioPlayer_WaitOver;// remove SL_PLAYEVENT_HEADATNEWPOS for reduce the number of callback(*player->play)->SetCallbackEventsMask(player->play,  SL_PLAYEVENT_HEADATEND);}
}

其次,回调函数中:

  • SL_PLAYEVENT_HEADATEND 回调时销毁SLPlayItf
  • SL_PLAYEVENT_HEADATNEWPOS 回调是一种优化,一点进入说明音频是可以正常播放的,此时就移除SL_PLAYEVENT_HEADATNEWPOS 回调。
  • waitCallbackCount 计数器,是超时检测。如果超过这个时间,依然没有进入回调,就说明音频播放有问题。
static void Update(float deltaSeconds)
{while (destroyList->size > 0){AudioPlayer* player = AArrayList_Pop(destroyList, AudioPlayer*);(*player->object)->Destroy(player->object);AArrayList_Add(cacheList, player);}for (int i = testErrorList->size - 1; i > -1 ; --i){AudioPlayer* player = AArrayList_Get(testErrorList, i, AudioPlayer*);if (player->waitCallbackCount == AudioPlayer_WaitOver){AArrayList->RemoveByLast(testErrorList, i);}else if (player->waitCallbackCount >= AudioPlayer_WaitMax){// player callback not called, maybe E/libOpenSLES: Error after prepare: 1AArrayList->RemoveByLast(testErrorList, i);(*player->object)->Destroy(player->object);AArrayList_Add(cacheList, player);ALog_E("AAudio player = %s not callback normal", player->filePath);}else{++player->waitCallbackCount;}}
}

最后,Update 每帧调用的检测,有以下功能:

  • 如果SLPlayItf销毁队列不为空,那么就销毁其中的SLPlayItf
  • 每个SLPlayItf生成的时候,都会加入testErrorList 列表,然后进行回调超时检测,并推进超时计数器,如果超时成立,那么就直接销毁testErrorList。

总结

以上就是,解决OpenSLES两个常见问题的思路和方法,实践中是有效的。另外,OpenSLES的实现可以参看另一篇文章有详细介绍:Android studio使用ndk native c调用OpenSLES播放声音。