使用OpenSL直接播放mp3

使用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的隐式接口。
隐式接口定义的原文:

An object’s type determines the set of interfaces that will always exist, regardless of whether the application requests them or not. These interfaces are called implicit

显示接口定义的原文:

Every object type also defines a set of interfaces that are available on objects of this type, but will not be exposed by an object unless explicitly requested by the application during the object’s creation. These explicitly requested interfaces are called explicit interfaces

  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

猜你喜欢

转载自blog.csdn.net/a992036795/article/details/124077583