Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频

文章目录

      • 1. OpenSL ES原理
        • 1.1 OpenSL ES核心API讲解
          • 1.1.1 对象(Object)与接口(Interface)
          • 1.1.2 [OpenSL ES的状态机制](https://www.jianshu.com/p/cccb59466e99)
          • 1.1.3 OpenSL ES重要接口
        • 1.2 OpenSL ES使用步骤
      • 2. FFmpeg音频解码原理
        • 2.1 FFmpeg相关函数解析
          • 2.1.1 av_register_all()/avcodec_register_all()
          • 2.1.2 avformat_alloc_context/avformat_free_context
          • 2.1.3 avformat_open_input/avformat_close_input()
          • 2.1.4 avformat_find_stream_info
          • 2.1.5 avcodec_find_decoder/avcodec_find_decoder_by_name
          • 2.1.6 avcodec_alloc_context3/avcodec_free_context
          • 2.1.7 avcodec_open2
          • 2.1.8 av_read_frame
          • 2.1.9 avcodec_send_packet/avcodec_receive_frame
          • 2.1.10 swr_convert/swr_init
          • 2.1.10 swr_convert
        • 2.2 FFmpeg音频解码步骤
          • 2.2.1 初始化操作
          • 2.2.2 读取流数据
          • 2.2.3 解码、重采样
          • 2.2.4 释放资源
      • 3. Android播放PCM音频项目实战
        • 3.1 解码线程
        • 3.2 播放线程

在Android直播开发之旅(12):初探FFmpeg开源框架一文中,我们详细介绍了FFmpeg框架的架构、音视频相关术语以及重要的结构体。为了能够对这些重要的结构体有个深入的理解,本文将在此基础上,利用FFmpeg解析rtsp数据流以获取AAC音频数据,再对AAC进行解码为PCM,然后结合OpenSL ES库本地实时播放解码得到的PCM音频数据。

1. OpenSL ES原理

 OpenSL ES(Open Sound Library for Embedded Systems),即嵌入式音频加速标准,是一个无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API库。它为嵌入移动多媒体设备上的本地应用程序开发者提供了标准化、高性能、低相应时间的音频开发方案,并实现软/硬件音频性能的直接跨平台部署,被广泛应用于3D音效、音频播放、音频录制以及音乐体验增强(低音增强和环境混响)等方面。对于Android平台而言,我们可以使用OpenSL ES库直接在native层处理音频数据,比如录制音频、播放音频等。OpenSL ES嵌入式设备中部署的软/硬件层次结构,如下图所示:

[外链图片转存失败(img-ZgeSOUDe-1568164982160)(https://www.khronos.org/assets/uploads/apis/sw_hw1.jpg)]

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);
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状态机制转化流程如下图所示:
Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频_第1张图片

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默认与系统相关的默认输出设备关联,示意图如下:
Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频_第2张图片

 由于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对象的缓冲队列接口,注册回调函数

SLAndroidSimpleBufferQueueItf pBufferItf = NULL;
// 获取缓冲区接口
(*pPlayerObject)->GetInterface(pPlayerObject,SL_IID_BUFFERQUEUE,&pBufferItf);  
// 注册回调接口,pcmBufferCallBack为自定义的回调函数
(*pBufferItf)->RegisterCallback(pBufferItf, pcmBufferCallBack,NULL);   

5.实现回调函数,向缓冲区队列写入PCM数据

void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void *context) {
	PCMData pcmData;
	PCMData *data = &pcmData;

	SLresult result = NULL;
    // 这里要用循环读取,否则当插入数据失败后,OpenSL ES就不会再调用Enqueue函数
	for(;;){
		int ret = playQueueGet(&global_openSL.queue_play, data);
		if (!isExit && ret > 0) {
			LOGI("start to play......");
            // 将PCM数据插入到缓冲队列
            // 挡播放完毕后,会自动调用该回调函数
			result = (*pBufferItf)->Enqueue(pBufferItf, data->pcm,data->size);
			if (SL_RESULT_SUCCESS == result) {
				LOGI("success to play......");
				break;
			}
		}
		if(isExit) {
			LOGI("stop to play......");
			break;
		}
	}
}

6.销毁对象,释放资源

if (pPlayerObject) {
    (*pPlayerItf)->SetPlayState(pPlayerItf, SL_PLAYSTATE_STOPPED);
    (*pPlayerObject)->Destroy(pPlayerObject);
    pPlayerObject = NULL;
    pPlayerItf = NULL;
    pVolumeItf = NULL;
}
if (pOutputMixObject) {
    (*pOutputMixObject)->Destroy(pOutputMixObject);
    pOutputMixObject = NULL;
    pBufferItf = NULL;
    outputMixEnvironmentalReverb = NULL;
}
if (pEngineObject) {
    (*pEngineObject)->Destroy(global_openSL.pEngineObject);
    pEngineObject = NULL;
    pEngineItf = NULL;
}

2. FFmpeg音频解码原理

2.1 FFmpeg相关函数解析

2.1.1 av_register_all()/avcodec_register_all()
void av_register_all(void);
void avcodec_register_all(void);

解析:av_register_all(void)函数被定义在…/libavformat/avfotmat.h头文件中,作用是初始化libavformat模块、注册所有的muxers(复用器)/demuxers(解复用器)/protocols(协议)。当然,我们也可以通过函数av_register_input_format()和av_register_output_format()注册指定的输入或输出format;avcodec_register_all函数被定义在…/libavcodec/avcodec.h头文件中,作用是注册所有codecs(编解码器)/parses(解析器)/bitstream filters。当然,我们也可以使用avcodec_register()、av_register_codec_parser()和av_register_bitstream_filter函数有选择性的注册。

2.1.2 avformat_alloc_context/avformat_free_context
AVFormatContext *avformat_alloc_context(void);
void avformat_free_context(AVFormatContext *s);

解析:avformat_alloc_context()avformat_free_context()函数被定义在…/libavformat/avfotmat.h头文件中,前者的作用是创建一个AVFormatContext结构体,并为其分配内存;后者是释放这个被创建的AVFormatContext结构体以及其所占的各种资源。

2.1.3 avformat_open_input/avformat_close_input()
/**
 * 打开一个输入流(input stream)
 *
 * @param ps 指向AVFormatContext结构体的指针变量,该结构体用于存储输入流相关数据;
 * @param url 输入流的URL地址
 * @param fmt 如果不为NULL,即指定input format;如果为NULL,引擎自动检测format;
 * @param options  配置AVFormatContext的元数据
 * @return 0 成功, <0 失败
 */
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

/**
 * 关闭打开的输入AVFormatContext
 */
void avformat_close_input(AVFormatContext **s);

解析:avformat_open_input()avformat_close_input()函数被定义在…/libavformat/avfotmat.h头文件中,前者用于打开一个input stream和读取它的header,需要注意的是,此时编解码器还未启动;后者用于关闭输入流并释放与流相关的所有资源。这里重点讲解下avformat_open_input函数的options参数,该参数主要是用于配置元数据,它的类型为结构体AVDictionary,该结构体被定义在…/libavutil/dict.c源文件中,源码如下:

struct AVDictionary {
    int count;					// 数量
    AVDictionaryEntry *elems;	// 条目
};

typedef struct AVDictionaryEntry {
    char *key;   // 键
    char *value; // 值
} AVDictionaryEntry;

 我们可以通过av_dict_set()函数为AVDictionary设定一个entry(条目),和通过av_dict_get()函数从AVDictionary中取出一个entry条目。比如,在网络特别差的情况下,使用avformat_open_input()函数打开一个输入流RUL往往会因为网络原因"卡"在这里,在较长的一段时间内,既不报错也不继续向下执行,而Android上层又无法捕捉FFmpeg部分执行的情况,导致用户体验非常差。这个时候,如果我们给avformat_open_input()加个超时检测,只要超过设定的时间,就认为打开输入流失败通知Android层作异常处理,体验效果肯定会大大不同,而实现这个超时时限就需要用到AVDictionary。示例代码如下:

AVDictionary *openOpts = NULL;
av_dict_set(&openOpts, "stimeout", "15000000", 0);    // 配置超时选项,设置15s为超时
avformat_open_input(&pFmtCtx, url, NULL, &openOpts);

 当然,除了"stimeout",FFmpeg还为我们提供了各种可设置的选项,比如"buffer_size"用于设置输入缓冲区大小(字节);"rtsp_transport"用于设置rtsp传输协议类型(默认为udp);"b"用于设置传输码率等等。

2.1.4 avformat_find_stream_info
/**
 * Read packets of a media file to get stream information. This
 * is useful for file formats with no headers such as MPEG.
 * @param ic 多媒体文件句柄,即广义文件上下文
 * @param options  元数组选项
 * @return >=0 if OK, AVERROR_xxx on error
 */
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

解析:

2.1.5 avcodec_find_decoder/avcodec_find_decoder_by_name
/**
 * 根据解码器ID,匹配得到解码器
 *
 * @param id 解码器ID
 * @return 如果为NULL,说明不存在与ID对应的解码器
 */
AVCodec *avcodec_find_decoder(enum AVCodecID id);

/**
 * 根据名称,获取解码器
 *
 * @param name 解码器名称,如"h264"
 * @return 如果为NULL,说明不存在与ID对应的解码器
 */
AVCodec *avcodec_find_decoder_by_name(const char *name);

解析:avcodec_find_decoder()和avcodec_find_decoder_by_name()函数被定义在…/libavcodec/avcodec.h头文件中,分别用于根据ID、名称来获取指定的解码器,其中,AVCodecID的类型有:

enum AVCodecID {
	// video codecs
	AV_CODEC_ID_MJPEG,
    AV_CODEC_ID_H264,
    ...
    // pcm
    AV_CODEC_ID_PCM_S8,
    AV_CODEC_ID_PCM_U8,
    AV_CODEC_ID_PCM_ALAW,    
    ...
	// audio codecs
    AV_CODEC_ID_AAC,
    AV_CODEC_ID_MP3,
    ...
    //subtitle codecs
    AV_CODEC_ID_MOV_TEXT,
    AV_CODEC_ID_HDMV_PGS_SUBTITLE,
    AV_CODEC_ID_DVB_TELETEXT,
    AV_CODEC_ID_SRT,
    ...
}
2.1.6 avcodec_alloc_context3/avcodec_free_context
/**
 * 为AVCodec创建一个对应AVCodecContext,为其分配内存
 *
 * @param codec 编解码器
 * @return An AVCodecContext filled with default values or NULL on failure.
 */
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

/**
 * 释放AVCodecContext,以及与其相关联的所有资源,置空释放AVCodecContext引用
 */
void avcodec_free_context(AVCodecContext **avctx);

解析:avcodec_alloc_context3()avcodec_free_context()函数被定义在…/libavcodec/avcodec.h头文件中,前者用于为编解码器创建一个AVCodecContext;后者用于释放AVCodecContext引用及其相关联的所有资源。

2.1.7 avcodec_open2
/**
 * 使用给定的编解码器AVCodec初始化AVCodecContext,其中AVCodecContext由  
 * avcodec_alloc_context3()得到。注意:该函数非线程安全
 * @param avctx 将要被初始化的AVCodecContext
 * @param codec 编解码器
 * @param options AVCodecContext元数据
 * @return 0表示成功
 */
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

解析:avcodec_open2()函数被定义在…/libavcodec/avcodec.h头文件中,它的作用是使用给定的编解码器codec初始化AVCodecContext,需要注意的是,AVCodecContext由avcodec_alloc_context3()得到且与codec相对应,否则,avcodec_open2会失败。下面是演示如何打开指定解码器的核心代码,具体如下:

// 1.注册所有编解码器
avcodec_register_all();
// 2.根据ID查找得到对应的解码器AVCodec
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!codec)
  exit(1);
