OpenSL ES 是可以让你使用C或者C++来实现高性能,低延迟音频API。ES代表Embedded Systems,在嵌入式系统中单独设计。使用OpenSL ES可以省去native层到java层的数据拷贝的开销,更加高效。本文分享如何利用OpenSL ES 来渲染 ffmpeg 解码的音频数据。
引入OpenSL ES的库
要使用OpenSL ES我们需要先在CMakeLists.txt中引入库。
target_link_libraries( # Specifies the target library.
OpenSLES )
引入相关的头文件:
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
使用OpenSL ES渲染音频
通过一张图我们看一下OpengSL ES播放音频的流程:
- 创建引擎对象,引擎对象是OpenSL ES提供API的唯一入口。
创建引擎主要调用三个全局的函数,首先通过slCreateEngine
创建引擎对象,然后使用Realize
初始化引擎对象,最好通过GetInterface
获取到引擎对象的接口即可。 - 创建混音器
创建混音器也叫创建输出设备。首先通过CreateOutputMix
创建一个混音器对象,然后通过Realize
初始化混音器对象,
最后使用SLDataLocator_OutputMix
和SLDataSink
配置混音器。
SLEngineItf engineEngine = NULL;
SLObjectItf outputMixObject = NULL;
(*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
(*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&outputMix, NULL};
- 创建播放器
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE}; const SLboolean req[1] = {SL_BOOLEAN_TRUE};
(*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource, &audioSnk, 1, ids, req);
//初始化播放器
(*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);
// 得到接口后调用 获取Player接口
(*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);
- 播放和缓冲队列
// 注册回调缓冲区 获取缓冲队列接口
(*pcmPlayerObject)- >GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);
(*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcmBufferCallBack, NULL);
(*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);
(*pcmBufferQueue)->Enqueue(pcmBufferQueue, "", 1);
搭配ffmpeg渲染音频实例
在 AudioTrack播放ffmpeg解码的PCM音频数据 一文中我们实现了音频的解码以及使用AudioTrack播放音频。下面我们继续使用这篇文章中音频的解码代码,将AudioTrack播放音频部分修改成OpenSL ES播放音频。
首先准备好OpenSL ES播放器:
//1 创建引擎
static SLObjectItf engineSL = NULL;
SLEngineItf CreateSL()
{
SLresult re;
SLEngineItf en;
re = slCreateEngine(&engineSL,0,0,0,0,0);
if(re != SL_RESULT_SUCCESS) return NULL;
re = (*engineSL)->Realize(engineSL,SL_BOOLEAN_FALSE);
if(re != SL_RESULT_SUCCESS) return NULL;
re = (*engineSL)->GetInterface(engineSL,SL_IID_ENGINE,&en);
if(re != SL_RESULT_SUCCESS) return NULL;
return en;
}
const char *path = NULL;
extern "C"
JNIEXPORT jint JNICALL
Java_com_flyer_ffmpeg_FFmpegUtils_playAudioByOpenSLES(JNIEnv *env, jclass clazz,
jstring audio_path) {
path = env->GetStringUTFChars(audio_path, 0);
// 初始化OpenSL ES
//1 创建引擎
SLEngineItf eng = CreateSL();
if(eng){
LOGE("CreateSL success! ");
}else{
LOGE("CreateSL failed! ");
}
//2 创建混音器
SLObjectItf mix = NULL;
SLresult re = 0;
re = (*eng)->CreateOutputMix(eng,&mix,0,0,0);
if(re !=SL_RESULT_SUCCESS )
{
LOGE("SL_RESULT_SUCCESS failed!");
}
re = (*mix)->Realize(mix,SL_BOOLEAN_FALSE);
if(re !=SL_RESULT_SUCCESS )
{
LOGE("(*mix)->Realize failed!");
}
SLDataLocator_OutputMix outmix = {SL_DATALOCATOR_OUTPUTMIX,mix};
SLDataSink audioSink= {&outmix,0};
//3 配置音频信息
//缓冲队列
SLDataLocator_AndroidSimpleBufferQueue que = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,10};
//音频格式
SLDataFormat_PCM pcm = {
SL_DATAFORMAT_PCM,
2,// 声道数
SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT|SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN //字节序,小端
};
SLDataSource ds = {&que,&pcm};
//4 创建播放器
SLObjectItf player = NULL;
SLPlayItf iplayer = NULL;
SLAndroidSimpleBufferQueueItf pcmQue = NULL;
const SLInterfaceID ids[] = {SL_IID_BUFFERQUEUE};
const SLboolean req[] = {SL_BOOLEAN_TRUE};
re = (*eng)->CreateAudioPlayer(eng,&player,&ds,&audioSink,sizeof(ids)/sizeof(SLInterfaceID),ids,req);
if(re !=SL_RESULT_SUCCESS )
{
LOGE("CreateAudioPlayer failed!");
} else{
LOGE("CreateAudioPlayer success!");
}
(*player)->Realize(player,SL_BOOLEAN_FALSE);
//获取player接口
re = (*player)->GetInterface(player,SL_IID_PLAY,&iplayer);
if(re !=SL_RESULT_SUCCESS )
{
LOGE("GetInterface SL_IID_PLAY failed!");
}
re = (*player)->GetInterface(player,SL_IID_BUFFERQUEUE,&pcmQue);
if(re !=SL_RESULT_SUCCESS )
{
LOGE("GetInterface SL_IID_BUFFERQUEUE failed!");
}
//设置回调函数,播放队列空调用
(*pcmQue)->RegisterCallback(pcmQue,PcmCall,0);
//设置为播放状态
(*iplayer)->SetPlayState(iplayer,SL_PLAYSTATE_PLAYING);
//启动队列回调
(*pcmQue)->Enqueue(pcmQue,"",1);
return 0;
}
播放回调函数。在实际项目中我们的播放回调应该使用生产者消费者模式的,但是在学习过程中我们使用流水式的代码可能学者们更容易看懂:
void PcmCall(SLAndroidSimpleBufferQueueItf bf,void *contex)
{
LOGE("PcmCall");
AVFormatContext *fmt_ctx;
// 初始化格式化上下文
fmt_ctx = avformat_alloc_context();
// 使用ffmpeg打开文件
int re = avformat_open_input(&fmt_ctx, path, nullptr, nullptr);
if (re != 0) {
LOGE("打开文件失败:%s", av_err2str(re));
return;
}
//探测流索引
re = avformat_find_stream_info(fmt_ctx, nullptr);
if (re < 0) {
LOGE("索引探测失败:%s", av_err2str(re));
return;
}
//寻找视频流索引
int audio_idx = av_find_best_stream(
fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
if (audio_idx == -1) {
LOGE("获取音频流索引失败");
return;
}
//解码器参数
AVCodecParameters *c_par;
//解码器上下文
AVCodecContext *cc_ctx;
//声明一个解码器
const AVCodec *codec;
c_par = fmt_ctx->streams[audio_idx]->codecpar;
//通过id查找解码器
codec = avcodec_find_decoder(c_par->codec_id);
if (!codec) {
LOGE("查找解码器失败");
return;
}
//用参数c_par实例化编解码器上下文,,并打开编解码器
cc_ctx = avcodec_alloc_context3(codec);
// 关联解码器上下文
re = avcodec_parameters_to_context(cc_ctx, c_par);
if (re < 0) {
LOGE("解码器上下文关联失败:%s", av_err2str(re));
return;
}
//打开解码器
re = avcodec_open2(cc_ctx, codec, nullptr);
if (re != 0) {
LOGE("打开解码器失败:%s", av_err2str(re));
return;
}
//数据包
AVPacket *pkt;
//数据帧
AVFrame *frame;
//初始化
pkt = av_packet_alloc();
frame = av_frame_alloc();
//音频重采样
int dataSize = av_samples_get_buffer_size(NULL, av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO) , cc_ctx->frame_size,AV_SAMPLE_FMT_S16, 0);
uint8_t *resampleOutBuffer = (uint8_t *) malloc(dataSize);
//音频重采样上下文初始化
SwrContext *actx = swr_alloc();
actx = swr_alloc_set_opts(actx,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,44100,
cc_ctx->channels,
cc_ctx->sample_fmt,cc_ctx->sample_rate,
0,0 );
re = swr_init(actx);
if(re != 0)
{
LOGE("swr_init failed:%s",av_err2str(re));
return;
}
while (av_read_frame(fmt_ctx, pkt) >= 0) {//持续读帧
// 只解码音频流
if (pkt->stream_index == audio_idx) {
//发送数据包到解码器
avcodec_send_packet(cc_ctx, pkt);
//清理
av_packet_unref(pkt);
//这里为什么要使用一个for循环呢?
// 因为avcodec_send_packet和avcodec_receive_frame并不是一对一的关系的
//一个avcodec_send_packet可能会出发多个avcodec_receive_frame
for (;;) {
// 接受解码的数据
re = avcodec_receive_frame(cc_ctx, frame);
if (re != 0) {
break;
} else {
//音频重采样
int len = swr_convert(actx,&resampleOutBuffer,
frame->nb_samples,
(const uint8_t**)frame->data,
frame->nb_samples);
(*bf)->Enqueue(bf,resampleOutBuffer,len);
}
}
}
}
//关闭环境
avcodec_free_context(&cc_ctx);
// 释放资源
av_frame_free(&frame);
av_packet_free(&pkt);
avformat_free_context(fmt_ctx);
LOGE("音频播放完毕");
}
结束
最后如果你对音视频开发感兴趣可扫码关注,笔者在各个知识点学习完毕之后也会使用ffmepg从零开始编写一个多媒体播放器,包括本地播放及网络流播放等等。欢迎关注,后续我们共同探讨,共同进步。
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/19861.html