0
点赞
收藏
分享

微信扫一扫

使用OpenSL直接播放mp3

三分梦_0bc3 2022-04-13 阅读 77

使用OpenSL直接播放mp3

前言

通过使用OpenSL来播放一个mp3文件来学习openSL的使用方式。

设计

在android平台播放mp3方式有多种方式入使用MediaPlayer、AudioTrack、OpenSL、oboe等。根据使用MediaPlayer,AudioTrack的经验一个播放器需要有的基础功能有加载数据、开始、暂停、停止、销毁等。
我们可以设计一个播放器它具有开始播放、暂停、停止、调整进度等方式。
那么我们需要设计一个类它应该支持以下功能:

  1. 初始化:初始化SLEngine、构建OpenSL层的SLAudioPlayer、设置输入uri、设置输出。
  2. 开始播放
  3. 暂停播放
  4. 停止播放
  5. 获取当前的播放进度/设置当前的进度
  6. 释放资源

JNI层定义

所以我们的JNI层提供的接口为:

    private native long initialize(String filePath);
    private native int start(long cPtr);
    private native int pause(long cPtr);
    private native int stop(long cPtr);
    private native int destroy(long cPtr);
    private native long getDuration(long cPtr);
    private native long getPosition(long cPtr);
    private native int seek(long cPtr, long position);

OpenSL API的使用

OpenSL的API提供的是C API,但是它的设计是面向对象的。它定义了对象(object),每个对象也会有一组接口。

  • 接口分为3类:显示的、隐式的、和动态的。显示需要在创建对象指明,如果对象不支持这个接口的feature那么创建对象将失败。隐式接口则不需要,可以直接获取。还有动态接口,动态接口可以使用SLDynamicInterfaceManagementItf来进行添加或删除
  • 每个接口都有一个ID
  • 对象在使用时需要初始化
    在这里插入图片描述
    套用官方文档的图,这张图描述了一个对象的状态转换。对象一开始是Unrealized可以由用户侧使用Realize初始化,初始化是为了获取资源。对象也有可能被系统转换为Suspended的状态,这是因为它持有的资源有可能被其他高优先级的对象偷走。用户可以通过Resume以及设置自身更高的优先级转会为Realized。用户在使用完对象后可以调用destroy销毁对象系统会进行资源释放。

在来看一张图:在这里插入图片描述
这种图中矩形AudioPlayer、Output Mix、Engine 就属于OpenSL中的对象,他们身上伸出来的小火柴就属于接口,接口的命名都是xxxxItf这种。我们操作一个对象,都是创建一个对象,然后获取它的某个接口,这种接口中会有一组方法可以供我们使用。
另外,这个图也正好可以表示我们用OpenSL播放一个本地mp3文件的设计图。

好了,我们来看代码如何操作。

  1. 创建SLEngine并获取EngineItf接口
// 创建SLEnginueObject
SLresult result = slCreateEngine(&mp3Engine->slObjectItf_,
                                     0, nullptr,
                                     0, nullptr,
                                     0);
// 初始化                                     
(*mp3Engine->slObjectItf_)->Realize(mp3Engine->slObjectItf_, SL_BOOLEAN_FALSE);

// 获取接口
(*mp3Engine->slObjectItf_)->GetInterface(mp3Engine->slObjectItf_, SL_IID_ENGINE,
                                                      &mp3Engine->slEngineItf_);                                

slCreateEnginue 这个API参数的定位为:
在这里插入图片描述
我这里第5,6个参数传的0,null,意思是不需要指明需要那些接口。但是后面的代码仍通过(*mp3Engine->slObjectItf_)->GetInterface(mp3Engine->slObjectItf_, SL_IID_ENGINE, &mp3Engine->slEngineItf_);来获取到了SLEngineItf,这也说明这个接口是engine的隐式接口。
隐式接口定义的原文:

显示接口定义的原文:

  1. 有了SLEnginueItf 我们就可以创建AudioPlayer,创建的同时我们需要为这个AudioPlayer构建输入和输出,它的输入就是本地Uri,输出OutputMix.
// 构建输入
 SLDataLocator_URI_ dataSourceUrl = {
            .locatorType = SL_DATALOCATOR_URI,
            .URI = (SLchar *) mp3Engine->uri_
    };
    SLDataFormat_MIME inputFormat = {
            .formatType = SL_DATAFORMAT_MIME,
            .mimeType = (SLchar *) "audio/mpeg",
            .containerType = SL_CONTAINERTYPE_MP3
    };
    SLDataSource slDataSource{
            .pLocator = &dataSourceUrl,
            .pFormat = &inputFormat,
    };
// 构建输入结束
// 开始构建输出
    SLObjectItf outputMix;
    (*mp3Engine->slEngineItf_)->CreateOutputMix(mp3Engine->slEngineItf_,
                                                &outputMix,
                                                0,
                                                nullptr,
                                                0);
    (*outputMix)->Realize(outputMix, SL_BOOLEAN_FALSE);

    SLDataLocator_OutputMix outputMixLocator = {
            .locatorType = SL_DATALOCATOR_OUTPUTMIX,
            .outputMix = outputMix,
    };
// 输出构建结束    
    SLDataSink slDataSink = {
            .pLocator = &outputMixLocator,
            .pFormat = nullptr
    };