// 3.创建解码器对应的AVCodecContext
context = avcodec_alloc_context3(codec);
// 4.初始化AVCodecContext,即打开解码器
AVDictionary *opts = NULL;
av_dict_set(&opts, "b", "2.5M", 0); 
if (avcodec_open2(context, codec, opts) < 0)
   exit(1);
2.1.8 av_read_frame
/**
 * 返回流的下一帧数据
 * @param s 广义文件上下文
 * @param pkt 数据包,用于存储读取到的一帧数据
 * @return 0表示成功, < 0 表示出错或文件结束
 */
int av_read_frame(AVFormatContext *s, AVPacket *pkt);

解析:av_read_frame()函数被定义于…/libavformat/avformat.h头文件中,用于从一个流中读取一帧数据,并存储在AVPacket结构体中。对于视频(video),pkt存储了完整的一帧视频数据;对于音频(audio),如果每个音频帧具有固定的大小(如PCM或ADPCM数据),那么pkt可能包含整数个帧。如果每个音频帧的大小是可变的(如MPEG数据),那么pkt包含一个完整帧数据。需要注意的是,如果pkt->buf=NULL,pkt直到下一次调用av_read_frame()或调用avformat_close_input()之前都是有效的;否则,pkt会是永远有效地。另外,当我们使用完pkt中的数据后,必须对pkt进行释放,通过调用av_packet_unref()函数来实现。

