OpenSL ES(Open Sound Library for Embedded Systems
),即嵌入式音频加速标准,是一个无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API库。它为嵌入移动多媒体设备上的本地应用程序开发者提供了标准化、高性能、低相应时间的音频开发方案,并实现软/硬件音频性能的直接跨平台部署,被广泛应用于3D音效、音频播放、音频录制以及音乐体验增强(低音增强和环境混响)
等方面。对于Android平台而言,我们可以使用OpenSL ES库直接在native层处理音频数据,比如录制音频、播放音频等。OpenSL ES嵌入式设备中部署的软/硬件层次结构,如下图所示:
1.1 OpenSL ES核心API讲解
虽然OpenSL ES是基于C语言开发,但是它的实现思想是基于面向对象的,与FFmpeg框架提供一系列的函数接口不同,OpenSL ES是以Interface的方式来提供API,即除了slCreateEngine这个函数用来创建声音引擎对象接口之外,其他所有的操作都是通过调用接口的成员函数完成的,这与JNI非常相似。比如我们调用OpenSL ES的API是这样的:
SLObjectItf pEngineObject = NULL;
(*pEngineObject)->Realize(pEngineObject, SL_BOOLEAN_FALSE);
【腾讯文档】FFmpegWebRTCRTMPRTSPHLSRTP播放器-音视频流媒体高级开发-资料领取
https://docs.qq.com/doc/DYU5ORlBOdkpCUkNxhttps://docs.qq.com/doc/DYU5ORlBOdkpCUkNx
1.1.1 对象(Object)与接口(Interface)
Object和Interface是OpenSL ES库的两个非常重要的概念,OpenSL ES的整个框架就是基于这两个概念构成的,后续对该库的使用也是基于此来实现。具体关系如下: 1.每个Object可能存在一个或者多个Interface,而每个Interface封装了相关的功能函数
。比如当我们获取一个Audio Player对象后,可以通过该对象得到音频播放Interface、音量Interface、缓存队列Interface,然后调用这些Interface的功能函数实现音频播放、音量调节等功能;
// OpenSL ES引擎Interface
SLEngineItf pEngineItf = NULL;
...
SLObjectItf pPlayerObject = NULL; // Audio Player对象
SLPlayItf pPlayerItf = NULL; // 播放接口
SLVolumeItf pVolumeItf = NULL; // 音量接口
SLAndroidSimpleBufferQueueItf pBufferItf = NULL; // 缓存队列接口
(*pEngineItf)->CreateAudioPlayer(pEngineItf,&pPlayerObject,..);
(*pPlayerObject)->Realize(pPlayerObject,SL_BOOLEAN_FALSE);
(*pPlayerObject)->GetInterface(pPlayerObject, SL_IID_PLAY,&pPlayerItf);
(*pPlayerObject)->GetInterface(pPlayerObject, SL_IID_VOLUME,&pVolumeItf);
(*pPlayerObject)->GetInterface(pPlayerObject,SL_IID_BUFFERQUEUE,&pBufferItf);
2.每个Object对象提供了一些最基础的"管理"操作,比如它的Realize、Destory函数用于分配、释放资源,Resume函数用于结束SL_OBJECT_STATE_SUSPENED状态等等。如果系统使用该对象支持的功能函数,就需要通过该对象的GetInterface函数获取相应的Interface接口,然后通过该Interface接口来访问功能函数。下面以调节音量为例:
// OpenSL ES引擎Interface
SLEngineItf pEngineItf = NULL;
...
SLObjectItf pPlayerObject = NULL;
// 首先,创建Audio Player对象
(*pEngineItf)->CreateAudioPlayer(pEngineItf,&pPlayerObject,..);
// 其次,初始化Audio Player对象,即分配资源
(*pPlayerObject)->Realize(pPlayerObject,SL_BOOLEAN_FALSE);
// 第三,获取Audio Player对象的音量(Volume)Interface
(*pPlayerObject)->GetInterface(pPlayerObject, SL_IID_VOLUME,&pVolumeItf);
// 最后,调用Volume Interface的调节音量功能函数
(*pVolumeItf)->SetVolumeLevel(&pVolumeItf,level);
注意:由于OpenSL ES库是跨平台的,但是并非所有平台都实现了某个对象(Object)定义的所有接口(Interface),因此在使用的过程中,最好还是对获取的Interface作一些选择和判断,以免出现预料不到的错误。
1.1.2 OpenSL ES的状态机制
OpenSL ES有个比较重要的概念-状态机制,即对于任何OpenSL ES的对象,在被创建成功后都会进入SL_OBJECT_STATE_UNREALIZE
状态,此时系统不会为该对象分配任何资源;当调用对象的Realize()成员函数后,该对象就会进入SL_OBJECT_STATE_REALIZE
状态,此时对象的各个功能和资源才能正常被访问;当然,当突然出现一些系统事件,比如系统错误或者Audio设备被其他应用抢占,该对象就会进入SL_OBJECT_STATE_SUSPENED
状态,如果我们希望恢复正常使用,就需要调用对象的Resume函数;最后,我们可以调用对象的Destory函数,来释放资源,此时对象的状态会回到SL_OBJECT_STATE_UNREALIZE状态。OpenSL ES状态机制转化流程如下图所示:
1.1.3 OpenSL ES重要接口
(1) SLObjectItf:对象接口,是Sound Library Object Interface的缩写,表示一个泛指对象,就像Java中的Object一样,OpenSL ES中的所有对象均由它来表示,但具体代表是哪个对象,就由SLEngineItf的相关函数决定(注:SLEngineItf是Engine Object的接口)。创建对象:
// 创建OpenSL SL引擎对象(Engine Object)
SLObjectItf pEngineObject = NULL;
slCreateEngine(&pEngineObject, 0, NULL, 0, NULL, NULL);
// 创建混音器对象(OutputMix Object)
SLObjectItf pOutputMixObject = NULL;
(*pEngineItf)->CreateOutputMix(pEngineItf,&pOutputMixObject,...);
// 创建播放器对象(Player Object)
SLObjectItf pPlayerObject = NULL;
(*pEngineItf)->CreateAudioPlayer(pEngineItf,&pPlayerObject,...);
如果是销毁某个对象,调用它的Destory成员函数即可。
if (pEngineObject) {
(*pEngineObject)->Destroy(pEngineObject);
}
if (pOutputMixObject) {
(*pOutputMixObject)->Destroy(pOutputMixObject);
}
if (pPlayerObject) {
(*pPlayerObject)->Destroy(pPlayerObject);
}
重难讲一下,Engine Object是OpenSL ES API的入口点
,是OpenSL ES中最核心的对象,用于管理Audio Engine生命周期和创建OpenSL ES中所有其他的对象。Engine Object由slCreateEngine函数创建,创建的结果是得到Engine Object的一个接口-SLEngineItf
,在这个接口的结构体中封装了创建其他各种对象的函数。SLObjectItf
结构体定义如下,位于.../SLES/OpenSLES.h
头文件中:
struct SLObjectItf_;
typedef const struct SLObjectItf_ * const * SLObjectItf;
struct SLObjectItf_ {
// 初始化对象,即为对象分配资源
SLresult (*Realize) (
SLObjectItf self,
SLboolean async
);
// 恢复对象正常使用,即结束SL_OBJECT_STATE_SUSPENED状态
SLresult (*Resume) (
SLObjectItf self,
SLboolean async
);
// 获取对象的状态
SLresult (*GetState) (
SLObjectItf self,
SLuint32 * pState
);
// 获取ID为iid的接口
SLresult (*GetInterface) (
SLObjectItf self,
const SLInterfaceID iid,
void * pInterface
);
// 注册回调接口
SLresult (*RegisterCallback) (
SLObjectItf self,
slObjectCallback callback,
void * pContext
);
void (*AbortAsyncOperation) (
SLObjectItf self
);
// 销毁当前对象,释放资源
void (*Destroy) (
SLObjectItf self
);
// 设置优先级
SLresult (*SetPriority) (
SLObjectItf self,
SLint32 priority,
SLboolean preemptable
);
SLresult (*GetPriority) (
SLObjectItf self,
SLint32 *pPriority,
SLboolean *pPreemptable
);
SLresult (*SetLossOfControlInterfaces) (
SLObjectItf self,
SLint16 numInterfaces,
SLInterfaceID * pInterfaceIDs,
SLboolean enabled
);
};
注:除了SLEngineItf接口之外,Engine Object还提供了SLEngineCapabilitiesItf和SLAudioIODeviceCapabilitiesItf设备属性信息查询接口等。 (2) SLEngineItf:引擎接口,Sound Library Engine Interface的缩写。是Engine Object提供的管理接口,用于创建所有其他的Object对象。SLEngineItf
结构体定义如下,位于.../SLES/OpenSLES.h
头文件中:
extern SL_API const SLInterfaceID SL_IID_ENGINE;
struct SLEngineItf_;
typedef const struct SLEngineItf_ * const * SLEngineItf;
struct SLEngineItf_ {
SLresult (*CreateLEDDevice) (
SLEngineItf self,
SLObjectItf * pDevice,
SLuint32 deviceID,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*CreateVibraDevice) (
SLEngineItf self,
SLObjectItf * pDevice,
SLuint32 deviceID,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
// 创建播放器对象
SLresult (*CreateAudioPlayer) (
SLEngineItf self, // Enghine Interface
SLObjectItf * pPlayer, // Player Object
SLDataSource *pAudioSrc, // 音频数据来源,可为url、assets、pcm
SLDataSink *pAudioSnk, // 音频输出
SLuint32 numInterfaces, // 要获取的PlayerObject的接口数量
const SLInterfaceID * pInterfaceIds, //PlayerObject需要支持的接口ID
// 指定每个支持的接口是可选的标志位数组,如果要求支持的接口没有实现,创建对象
// 时会失败并返回错误码SL_RESULT_FEATURE_UNSUPPORTED
const SLboolean * pInterfaceRequired
);
// 创建音频录制对象
SLresult (*CreateAudioRecorder) (
SLEngineItf self,
SLObjectItf * pRecorder,
SLDataSource *pAudioSrc,
SLDataSink *pAudioSnk,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*CreateMidiPlayer) (
SLEngineItf self,
SLObjectItf * pPlayer,
SLDataSource *pMIDISrc,
SLDataSource *pBankSrc,
SLDataSink *pAudioOutput,
SLDataSink *pVibra,
SLDataSink *pLEDArray,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*CreateListener) (
SLEngineItf self,
SLObjectItf * pListener,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*Create3DGroup) (
SLEngineItf self,
SLObjectItf * pGroup,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
// 创建混音输出(mix output)对象
SLresult (*CreateOutputMix) (
SLEngineItf self, // Engine Interface
SLObjectItf * pMix,// OutputMix Object
SLuint32 numInterfaces, // 要获取的OutputMix Object接口数量
const SLInterfaceID * pInterfaceIds, // 对象需要支持的接口ID数组
// 指定每个支持的接口是可选的标志位数组,如果要求支持的接口没有实现,创建对象
// 时会失败并返回错误码SL_RESULT_FEATURE_UNSUPPORTED
const SLboolean * pInterfaceRequired
);
SLresult (*CreateMetadataExtractor) (
SLEngineItf self,
SLObjectItf * pMetadataExtractor,
SLDataSource * pDataSource,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*CreateExtensionObject) (
SLEngineItf self,
SLObjectItf * pObject,
void * pParameters,
SLuint32 objectID,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
SLresult (*QueryNumSupportedInterfaces) (
SLEngineItf self,
SLuint32 objectID,
SLuint32 * pNumSupportedInterfaces
);
SLresult (*QuerySupportedInterfaces) (
SLEngineItf self,
SLuint32 objectID,
SLuint32 index,
SLInterfaceID * pInterfaceId
);
SLresult (*QueryNumSupportedExtensions) (
SLEngineItf self,
SLuint32 * pNumExtensions
);
SLresult (*QuerySupportedExtension) (
SLEngineItf self,
SLuint32 index,
SLchar * pExtensionName,
SLint16 * pNameLength
);
SLresult (*IsExtensionSupported) (
SLEngineItf self,
const SLchar * pExtensionName,
SLboolean * pSupported
);
};
(3) SLEnvironmentalReverbItf:环境混响接口。可以理解为指定音频混响输出的效果,比如房间效果、剧院效果、礼堂效果等等...。SLEnvironmentalReverbItf
结构体定义如下,位于.../SLES/OpenSLES.h
头文件中:
// 接口ID
extern SL_API const SLInterfaceID SL_IID_ENVIRONMENTALREVERB;
struct SLEnvironmentalReverbItf_ {
SLresult (*SetRoomLevel) (
SLEnvironmentalReverbItf self,
SLmillibel room
);
SLresult (*GetRoomLevel) (
SLEnvironmentalReverbItf self,
SLmillibel *pRoom
);
...
// 设置环境混响效果
SLresult (*SetEnvironmentalReverbProperties) (
SLEnvironmentalReverbItf self,
const SLEnvironmentalReverbSettings *pProperties
);
SLresult (*GetEnvironmentalReverbProperties) (
SLEnvironmentalReverbItf self,
SLEnvironmentalReverbSettings *pProperties
);
};
// OpenSL ES提供的环境混响效果
// 洞穴效果
#define SL_I3DL2_ENVIRONMENT_PRESET_CAVE \
{ -1000, 0, 2910, 1300, -602, 15, -302, 22, 1000,1000 }
#define SL_I3DL2_ENVIRONMENT_PRESET_ARENA \
{ -1000, -698, 7240, 330, -1166, 20, 16, 30, 1000,1000 }
#define SL_I3DL2_ENVIRONMENT_PRESET_HANGAR \
{ -1000,-1000, 10050, 230, -602, 20, 198, 30, 1000,1000 }
#define SL_I3DL2_ENVIRONMENT_PRESET_CARPETEDHALLWAY \
{ -1000,-4000, 300, 100, -1831, 2, -1630, 30, 1000,1000 }
// 大厅效果
#define SL_I3DL2_ENVIRONMENT_PRESET_HALLWAY \
{ -1000, -300, 1490, 590, -1219, 7, 441, 11, 1000,1000 }
// stone走廊效果
#define SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR \
{ -1000, -237, 2700, 790, -1214, 13, 395, 20, 1000,1000 }
.... // 省略
(4) SLPlayItf:播放接口。用于设置播放状态,获取播放参数等,比如调用播放接口的setPlayState成员函数讲播放状态设置为SL_PLAYSTATE_PLAYING时,OpenSL SL即开始播放音频。SLPlayItf
结构体定义如下,位于.../SLES/OpenSLES.h
头文件中:
// 播放接口ID
extern SL_API const SLInterfaceID SL_IID_PLAY;
struct SLPlayItf_ {
// 设置播放状态
// #define SL_PLAYSTATE_STOPPED ((SLuint32) 0x00000001)
// #define SL_PLAYSTATE_PAUSED ((SLuint32) 0x00000002)
// #define SL_PLAYSTATE_PLAYING ((SLuint32) 0x00000003)
SLresult (*SetPlayState) (
SLPlayItf self,
SLuint32 state
);
SLresult (*GetPlayState) (
SLPlayItf self,
SLuint32 *pState
);
// 获取播放时长
SLresult (*GetDuration) (
SLPlayItf self,
SLmillisecond *pMsec
);
// 获取播放位置
SLresult (*GetPosition) (
SLPlayItf self,
SLmillisecond *pMsec
);
...
};
(5) SLAndroidSimpleBufferQueueItf :缓冲队列接口。该接口是Android NDK专为Android提供的一个缓冲队列接口,位于.../SLES/openSLES_Android.h
头文件中,它的结构体定义如下:
// 缓存队列接口ID
extern SL_API const SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
struct SLAndroidSimpleBufferQueueItf_ {
// 插入数据到缓冲队列
// 当执行完该函数后,会自动回调回调接口处理下一个数据
SLresult (*Enqueue) (
SLAndroidSimpleBufferQueueItf self,
const void *pBuffer,
SLuint32 size
);
// 清空缓冲队列
SLresult (*Clear) (
SLAndroidSimpleBufferQueueItf self
);
// 获取缓冲队列状态
SLresult (*GetState) (
SLAndroidSimpleBufferQueueItf self,
SLAndroidSimpleBufferQueueState *pState
);
// 注册回调接口callback
SLresult (*RegisterCallback) (
SLAndroidSimpleBufferQueueItf self,
slAndroidSimpleBufferQueueCallback callback,
void* pContext
);
};
当然,这里我们只是介绍了几个常用的对象和接口,OpenSL ES中还有很多其他的对象及其接口,在以后用到了再作详细的介绍。
1.2 OpenSL ES使用步骤
OpenSL ES使用的场景比较多,本文只介绍下使用Audio Player对象播放音频的场景。首先,创建OpenSL ES引擎对象(Engine Object)和接口(Engine Interface);然后,使用引擎接口SLEngineItf分别创建Audio Player对象和Output Mix对象,前者创建之后与Output mix相关联用于音频输出。输入以URI作为示例,Output Mix默认与系统相关的默认输出设备关联,示意图如下:
由于NDK原生库已经添加了对OpenSL ES库的支持,因此在Android开发中我们可以非常容易地使用OpenSL ES来处理音频。修改CmakeList.txt文件如下所示:
...
# 查找NDK原生库OpenSLES
find_library(openSLES-lib openSLES)
# 链接所有库到avstream
target_link_libraries(
avstream
...
${openSLES-lib})
...
OpenSL ES音频播放步骤:
1.创建OpenSL ES引擎,即初始化Engine Object和Engine Interface
SLObjectItf pEngineObject = NULL;
SLEngineItf pEngineItf = NULL;
// 创建Engine对象
slCreateEngine(&pEngineObject, 0, NULL, 0, NULL, NULL);
// 初始化Engine对象
(*pEngineObject)->Realize(pEngineObject, SL_BOOLEAN_FALSE);
// 得到Engine对象的Engine Interface(接口)
(*pEngineObject)->GetInterface(pEngineObject, SL_IID_ENGINE,&pEngineItf);
2.创建混响输出对象,指定环境混响效果和音频输出
// 创建混响输出(output mix)对象
SLInterfaceID effect[1] = {SL_IID_ENVIRONMENTALREVERB};
SLboolean boolValue[1] = {SL_BOOLEAN_FALSE};
(*pEngineItf)->CreateOutputMix(pEngineItf,&pOutputMixObject, 1, effect,
boolValue);
// 初始化混响输出(output mix)对象
(*pOutputMixObject)->Realize(pOutputMixObject,SL_BOOLEAN_FALSE);
// 得到环境混响接口(Environmental Reverb)
(*pOutputMixObject)->GetInterface(pOutputMixObject,SL_IID_ENVIRONMENTALREVERB,
&outputMixEnvironmentalReverb);
// 指定环境混响效果为STONECORRIDOR
SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
(*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
outputMixEnvironmentalReverb, &reverbSettings);
// 配置音频输出
SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, pOutputMixObject};
SLDataSink audioSnk = {&outputMix, NULL};
其中,SLEnvironmentalReverbSettings和SLDataLocator_OutputMix、SLDataSink是结构体,它们在.../SLES/openSLES.h头文件的定义为:
// 环境混响效果结构体
typedef struct SLEnvironmentalReverbSettings_ {
SLmillibel roomLevel;
SLmillibel roomHFLevel;
SLmillisecond decayTime;
SLpermille decayHFRatio;
SLmillibel reflectionsLevel;
SLmillisecond reflectionsDelay;
SLmillibel reverbLevel;
SLmillisecond reverbDelay;
SLpermille diffusion;
SLpermille density;
} SLEnvironmentalReverbSettings;
复制代码
其中,#define SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR
{ -1000, -237, 2700, 790, -1214, 13, 395, 20, 1000,1000 }
// 混合输出定位器结构体
typedef struct SLDataLocator_OutputMix {
SLuint32 locatorType; // 固定值:SL_DATALOCATOR_OUTPUTMIX
SLObjectItf outputMix; // 混合输出对象
} SLDataLocator_OutputMix;
复制代码
// 音频数据输出结构体
typedef struct SLDataSink_ {
void *pLocator; // SLDataLocator_OutputMix引用,即指定混合输出
void *pFormat; // 格式,可为NULL
} SLDataSink;
3.创建播放(Audio Player)对象,获取该对象的播放接口,以实现播放状态控制操作
SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
SLDataFormat_PCM format_pcm = {
SL_DATAFORMAT_PCM, // 播放PCM格式数据
2, // 通道数量,立体声
SL_SAMPLINGRATE_44_1, // 采样率
SL_PCMSAMPLEFORMAT_FIXED_16, // 采样深度
SL_PCMSAMPLEFORMAT_FIXED_16,
getChannelMask((SLuint32) nbChannels), // 前作前右
SL_BYTEORDER_LITTLEENDIAN // 结束标志
};
// 指定数据源和数据所在位置
SLDataSource pAudioSrc = {&android_queue, &format_pcm};
SLuint32 numInterfaces_audio = 2;
const SLInterfaceID ids_audio[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND};
const SLboolean requireds_audio[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
// 创建Audio Player对象pPlayerObject
SLObjectItf pPlayerObject = NULL;
(*pEngineItf)->CreateAudioPlayer(
pEngineItf, // Engine Interface
&pPlayerObject, // Audio Player Object
&pAudioSrc, // 音频源为PCM
&audioSnk, // 音频输出
numInterfaces_audio, // 对象所要支持接口数目:2
ids_audio, // 对象所要支持接口的ID
requireds_audio); // 标志
// 初始化pPlayerObject对象
(*pPlayerObject)->Realize(pPlayerObject,SL_BOOLEAN_FALSE);
// 获取pPlayerObject对象的播放接口SLPlayItf
SLPlayItf pPlayerItf = NULL;
(*pPlayerObject)->GetInterface(pPlayerObject, SL_IID_PLAY,&pPlayerItf);
其中,SLDataLocator_AndroidSimpleBufferQueue和SLDataFormat_PCM、SLDataSource是结构体,它们在.../SLES/openSLES_Android.h和.../SLES/openSLES.h头文件的定义为:
// BufferQueue-based data locator definition
typedef struct SLDataLocator_AndroidSimpleBufferQueue {
SLuint32 locatorType; // 固定值,SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
SLuint32 numBuffers; // 缓冲数据个数
} SLDataLocator_AndroidSimpleBufferQueue;
// PCM数据格式结构体
typedef struct SLDataFormat_PCM_ {
SLuint32 formatType;
SLuint32 numChannels;
SLuint32 samplesPerSec;
SLuint32 bitsPerSample;
SLuint32 containerSize;
SLuint32 channelMask;
SLuint32 endianness;
} SLDataFormat_PCM;
// 音频数据源结构体
typedef struct SLDataSource_ {
void *pLocator; // 数据源定位器
void *pFormat; // 数据格式
} SLDataSource;
4.获取Audio Player对象的缓冲队列接口,注册回调函数
// 关闭所有流,释放AVFormatContext
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
avformat_free_context(pFmtCtx);
}
// 释放AVCodecContext
if (pACodecCtx) {
avcodec_free_context(&pACodecCtx);
}
if (pPacket) {
av_free(pPacket);
}
if (pAFrame) {
av_frame_free(&pAFrame);
}
if(swrContext) {
swr_free(&swrContext);
}
avformat_network_deinit();
该示例项目是利用FFmpeg和OpenSL ES引擎实现AAC音频的解码、播放,其中,FFmpeg用于解析RTSP网络流得到AAC数据,并对AAC进行解码、重采样得到PCM数据;OpenSL ES引擎用于在native层实现播放PCM音频流。该项目的核心思想:创建两个子线程,分别用于实现音频的解码和播放;创建两个链表,分别用于存储解码数据和播放数据。它的流程图如下所示:
3.1 解码线程
1.初始化FFmpge引擎、初始化链表、启动音频播放线程; 2.使用FFmpeg循环读取网络流中的数据包AVPacket,其数据格式为AAC(也可能为其他编码压缩格式),然后再进行解码、重采样得到PCM数据; 3.将解码得到PCM数据插入到链表中,以等待播放线程读取播放; 4.停止解码,释放资源。
decode_audio_thread函数源码如下:
void *decode_audio_thread(void *argv) {
quit = 0;
// 初始化FFmpeg引擎
int ret = createFFmpegEngine((const char *) argv);
if (ret < 0) {
LOGE("create FFmpeg Engine failed in decode_audio_thread");
return NULL;
}
// 初始化链表
queue_pcm_init(&pcm_queue);
PCMPacket *pcmPkt = (PCMPacket *) malloc(sizeof(PCMPacket));
// 启动音频播放线程
pthread_t threadId_play;
pthread_create(&threadId_play, NULL, play_audio_thread, NULL);
while (readAVPacket() >= 0) {
// 线程终止标志
if (quit) {
break;
}
// 解码
uint8_t *data = NULL;
int nb_samples = decodeAudio(&data);
// 插入到队列
if (nb_samples > 0 && data != NULL) {
pcmPkt->pcm = (char *) data;
pcmPkt->size = nb_samples;
queue_pcm_put(&pcm_queue, pcmPkt);
}
}
releaseFFmpegEngine();
free(pcmPkt);
return NULL;
}
3.2 播放线程
1.初始化播放链表; 2.启动初始化OPenSL ES线程,该线程主要是完成对OpenSL ES引擎的初始化,待初始化完毕后,会结束掉该线程。因为,OpenSL ES播放音频是通过回调函数的方式实现的,只需要循环读取PCM数据的线程即可; 3.循环读取PCM链表,并将读取的数据存储到播放链表中。 play_audio_thread函数源码如下:
pthread_t threadId_open_opensl;
void *play_audio_thread(void *argv) {
PCMPacket pcmPacket;
PCMPacket *pkt = &pcmPacket;
// 初始化播放链表
playQueueInit(&global_openSL.queue_play);
// 启动初始化OpenSL ES线程
pthread_create(&threadId_open_opensl,NULL,thread_open_opensl,NULL);
// 循环读取PCM链表,并将读取的数据存储到播放链表中
for (;;) {
if (quit) {
break;
}
if (queue_pcm_get(&pcm_queue, pkt) > 0) {
// 写入数据
PCMData *pcmData = (PCMData *) malloc(sizeof(PCMData));
pcmData->pcm = pkt->pcm;
pcmData->size = pkt->size;
playQueuePut(&global_openSL.queue_play,pcmData);
}
}
}
void * thread_open_opensl(void * argv) {
// 初始化OpenSL ES引擎
int ret = createOpenSLEngine(channels, sample_rate,sample_fmt);
if (ret < 0) {
quit = 1;
LOGE("create OpenSL Engine failed in play_audio_thread");
return NULL;
}
pthread_exit(&threadId_open_opensl);
}