// 设置必须支持的接口,SL_IID_PLAY 我们用来控制开始/暂停/停止播放等等
// SL_IID_SEEK 用来控制播放的进度
    SLInterfaceID interfaces[2]{
            SL_IID_PLAY,
            SL_IID_SEEK,
    };
    SLboolean itfResult[2]{
            SL_BOOLEAN_TRUE,
            SL_BOOLEAN_TRUE,
    };
// 创建播放器
   (*mp3Engine->slEngineItf_)->CreateAudioPlayer(mp3Engine->slEngineItf_,
                                                           &mp3Engine->slAudioPlayer_,
                                                           &slDataSource,
                                                           &slDataSink,
                                                           2,
                                                           interfaces,
                                                           itfResult);
  (*mp3Engine->slAudioPlayer_)->Realize(mp3Engine->slAudioPlayer_, SL_BOOLEAN_FALSE);
                                                             
  1. 播放器object创建成功之后,我们需要获取这个播放器的播放控制接口、控制进度的接口
 (*mp3Engine->slAudioPlayer_)->GetInterface(mp3Engine->slAudioPlayer_, SL_IID_PLAY,
                                               &mp3Engine->slPlayItf_);

    result = (*mp3Engine->slAudioPlayer_)->GetInterface(mp3Engine->slAudioPlayer_,
                                                        SL_IID_SEEK, &mp3Engine->slSeekItf_);
  1. 获取了播放器相关接口后,后续的开始/暂停/停止等就非常的简单,只需要改变播放器的状态就好了
// 开始播放
NIEXPORT jint JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_start(JNIEnv *env, jobject thiz, jlong cptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(cptr);
    auto mp3Engine = *mp3EnginePtr;
    SLuint32 state;
    (*mp3Engine.slPlayItf_)->GetPlayState(mp3Engine.slPlayItf_, &state);
    if (state == SL_PLAYSTATE_PLAYING) {
        return 0;
    }
    (*mp3Engine.slPlayItf_)->SetPlayState(mp3Engine.slPlayItf_, SL_PLAYSTATE_PLAYING);
    return 1;
}
// 暂停播放
JNIEXPORT jint JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_pause(JNIEnv *env, jobject thiz, jlong cptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(cptr);
    auto mp3Engine = *mp3EnginePtr;
    SLuint32 state;
    (*mp3Engine.slPlayItf_)->GetPlayState(mp3Engine.slPlayItf_, &state);
    if (state == SL_PLAYSTATE_STOPPED) {
        return 0;
    }
    if (state == SL_PLAYSTATE_PAUSED) {
        (*mp3Engine.slPlayItf_)->SetPlayState(mp3Engine.slPlayItf_, SL_PLAYSTATE_PLAYING);
    } else {
        (*mp3Engine.slPlayItf_)->SetPlayState(mp3Engine.slPlayItf_, SL_PLAYSTATE_PAUSED);
    }
    return 1;
}
// 停止播放
JNIEXPORT jint JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_stop(JNIEnv *env, jobject thiz, jlong cptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(cptr);
    auto mp3Engine = *mp3EnginePtr;
    SLuint32 state;
    (*mp3Engine.slPlayItf_)->GetPlayState(mp3Engine.slPlayItf_, &state);
    if (state == SL_PLAYSTATE_STOPPED) {
        return 0;
    }
    (*mp3Engine.slPlayItf_)->SetPlayState(mp3Engine.slPlayItf_, SL_PLAYSTATE_STOPPED);
    return 1;
}

// 获取播放总时长
JNIEXPORT jlong JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_getDuration(JNIEnv *env, jobject thiz, jlong c_ptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(c_ptr);
    auto mp3Engine = *mp3EnginePtr;
    SLmillisecond duration;
    (*mp3Engine.slPlayItf_)->GetDuration(mp3Engine.slPlayItf_, &duration);
    return duration;
}
// 设置播放进度
JNIEXPORT jint JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_seek(JNIEnv *env, jobject thiz, jlong c_ptr,
                                                    jlong position) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(c_ptr);
    auto mp3Engine = *mp3EnginePtr;
    (*mp3EnginePtr->slSeekItf_)->SetPosition(mp3Engine.slSeekItf_, position, SL_SEEKMODE_FAST);
    return 1;
}
// 获取当前的播放进度
JNIEXPORT jlong JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_getPosition(JNIEnv *env, jobject thiz, jlong c_ptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(c_ptr);
    auto mp3Engine = *mp3EnginePtr;
    SLmillisecond position;
    (*mp3Engine.slPlayItf_)->GetPosition(mp3Engine.slPlayItf_, &position);
    return position;
}

  1. 释放资源
JNIEXPORT jint JNICALL
Java_com_blueberry_videoplayer_SLPlayMp3JniLib_destroy(JNIEnv *env, jobject thiz, jlong cptr) {
    auto *mp3EnginePtr = reinterpret_cast<Mp3Engine *>(cptr);
    auto mp3Engine = *mp3EnginePtr;

    (*mp3EnginePtr->slPlayItf_)->SetPlayState(mp3Engine.slPlayItf_, SL_PLAYSTATE_STOPPED);
    (*mp3Engine.slObjectItf_)->Destroy(mp3Engine.slObjectItf_);
    delete mp3EnginePtr;
    return 1;
}

参考

http://supercurio.project-voodoo.org/ndk-docs/docs/opensles/
https://www.khronos.org/registry/OpenSL-ES/specs/OpenSL_ES_Specification_1.1.pdf

举报

相关推荐

0 条评论