2.1.9 avcodec_send_packet/avcodec_receive_frame
/**
 * 向解码器提供一个要解码的原始数据包
 *
 * @param avctx codec上下文
 * @param[in] avpkt 输入数据包AVPacket,通常来源于av_read_frame(),通常包含一个视频帧或几个完整 
 *    				的音频帧
 *
 * @return 0 成功,其他情况失败
 *      AVERROR(EAGAIN):   当前状态不接受输入
 *      AVERROR_EOF:       解码器被flushed并且没有可用的AVPacket被添加的解码器中
 *      AVERROR(EINVAL):   打开解码器失败,或者打开的是编码器,或者需要flush一下
 *      AVERROR(ENOMEM):   添加AVPacket到解码队列失败
 *      other errors: 其他异常
 */
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

/**
 * 返回解码器解码输出的数据
 *
 * @param avctx codec上下文
 * @param frame 用于存储解码器输出的音频或视频帧数据
 *
 * @return
 *      0:                 成功,返回一帧解码数据
 *      AVERROR(EAGAIN):   解码器输出数据无效,需尝试向解码器输入新的被解码数据
 *      AVERROR_EOF:       标志解码结束
 *      AVERROR(EINVAL):   解码器未打开,或者打开的是编码器
 *      other negative values: 其他错误
 */
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

