使用OpenSL直接播放mp3
前言
通过使用OpenSL来播放一个mp3文件来学习openSL的使用方式。
设计
在android平台播放mp3方式有多种方式入使用MediaPlayer、AudioTrack、OpenSL、oboe等。根据使用MediaPlayer,AudioTrack的经验一个播放器需要有的基础功能有加载数据、开始、暂停、停止、销毁等。
我们可以设计一个播放器它具有开始播放、暂停、停止、调整进度等方式。
那么我们需要设计一个类它应该支持以下功能:
- 初始化:初始化SLEngine、构建OpenSL层的SLAudioPlayer、设置输入uri、设置输出。
- 开始播放
- 暂停播放
- 停止播放
- 获取当前的播放进度/设置当前的进度
- 释放资源
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文件的设计图。
好了,我们来看代码如何操作。
- 创建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的隐式接口。
隐式接口定义的原文:
显示接口定义的原文:
- 有了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);
- 播放器object创建成功之后,我们需要获取这个播放器的播放控制接口、控制进度的接口
(*mp3Engine->slAudioPlayer_)->GetInterface(mp3Engine->slAudioPlayer_, SL_IID_PLAY,
&mp3Engine->slPlayItf_);
result = (*mp3Engine->slAudioPlayer_)->GetInterface(mp3Engine->slAudioPlayer_,
SL_IID_SEEK, &mp3Engine->slSeekItf_);
- 获取了播放器相关接口后,后续的开始/暂停/停止等就非常的简单,只需要改变播放器的状态就好了
// 开始播放
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;
}
- 释放资源
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