Android studio使用ndk native c调用OpenSLES播放声音

本文介绍一下内容:

  • 介绍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");


回调函数调用的时候,会传入一个事件类型,是由SetCallbackEventsMash来设置的。
#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
)


  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值