解析:avcodec_send_packet()avcodec_receive_frame()函数被定义在…/libavcodec/avcodec.h头文件中,是FFmpeg提供用于替换解码函数avcodec_decode_audio4()/avcodec_decode_video2()的一组新API。前者用于将av_read_frame()获取到的一帧编码数据(注:存储在一个AVPacket中,可以使音频或视频)作为输入添加到解码器中;后者用于从解码器中读取一帧已解码数据。
 需要注意的是,对于某些解码器来说,比如PCM,则需要多次调用avcodec_receive_frame()函数直到返回0,来获取完整的解码数据,并且当使用完AVFrame中的数据,必须调用av_frame_unref()函数来AVFrame中存储的所有缓存区引用和重置AVFrame结构体字段。

2.1.10 swr_convert/swr_init
/**
 * 创建一个SwrContext结构体,并设置/重置重采样相关参数
 *
 * @param s               可用Swr context或者NULL
 * @param out_ch_layout   输出通道布局 (AV_CH_LAYOUT_*)
 * @param out_sample_fmt  输出采样格式 (AV_SAMPLE_FMT_*).
 * @param out_sample_rate 输出采样率(frequency in Hz)
 * @param in_ch_layout    输入通道布局(AV_CH_LAYOUT_*)
 * @param in_sample_fmt   输入采样格式(AV_SAMPLE_FMT_*).
 * @param in_sample_rate  输入采样率(frequency in Hz)
 * @param log_offset      通常为0
 * @param log_ctx         通常为NULL
 * @return NULL表示失败
 */
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
   									  int64_t out_ch_layout, 
                                      enum AVSampleFormat out_sample_fmt, 
                                      int out_sample_rate,
    								  int64_t  in_ch_layout, 
                                      enum AVSampleFormat  in_sample_fmt, 
                                      int  in_sample_rate,
    								  int log_offset, void *log_ctx);
/**
 * 初始化SwrContext,在参数设置完毕后调用
 *
 * @param[in,out]   s Swr context to initialize
 * @return AVERROR error code in case of failure.
 */
int swr_init(struct SwrContext *s);

/**
 * 释放SwrContext,及其所占资源
 *
 * @param[in] s a pointer to a pointer to Swr context
 */
void swr_free(struct SwrContext **s);

解析:swr_alloc_set_opts()swr_init()函数被声明在…/libswresample/swresample.h头文件中,其中,swr_alloc_set_opts()用于创建一个重采样所需的SwrContext结构体,同时配置相关参数;swr_init()用于初始化被创建的SwrContext结构体;。这两个函数共同完成重采样模块的初始化配置工作,当然我们还可以通过另一种方式来替换实现,示例代码如下:

