Android直播開發之旅(13):使用FFmpeg+OpenSL ES播放PCM音頻

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狀態機制轉化流程如下圖所示:
狀態機制

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對象的緩衝隊列接口,註冊回調函數

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音頻流。該項目的核心思想:創建兩個子線程,分別用於實現音頻的解碼和播放;創建兩個鏈表,分別用於存儲解碼數據和播放數據。它的流程圖如下所示:
在這裏插入圖片描述

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章