【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )
文章目錄
- I . FFMPEG 播放視頻流程
- II . OpenSLES 播放音頻流程
- III . OpenSLES 播放參考 Google 官方示例
- IV . OpenSL ES 播放代碼 ( 詳細注釋 )
- IV . OpenSLES slAndroidSimpleBufferQueueCallback 回調函數聲明及實現代碼
I . FFMPEG 播放視頻流程
FFMPEG 播放視頻流程 : 視頻中包含圖像和音頻 ;
① FFMPEG 初始化 : 參考博客 【Android FFMPEG 開發】FFMPEG 初始化 ( 網絡初始化 | 打開音視頻 | 查找音視頻流 )
② FFMPEG 獲取 AVStream 音視頻流 : 參考博客 【Android FFMPEG 開發】FFMPEG 獲取 AVStream 音視頻流 ( AVFormatContext 結構體 | 獲取音視頻流信息 | 獲取音視頻流個數 | 獲取音視頻流 )
③ FFMPEG 獲取 AVCodec 編解碼器 : 參考博客 【Android FFMPEG 開發】FFMPEG 獲取編解碼器 ( 獲取編解碼參數 | 查找編解碼器 | 獲取編解碼器上下文 | 設置上下文參數 | 打開編解碼器 )
④ FFMPEG 讀取音視頻流中的數據到 AVPacket : 參考博客 【Android FFMPEG 開發】FFMPEG 讀取音視頻流中的數據到 AVPacket ( 初始化 AVPacket 數據 | 讀取 AVPacket )
⑤ FFMPEG 解碼 AVPacket 數據到 AVFrame ( 音頻 / 視頻數據解碼 ) : 參考博客 【Android FFMPEG 開發】FFMPEG 解碼 AVPacket 數據到 AVFrame ( AVPacket->解碼器 | 初始化 AVFrame | 解碼為 AVFrame 數據 )
⑥ FFMPEG AVFrame 圖像格式轉換 YUV -> RGBA : 參考博客 【Android FFMPEG 開發】FFMPEG AVFrame 圖像格式轉換 YUV -> RGBA ( 獲取 SwsContext | 初始化圖像數據存儲內存 | 圖像格式轉換 )
⑦ FFMPEG ANativeWindow 原生繪制 準備 : 參考博客 【Android FFMPEG 開發】FFMPEG ANativeWindow 原生繪制 ( Java 層獲取 Surface | 傳遞畫布到本地 | 創建 ANativeWindow )
⑧ FFMPEG ANativeWindow 原生繪制 : 參考博客 【Android FFMPEG 開發】FFMPEG ANativeWindow 原生繪制 ( 設置 ANativeWindow 緩沖區屬性 | 獲取繪制緩沖區 | 填充數據到緩沖區 | 啟動繪制 )
⑨ FFMPEG 音頻重采樣 : 參考博客 【Android FFMPEG 開發】FFMPEG 音頻重采樣 ( 初始化音頻重采樣上下文 SwrContext | 計算音頻延遲 | 計算輸出樣本個數 | 音頻重采樣 swr_convert )
II . OpenSLES 播放音頻流程
OpenSLES 播放音頻流程 :
〇 視頻播放操作 : FFMPEG 環境初始化 , 獲取 AVStream 音視頻流 , 獲取 AVCodec 編解碼器 , 讀取音視頻流中的數據到 AVPacket , 解碼 AVPacket 數據到 AVFrame , AVFrame 圖像格式轉換 YUV -> RGBA , ANativeWindow 原生繪制 ;
〇 音頻播放操作 : FFMPEG 環境初始化 , 獲取 AVStream 音視頻流 , 獲取 AVCodec 編解碼器 , 讀取音視頻流中的數據到 AVPacket , 解碼 AVPacket 數據到 AVFrame , 音頻重采樣 , 然后使用 OpenSLES 播放重采樣后的音頻 ;
① 創建引擎 : 先創建引擎對象 , 再實現引擎對象 , 最后從引擎對象中 , 獲取引擎接口 ;
SLresult result;// 創建引擎 result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);// 實現引擎 result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);// 獲取引擎接口 result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);② 設置輸出混音器 : 創建輸出混音器對象 , 實現輸出混音器 ;
// 創建輸出混音器對象 , 可以指定一個混響效果參數 ( 該混淆參數可選 ) const SLInterfaceID ids_engine[1] = {SL_IID_ENVIRONMENTALREVERB}; const SLboolean req_engine[1] = {SL_BOOLEAN_FALSE}; result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids_engine, req_engine);// 實現輸出混音器 result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);③ 獲取混響接口并設置混響 : 該步驟不是必須操作 , 另外獲取混響接口可能失敗 ;
// 獲取混響接口 result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb); // 設置混響 if (SL_RESULT_SUCCESS == result) {result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &reverbSettings);(void)result; }④ 配置音源輸入 : 配置音頻數據源緩沖隊列 , 和 音源格式 ( 采樣率 , 樣本位數 , 通道數 , 樣本大小端格式 ) ;
//1 . 配置音源輸入// 配置要播放的音頻輸入緩沖隊列屬性參數 , 緩沖區大小 , 音頻格式 , 采樣率 , 樣本位數 , 通道數 , 樣本大小端格式 SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};// PCM 格式 SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, //PCM 格式2, //兩個聲道SL_SAMPLINGRATE_44_1, //采樣率 44100 HzSL_PCMSAMPLEFORMAT_FIXED_16, //采樣位數 16位SL_PCMSAMPLEFORMAT_FIXED_16, //容器為 16 位SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, //左右雙聲道SL_BYTEORDER_LITTLEENDIAN}; //小端格式// 設置音頻數據源 , 配置緩沖區 ( loc_bufq ) 與 音頻格式 (format_pcm) SLDataSource audioSrc = {&loc_bufq, &format_pcm};⑤ 配置音頻輸出 : 裝載輸出混音器對象 到 SLDataLocator_OutputMix , 在將 SLDataLocator_OutputMix 結構體裝載到 SLDataSink 中 ;
// 配置混音器 : 將 outputMixObject 混音器對象裝載入 SLDataLocator_OutputMix 結構體中 SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject}; // 將 SLDataLocator_OutputMix 結構體裝載到 SLDataSink 中 // 音頻輸出通過 loc_outmix 輸出 , 實際上是通過 outputMixObject 混音器對象輸出的 SLDataSink audioSnk = {&loc_outmix, NULL};⑥ 創建并實現播放器 : 先使用 引擎 , 音源輸入 , 音頻輸出 , 采樣率 , 接口隊列ID 等參數創建播放器 , 再實現播放器對象 ;
// 操作隊列接口 , 如果需要 特效接口 , 添加 SL_IID_EFFECTSEND const SLInterfaceID ids_player[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND,/*SL_IID_MUTESOLO,*/}; const SLboolean req_player[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,/*SL_BOOLEAN_TRUE,*/ };// 創建播放器 result = (*engineEngine)->CreateAudioPlayer(engineEngine,&bqPlayerObject,&audioSrc, //音頻輸入&audioSnk, //音頻商戶處bqPlayerSampleRate? 2 : 3,//ids_player,req_player);// 創建播放器對象 result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);⑦ 獲取播放器接口 和 緩沖隊列接口 : 獲取的接口 對應 播放器創建時的接口 ID 數組參數 ;
// 獲取播放器 Player 接口 : 該接口用于設置播放器狀態 , 開始 暫停 停止 播放 等操作 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);// 獲取播放器 緩沖隊列 接口 : 該接口用于控制 音頻 緩沖區數據 播放 result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,&bqPlayerBufferQueue);⑧ 注冊回調函數 : 按照指定的回調函數類型 , 聲明并實現該回調函數 , 并將該回調函數注冊給播放器緩沖隊列接口 ;
// 注冊緩沖區隊列的回調函數 , 每次播放完數據后 , 會自動回調該函數 // 傳入參數 this , 就是 bqPlayerCallback 函數中的 context 參數 result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);回調函數類型 :
typedef void (SLAPIENTRY *slAndroidSimpleBufferQueueCallback)(SLAndroidSimpleBufferQueueItf caller,void *pContext );回調函數實現 :
//每當緩沖數據播放完畢后 , 會自動回調該回調函數 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {...//通過播放器隊列接口 , 將 PCM 數據加入到該隊列緩沖區后 , 就會自動播放這段音頻(*bq)->Enqueue(bq, audioChannel->data, data_size);}⑨ 獲取效果器接口 和 音量控制接口 : 這兩個接口不是必須的 , 可選選項 ;
// 獲取效果器發送接口 ( get the effect send interface )bqPlayerEffectSend = NULL;if( 0 == bqPlayerSampleRate) {result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,&bqPlayerEffectSend);}// 獲取音量控制接口 ( get the volume interface ) [ 如果需要調節音量可以獲取該接口 ]result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);⑩ 設置播放狀態 : 設置播放狀態為 SL_PLAYSTATE_PLAYING ;
// 設置播放器正在播放狀態 ( set the player's state to playing ) result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);? 手動調用激活回調函數 : 第一次激活回調函數調用 , 需要手動激活 ;
// 手動激活 , 手動調用一次 bqPlayerCallback 回調函數 bqPlayerCallback(bqPlayerBufferQueue, this);III . OpenSLES 播放參考 Google 官方示例
1 . Google 官方示例 : 關于 OpenSL ES 音頻播放 , 在 Google 的官方示例 native-audio 中 , 有現成的代碼可供使用 ;
① Google 官方示例 參考地址 : native-audio
② OpenSL ES 播放代碼 : native-audio-jni.c
2 . FFMPEG 播放 : 在 FFMPEG 中可以原封不動的拷貝 native-audio 項目中的關于 OpenSL ES 播放相關的代碼 , 但是在 slAndroidSimpleBufferQueueCallback 回調函數中播放的音頻 , 是 FFMPEG 中音頻從 AVPacket 解碼成的 AVFrame 重采樣后的音頻 , 關于音頻重采樣參考 【Android FFMPEG 開發】FFMPEG 音頻重采樣 ( 初始化音頻重采樣上下文 SwrContext | 計算音頻延遲 | 計算輸出樣本個數 | 音頻重采樣 swr_convert ) ;
IV . OpenSL ES 播放代碼 ( 詳細注釋 )
OpenSL ES 播放部分的代碼 : 細節內容看注釋吧 , 不再展開一條一條的寫了 ;
// I . 創建 OpenSLES 引擎并獲取引擎的接口 ( 相關代碼拷貝自 Google 官方示例 native-audio )// 參考 : https://github.com/android/ndk-samples/blob/master/native-audio/app/src/main/cpp/native-audio-jni.c//聲明每個方法執行的返回結果 , 一般情況下返回 SL_RESULT_SUCCESS 即執行成功// 該類型本質是 int 類型 , 定義的是各種類型的異常SLresult result;// 創建引擎result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);// 返回 0 成功 , 否則失敗 , 一旦失敗就中斷退出assert(SL_RESULT_SUCCESS == result);(void)result;// 實現引擎result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);assert(SL_RESULT_SUCCESS == result);(void)result;// 獲取引擎接口 , 使用該接口創建輸出混音器 , 音頻播放器等其它對象// 引擎對象不提供任何調用的方法 , 引擎調用的方法都定義在接口中result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);assert(SL_RESULT_SUCCESS == result);(void)result;// II . 設置輸出混音器// 輸出聲音 , 添加各種音效 ( 混響 , 重低音 , 環繞音 , 均衡器 等 ) , 都要通過混音器實現 ;// 創建輸出混音器對象 , 可以指定一個混響效果參數 ( 該混淆參數可選 )const SLInterfaceID ids_engine[1] = {SL_IID_ENVIRONMENTALREVERB};const SLboolean req_engine[1] = {SL_BOOLEAN_FALSE};result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids_engine, req_engine);assert(SL_RESULT_SUCCESS == result);(void)result;// 實現輸出混音器result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);assert(SL_RESULT_SUCCESS == result);(void)result;// III . 獲取混響接口 并 設置混響 ( 可能會失敗 )// 獲取環境混響接口// 如果環境混響效果不可用 , 該操作可能失敗// either because the feature is not present, excessive CPU load, or// the required MODIFY_AUDIO_SETTINGS permission was not requested and grantedresult = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,&outputMixEnvironmentalReverb);if (SL_RESULT_SUCCESS == result) {result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb, &reverbSettings);(void)result;}//IV . 配置音源輸入// 配置要播放的音頻輸入緩沖隊列屬性參數 , 緩沖區大小 , 音頻格式 , 采樣率 , 樣本位數 , 通道數 , 樣本大小端格式SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};/*typedef struct SLDataFormat_PCM_ {SLuint32 formatType; //數據格式 SL_DATAFORMAT_PCMSLuint32 numChannels; //通道數 , 左右聲道 2個 2SLuint32 samplesPerSec; //采樣率 44100Hz SL_SAMPLINGRATE_44_1SLuint32 bitsPerSample; //采樣位數 16位 SL_PCMSAMPLEFORMAT_FIXED_16SLuint32 containerSize; //容器大小 SL_PCMSAMPLEFORMAT_FIXED_16SLuint32 channelMask; //通道 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHTSLuint32 endianness; //小端格式 SL_BYTEORDER_LITTLEENDIAN} SLDataFormat_PCM;*/SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, //PCM 格式2, //兩個聲道SL_SAMPLINGRATE_44_1, //采樣率 44100 HzSL_PCMSAMPLEFORMAT_FIXED_16, //采樣位數 16位SL_PCMSAMPLEFORMAT_FIXED_16, //容器為 16 位SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, //左右雙聲道SL_BYTEORDER_LITTLEENDIAN}; //小端格式// 設置音頻數據源 , 配置緩沖區 ( loc_bufq ) 與 音頻格式 (format_pcm)SLDataSource audioSrc = {&loc_bufq, &format_pcm};// V . 配置音頻輸出// 配置混音器 : 將 outputMixObject 混音器對象裝載入 SLDataLocator_OutputMix 結構體中SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};// 將 SLDataLocator_OutputMix 結構體裝載到 SLDataSink 中// 音頻輸出通過 loc_outmix 輸出 , 實際上是通過 outputMixObject 混音器對象輸出的SLDataSink audioSnk = {&loc_outmix, NULL};// VI . 創建并實現播放器/** 創建音頻播放器:* 如果需要效果器時 , 不支持高性能音頻* ( fast audio does not support when SL_IID_EFFECTSEND is required, skip it* for fast audio case )*/// 操作隊列接口 , 如果需要 特效接口 , 添加 SL_IID_EFFECTSENDconst SLInterfaceID ids_player[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_EFFECTSEND,/*SL_IID_MUTESOLO,*/};const SLboolean req_player[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,/*SL_BOOLEAN_TRUE,*/ };// 創建播放器result = (*engineEngine)->CreateAudioPlayer(engineEngine,&bqPlayerObject,&audioSrc, //音頻輸入&audioSnk, //音頻商戶處bqPlayerSampleRate? 2 : 3,//ids_player,req_player);assert(SL_RESULT_SUCCESS == result);(void)result;// 創建播放器對象result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);assert(SL_RESULT_SUCCESS == result);(void)result;// VII . 獲取播放器接口 和 緩沖隊列接口// 獲取播放器 Player 接口 : 該接口用于設置播放器狀態 , 開始 暫停 停止 播放 等操作result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);assert(SL_RESULT_SUCCESS == result);(void)result;// 獲取播放器 緩沖隊列 接口 : 該接口用于控制 音頻 緩沖區數據 播放result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,&bqPlayerBufferQueue);assert(SL_RESULT_SUCCESS == result);(void)result;// VIII . 注冊回調函數// 注冊緩沖區隊列的回調函數 , 每次播放完數據后 , 會自動回調該函數// 傳入參數 this , 就是 bqPlayerCallback 函數中的 context 參數result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);assert(SL_RESULT_SUCCESS == result);(void)result;// IX . 獲取效果器接口 和 音量控制接口 ( 不是必須的 )// 獲取效果器發送接口 ( get the effect send interface )bqPlayerEffectSend = NULL;if( 0 == bqPlayerSampleRate) {result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND,&bqPlayerEffectSend);assert(SL_RESULT_SUCCESS == result);(void)result;}#if 0 // mute/solo is not supported for sources that are known to be mono, as this is// get the mute/solo interfaceresult = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_MUTESOLO, &bqPlayerMuteSolo);assert(SL_RESULT_SUCCESS == result);(void)result; #endif// 獲取音量控制接口// 獲取音量控制接口 ( get the volume interface ) [ 如果需要調節音量可以獲取該接口 ]result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);assert(SL_RESULT_SUCCESS == result);(void)result;// X . 設置播放狀態// 設置播放器正在播放狀態 ( set the player's state to playing )result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);assert(SL_RESULT_SUCCESS == result);(void)result;// XI. 手動調用激活回調函數// 手動激活 , 手動調用一次 bqPlayerCallback 回調函數bqPlayerCallback(bqPlayerBufferQueue, this);IV . OpenSLES slAndroidSimpleBufferQueueCallback 回調函數聲明及實現代碼
1 . 回調函數原型 :
typedef void (SLAPIENTRY *slAndroidSimpleBufferQueueCallback)(SLAndroidSimpleBufferQueueItf caller,void *pContext );2 . 回調函數聲明及實現 :
//每當緩沖數據播放完畢后 , 會自動回調該回調函數 // this callback handler is called every time a buffer finishes playing void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {//獲取 PCM 采樣數據 , 將重采樣的數據放到 data 中int data_size ; //進行 FFMPEG 音頻重采樣 ... 大塊代碼參考上一篇博客 //開始播放if ( data_size > 0 ){//通過播放器隊列接口 , 將 PCM 數據加入到該隊列緩沖區后 , 就會自動播放這段音頻// 注意 , 最后一個參數是樣本字節數(*bq)->Enqueue(bq, audioChannel->data, data_size);}}3 . 回調函數注冊 :
// VIII . 注冊回調函數// 注冊緩沖區隊列的回調函數 , 每次播放完數據后 , 會自動回調該函數// 傳入參數 this , 就是 bqPlayerCallback 函數中的 context 參數result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);assert(SL_RESULT_SUCCESS == result);(void)result;總結
以上是生活随笔為你收集整理的【Android FFMPEG 开发】OpenSLES 播放音频 ( 创建引擎 | 输出混音设置 | 配置输入输出 | 创建播放器 | 获取播放/队列接口 | 回调函数 | 开始播放 | 激活回调 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Flutter】Flutter 应用创
- 下一篇: 【Flutter】Dart 数据类型 字