NDK之OpenSL ES音频解码

前面学习了使用AudioTrack对音频进行解码,那么这次换一个,来看一看如何使用OpenSL ES对音频进行解码

OpenSL ES简介

官网关于OpenSL SE是这么解释的

OpenSL ES™ is a royalty-free, cross-platform, hardware-accelerated audio API tuned for embedded systems. It provides a standardized, high-performance, low-latency method to access audio functionality for developers of native applications on embedded mobile multimedia devices, enabling straightforward cross-platform deployment of hardware and software audio capabilities, reducing implementation effort, and promoting the market for advanced audio.

用一句话来说就是:OpenSL ES是一个嵌入式跨平台免费的音频处理库
OpenSL ES重点需要知道ObjectInterface

OpenSL ES流程

  1. 打开文件
  2. 创建OpenSL ES引擎
  3. 获取引擎接口
  4. 创建输出混音器
  5. 创建缓冲区保存读取到的音频数据
  6. 创建带有缓冲区队列的音频播放器
  7. 获得缓冲区队列接口
  8. 注册音频播放器回调函数
  9. SetPlayState启动播放器
  10. 让第一个缓冲区入队

代码实现

测试播放wav音频文件,由于Android本身是不支持wav播放的,那么就需要额外的解码库对其进行支持
这里使用的是transcode,将其引入到项目中
transcode下载:transcode下载地址
下载解压以后,由于我们不会用到全部的功能,所以只需要需要的模块即可
查看avilib目录下的文件发现,在需要支持的wav播放的文件是wavlib.c,在查看过程中发现,使用wavlib.c的话,需要platform_posix.h,因此在引入头文件的时候只需要对应着引入即可,查找发现需要的文件如下:
wavlib.h,platform_posix.h,wavlib.c`platform_posix.c,复制这四个文件,将其放入jni目录,修改Android.mk`文件,指定包含文件和源代码位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := wavlib
LOCAL_SRC_FILES := wavlib.c platform_posix.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/inc
include $(BUILD_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := OpenSLESTest
LOCAL_SRC_FILES := OpenSLESTest.cpp
LOCAL_LDLIBS := -lOpenSLES -llog
LOCAL_SHARED_LIBRARIES := wavlib
include $(BUILD_SHARED_LIBRARY)

这里我编译生成动态库,也可以将wavlib生成静态库,然后编译到一个动态库中
创建一个buf_queen队列,用来当作容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
extern "C" {
#include "inc/wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))

//创建音频播放对象
void CreateBufferQueueAudioPlayer(
WAV wav,
SLEngineItf engineEngine,
SLObjectItf outputMixObject,
SLObjectItf &audioPlayerObject){

// Android针对数据源的简单缓冲区队列定位器
SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // 定位器类型
1 // 缓冲区数
};

// PCM数据源格式
SLDataFormat_PCM dataSourceFormat = {
SL_DATAFORMAT_PCM, // 格式类型
wav_get_channels(wav), // 通道数
wav_get_rate(wav) * 1000, // 毫赫兹/秒的样本数
wav_get_bits(wav), // 每个样本的位数
wav_get_bits(wav), // 容器大小
SL_SPEAKER_FRONT_CENTER, // 通道屏蔽
SL_BYTEORDER_LITTLEENDIAN // 字节顺序
};

// 数据源是含有PCM格式的简单缓冲区队列
SLDataSource dataSource = {
&dataSourceLocator, // 数据定位器
&dataSourceFormat // 数据格式
};

// 针对数据接收器的输出混合定位器
SLDataLocator_OutputMix dataSinkLocator = {
SL_DATALOCATOR_OUTPUTMIX, // 定位器类型
outputMixObject // 输出混合
};

// 数据定位器是一个输出混合
SLDataSink dataSink = {
&dataSinkLocator, // 定位器
0 // 格式
};

// 需要的接口
SLInterfaceID interfaceIds[] = {
SL_IID_BUFFERQUEUE
};

// 需要的接口,如果所需要的接口不要用,请求将失败
SLboolean requiredInterfaces[] = {
SL_BOOLEAN_TRUE // for SL_IID_BUFFERQUEUE
};

// 创建音频播放器对象
SLresult result = (*engineEngine)->CreateAudioPlayer(
engineEngine,
&audioPlayerObject,
&dataSource,
&dataSink,
ARRAY_LEN(interfaceIds),
interfaceIds,
requiredInterfaces);
}

按照OpenSL ES步骤实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "com_cj5785_openslestest_OpenSLESAudio.h"

extern "C" {
#include "inc/wavlib.h"
}
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#include "buf_queen.cpp"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"cj5785",FORMAT,##__VA_ARGS__);


#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))


WAV wav; //wav文件指针
SLObjectItf engineObject; //引擎对象
SLEngineItf engineInterface; //引擎接口
SLObjectItf outputMixObject; //混音器
SLObjectItf audioPlayerObject; //播放器对象
SLAndroidSimpleBufferQueueItf andioPlayerBufferQueueItf; //缓冲器队列接口
SLPlayItf audioPlayInterface; //播放接口


unsigned char *buffer; //缓冲区
size_t bufferSize; //缓冲区大小

//上下文
struct PlayerContext{
WAV wav;
unsigned char *buffer;
size_t bufferSize;

PlayerContext(WAV wav,
unsigned char *buffer,
size_t bufferSize){
this->wav = wav;
this->buffer = buffer;
this->bufferSize = bufferSize;
}
};

//打开文件
WAV OpenWaveFile(JNIEnv *env,jstring jFileName){
const char *cFileName = env->GetStringUTFChars(jFileName,JNI_FALSE);
WAVError err;
WAV wav = wav_open(cFileName,WAV_READ,&err);

LOGI("%d",wav_get_bitrate(wav));
env->ReleaseStringUTFChars(jFileName,cFileName);
if(wav == 0){
LOGE("%s",wav_strerror(err));
}
return wav;
}

//关闭文件
void CloseWaveFile(WAV wav){
wav_close(wav);
}

//实现对象
void RealizeObject(SLObjectItf object){
//非异步(阻塞)
(*object)->Realize(object,SL_BOOLEAN_FALSE);
}

//回调函数
void PlayerCallBack(SLAndroidSimpleBufferQueueItf andioPlayerBufferQueue,void *context){
PlayerContext* ctx = (PlayerContext*)context;
//读取数据
ssize_t readSize = wav_read_data(ctx->wav,ctx->buffer,ctx->bufferSize);
if(0 < readSize){
(*andioPlayerBufferQueue)->Enqueue(andioPlayerBufferQueue,ctx->buffer,readSize);
}else{
//destroy context
CloseWaveFile(ctx->wav); //关闭文件
delete ctx->buffer; //释放缓存
}
}

JNIEXPORT void JNICALL Java_com_cj5785_openslestest_OpenSLESAudio_play
(JNIEnv *env, jclass jthiz, jstring jFileName){
//1.打开文件
WAV wav = OpenWaveFile(env,jFileName);

//2.创建OpenSL ES引擎
//OpenSL ES在Android平台下默认是线程安全的,这样设置是为了为了兼容其他平台
SLEngineOption options[] = {
{(SLuint32)SL_ENGINEOPTION_THREADSAFE, (SLuint32)SL_BOOLEAN_TRUE}
};
slCreateEngine(&engineObject,ARRAY_LEN(engineObject),options,0,0,0); //没有接口

//实例化对象
//对象创建之后,处于未实例化状态,对象虽然存在但未分配任何资源,使用前先实例化(使用完之后destroy)
RealizeObject(engineObject);

//3.获取引擎接口
(*engineObject)->GetInterface(engineObject,SL_IID_ENGINE,&engineInterface);

//4.创建输出混音器
(*engineInterface)->CreateOutputMix(engineInterface,&outputMixObject,0,0,0); //没有接口

//实例化混音器
RealizeObject(outputMixObject);

//5.创建缓冲区保存读取到的音频数据库
//缓冲区的大小
bufferSize = wav_get_channels(wav) * wav_get_rate(wav) * wav_get_bits(wav);
buffer = new unsigned char[bufferSize];

//6.创建带有缓冲区队列的音频播放器
CreateBufferQueueAudioPlayer(wav,engineInterface,outputMixObject,audioPlayerObject);

//实例化音频播放器
RealizeObject(audioPlayerObject);

//7.获得缓冲区队列接口Buffer Queue Interface
//通过缓冲区队列接口对缓冲区进行排序播放
(*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_BUFFERQUEUE,&andioPlayerBufferQueueItf);

//8.注册音频播放器回调函数
//当播放器完成对前一个缓冲区队列的播放时,回调函数会被调用,然后我们又继续读取音频数据,直到结束
//上下文,包裹参数方便再回调函数中使用
PlayerContext *ctx = new PlayerContext(wav,buffer,bufferSize);
(*andioPlayerBufferQueueItf)->RegisterCallback(andioPlayerBufferQueueItf,PlayerCallBack,ctx);

//9.获取Play Interface通过对SetPlayState函数来启动播放音乐
//一旦播放器被设置为播放状态,该音频播放器开始等待缓冲区排队就绪
(*audioPlayerObject)->GetInterface(audioPlayerObject,SL_IID_PLAY,&audioPlayInterface);
//设置播放状态
(*audioPlayInterface)->SetPlayState(audioPlayInterface,SL_PLAYSTATE_PLAYING);

//10.开始,让第一个缓冲区入队
PlayerCallBack(andioPlayerBufferQueueItf,ctx);


//关闭文件
//CloseWaveFile(wav);
}

Donate comment here