SwrContext *swr = swr_alloc();
av_opt_set_channel_layout(swr, "in_channel_layout",  AV_CH_LAYOUT_5POINT1, 0);
av_opt_set_channel_layout(swr, "out_channel_layout", AV_CH_LAYOUT_STEREO,  0);
av_opt_set_int(swr, "in_sample_rate",     48000,                0);
av_opt_set_int(swr, "out_sample_rate",    44100,                0);
av_opt_set_sample_fmt(swr, "in_sample_fmt",  AV_SAMPLE_FMT_FLTP, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16,  0);
swr_init(swr);

注:当SwrContext使用完毕后,需要调用swr_free()函数进行资源释放。

2.1.10 swr_convert
/** 转换音频
 *
 * @param s         Swr上下文
 * @param out       输出缓存
 * @param out_count 每个通道中用于输出样本的可用空间量
 * @param in        输入缓存, 
 * @param in_count  每个通道中可用的输入样本数量
 *
 * @return 每个通道输出的采样数量;<0 失败
 */
int swr_convert(struct SwrContext *s, 
				uint8_t **out, int out_count,
                const uint8_t **in , int in_count);

解析:swr_convert()函数被声明在…/libswresample/swresample.h头文件中,它的作用是对音频数据进行重新采样,以获得满足需求的音频数据。比如当使用FFmpeg将AudioRecord录制的PCM音频编码为AAC格式时,就将基于ENCODING_PCM_16BIT(AV_SAMPLE_FMT_S16,有符号整型16位)转换为AV_SAMPLE_FMT_FLTP(浮点方式),因为FFmpeg库的是使用AV_SAMPLE_FMT_FLTP这种采样格式(sample format)进行存储的;又比如如果我们需要播放FFmpeg解码的PCM数据,但是由于FFmpeg解码得到的数据类型是AV_SAMPLE_FMT_FLTP,而播放器的格式通常为AV_SAMPLE_FMT_S16,因此就需要使用swr_convert()函数进行重采样。除了上述两种,FFmpeg框架支持多种采样格式相互转换,具体如下:

// 位于.../libavutil/samplefmt.h头文件
enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats.
};

2.2 FFmpeg音频解码步骤

 使用FFmpeg解码RTSP流中的AAC数据比较简单,具体可归纳为如下几个步骤:

2.2.1 初始化操作

1.初始化FFmpeg引擎。注册muxers(复用器)、demuxers(解复用器)、网络以及编解码器等

AVFormatContext *pFmtCtx = NULL;  // 全局上下文
AVCodecContext *pACodecCtx = NULL;// 编解码器上下文
AVCodec *pACodec = NULL;  // 编解码器
int index_audio = -1;      // 音频
AVFrame *pAFrame;
AVPacket *pPacket;
SwrContext *swrContext = NULL;

av_register_all();
avformat_network_init();
avcodec_register_all();

2.解协议。即打开广义输入文件,创建、初始化对应的AVFormatContext结构体,和获取音视频流信息,包括码率、帧率以及音频轨道下标等。

// 创建AVFormatContext
pFmtCtx = avformat_alloc_context();
if (pFmtCtx == NULL) {
    return -1;
}
LOG_I("create ffmpeg for url = %s", url);

AVDictionary *openOpts = NULL;
av_dict_set(&openOpts, "stimeout", "15000000", 0);  // 15s超时连接断开
av_dict_set(&openOpts, "buffer_size", "1024000", 0);// 减少码率变大导致花屏现象
// 解协议,即初始化AVFormatContext
int ret = avformat_open_input(&pFmtCtx, url, NULL, &openOpts);
if (ret < 0) {
    LOGE("open input failed in PlayAudio,timesout.");
    releaseFFmpegEngine();
    return -1;
}

// 读取输入文件的数据包以获取流信息,比如码率、帧率等
ret = avformat_find_stream_info(pFmtCtx, NULL);
if (ret < 0) {
    LOGE("find stream info failed in PlayAudio.");
    releaseFFmpegEngine();
    return -1;
}
// 获取音频下标
for (int i = 0; i < pFmtCtx->nb_streams; i++) {
    if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        index_audio = i;
        break;
    }
}
if (index_audio == -1) {
    LOGE("url have no audio stream in PlayAudio.");
    releaseFFmpegEngine();
    return -1;
}

3.获取、打开解码器。根据AVFormatContext中音频数据编码格式信息,找到对应的解码器并创建对应的AVCodecContext结构体,然后开启解码器。

// 得到解码器
AVCodecParameters *avCodecParameters = NULL;
avCodecParameters = pFmtCtx->streams[index_audio]->codecpar;
if (avCodecParameters == NULL) {
    LOGE("get audio codec's AVCodecParameters failed.");
    return -1;
}
pACodec = avcodec_find_decoder(avCodecParameters->codec_id);
if (!pACodec) {
    LOG_E("do not find matched codec for %s", pFmtCtx->audio_codec->name);
    releaseFFmpegEngine();
    return -1;
}
// 获取解码器对应的AVCodecContext
pACodecCtx = avcodec_alloc_context3(pACodec);
if (!pACodecCtx) {
    LOGE("alloc AVCodecContext failed..");
    return -1;
}
avcodec_parameters_to_context(pACodecCtx, avCodecParameters);
// 打开解码器
ret = avcodec_open2(pACodecCtx, pACodec, NULL);
if (ret < 0) {
    LOG_E("open %s codec failed.", pFmtCtx->audio_codec->name);
    releaseFFmpegEngine();
    return -1;
}

4.初始化音频重采样模块。由于FFmpeg解码得到的PCM数据为AV_SAMPLE_FMT_FLTP,因此需要通过重采样的方式将音频格式转换为AV_SAMPLE_FMT_S16进行播放,下面代码用于创建、初始化重采样引擎。

// 创建swrContext,配置参数
swrContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, pACodecCtx->sample_rate,
channel_layout,
sample_fmt, pACodecCtx->sample_rate, 0, NULL);
// 初始化swrContext
swr_init(swrContext);
2.2.2 读取流数据

 从一个流中读取一帧数据,并存储在AVPacket结构体中,此时AVPacket存储的是AAC格式音频数据。

int readAVPacket() {
    if (pACodecCtx == NULL) {
        return -1;
    }
    return av_read_frame(pFmtCtx, pPacket);
}
2.2.3 解码、重采样

 将AAC音频send到解码器,然后再从解码器获取一帧完整的PCM数据并存储在AVFrame结构体中,最后再对该PCM数据(AV_SAMPLE_FMT_FLTP)进行重采样以获得可播放的PCM数据(AV_SAMPLE_FMT_S16)。

int decodeAudio(uint8_t **data) {
    if (pPacket->stream_index != index_audio) {
        return -1;
    }
    // 发送一个AVPacket(AAC)到解码器
    int ret = avcodec_send_packet(pACodecCtx, pPacket);
    if (ret != 0) {
        return -1;
    }
    // 循环读取,获取一帧完整PCM音频数据
    while (avcodec_receive_frame(pACodecCtx, pAFrame) == 0) {
        LOG_D("读取一帧音频数据,frameSize=%d", pAFrame->nb_samples);
        break;
    }
    // 重采样
    uint8_t *a_out_buffer = (uint8_t *) av_malloc(2 * sample_rate);
    swr_convert(swrContext, &a_out_buffer, sample_rate * 2,
                (const uint8_t **) pAFrame->data,
                pAFrame->nb_samples);
    int outbuffer_size = av_samples_get_buffer_size(NULL, 		
                                                   av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO),
                                                    pAFrame->nb_samples,
                                                    AV_SAMPLE_FMT_S16P, 1);
    *data = (uint8_t *)malloc(outbuffer_size);
    memset(*data,0,outbuffer_size);
    memcpy(*data,a_out_buffer,outbuffer_size);
    free(a_out_buffer);
    // Wipe the AVFrame
    av_frame_unref(pAFrame);
    // Wipe the AVPacket
    av_packet_unref(pPacket);
    return outbuffer_size;
}
2.2.4 释放资源
// 关闭所有流,释放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();

3. Android播放PCM音频项目实战

 该示例项目是利用FFmpeg和OpenSL ES引擎实现AAC音频的解码、播放,其中,FFmpeg用于解析RTSP网络流得到AAC数据,并对AAC进行解码、重采样得到PCM数据;OpenSL ES引擎用于在native层实现播放PCM音频流。该项目的核心思想:创建两个子线程,分别用于实现音频的解码和播放;创建两个链表,分别用于存储解码数据和播放数据。它的流程图如下所示:
Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频_第3张图片

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);
}

github源码: DemoOpenSLES,欢迎star & issues

你可能感兴趣的:(【ffmpeg,开发艺术】,Android视频直播)