IjkPlayer 學習筆記
筆記可能微亂,但大致清晰,可能會對他人有所幫助,故分享出來。
×××××××××××××××××××目錄×××××××××××××××××××
ijk概述
mediacodec相關
OpenGL相關
filter相關
setOption配置相關
metadata相關
h264編碼器特有的設定域
執行緒相關
訊息機制
音訊輸出
聲道切換
SDL_CreateCond 與 SDL_CreateThreadEx
如何暫停
×××××××××××××××××××××××××××××××××××××××××
ijk概述
《零基礎讀懂視訊播放器控制原理: ffplay 播放器原始碼分析》:https://cloud.tencent.com/developer/article/1004559
《基於 ffmpeg 的跨平臺播放器實現》:https://cloud.tencent.com/developer/article/1004561
《ijkplayer框架深入剖析》:https://www.jianshu.com/p/daf0a61cc1e0
三種播放器實現: 均繼承自 AbstractMediaPlayer 繼承自 IMediaPlayer
1.AndroidMediaPlayer:基於安卓自帶播放器(位於ijkplayer-java)
2.IjkExoMediaPlayer:基於ExoPlayer(位於ijkplayer-exo)
介紹:http://blog.csdn.net/u014606081/article/details/76181049
3.IjkMediaPlayer:基於ffplay(位於ijkplayer-java,底層實現在ijkmedia目錄)
輸出:
video-output: NativeWindow, OpenGL ES 2.0
audio-output: AudioTrack, OpenSL ES
jni底層介面:
IjkMediaPlayer : ijkmedia/ijkplayer/android/ijkplayer_jni.c
播放器結構體:VideoState(ff_ffplay_def.c )
播放入口:
ffplay.c : ffp_prepare_async_l
stream_open :建立音視訊解碼前後佇列, 建立資料讀取(read_thread)和視訊顯示執行緒(video_refresh_thread)
1.read_thread:讀取packet
stream_component_open: 開啟視訊、音訊解碼器。在此會開啟相應解碼器,並建立相應的解碼執行緒
av_read_frame:讀取媒體資料,得到的是音視訊分離的解碼前資料
packet_queue_put:往緩衝佇列中放入解碼前的音、視、字幕 packet
開啟視訊解碼器:
ffplay.c : stream_component_open
ffpipeline_open_video_decoder
ff_ffpipeline.c : ffpipeline_open_video_decoder 呼叫 IJKFF_Pipeline->func_open_video_decoder
IJKFF_Pipeline->func_open_video_decoder 函式指標指向 android/pipeline/cffpipeline_android.c 的 func_open_video_decoder 方法
然後呼叫 android/pipeline/ffpipenode_android_mediacodec_vdec.c的 ffpipenode_create_video_decoder_from_android_mediacodec
2.video_thread:解碼
ff_ffpipenode.c : ffpipenode_run_sync
/android/pipeline/ffpipenode_android_mediacodec_vdec.c: func_run_sync解碼後的幀加入幀緩衝佇列
IJKFF_PipenodeIJKFF_Pipenode_Opaque
視訊顯示:
video_refresh_thread
video_refresh
frame_queue_peek_last
video_display2
video_image_display2
ijksdl/ijksdl_vout.c : SDL_VoutDisplayYUVOverlay
ijksdl_vout_android_nativewindow.cvout->display_overlay = func_display_overlay
func_display_overlay
func_display_overlay_l
音視訊同步:
video_refresh
mediacodec相關
1.為什麼ijk硬解不用ffmpeg自帶的mediacodec_wrapper,而是自己在底層封裝的java api
https://github.com/Bilibili/ijkplayer/issues/1705
https://github.com/Bilibili/ijkplayer/issues/1557
FFmpeg3.1中也集成了MediaCodec硬體解碼
ijkplayer doesn't use ffmpeg's mediacodec implement
ijkplayer has its own mediacodec implement.
OpenGL相關
1.Android MediaCodec and OpenGL Render
https://github.com/Bilibili/ijkplayer/issues/2893
2.ijk android是支援opengl的
https://github.com/Bilibili/ijkplayer/issues/338ijksdl/gles2/fsh/下指令碼可修改gl渲染效果
問題:
播放時軟解解碼出的為yuv420p格式,可通過gl渲染
播放時硬解解碼出不能通過gl渲染,視訊幀格式為 QCOM_FORMATYUV420PackedSemiPlanar32m
分析:
不同裝置硬解出的視訊幀格式不同:
比如紅米裝置上硬解出的幀格式為:YUV420PackedSemiPlanar32m
ijk中的gl渲染只支援yuv420、rgb等,需要把 YUV420PackedSemiPlanar32m 轉為 YUV 才可以
轉換演算法:http://blog.csdn.net/u011270282/article/details/50698243
解決:
播放時的解碼速度並不嚴格要求,先用軟解,軟解解出的為yuv420p,可以直接gl渲染
遺留優化項:
若採用軟解播放則進行記憶體比較,若硬解較優,則硬解後將視訊幀轉換為gl可以渲染的格式,當然也要考慮轉換的時間消耗
filter相關
1.Does ijkplayer support video filters , ijk支援java層直接設定濾鏡!
https://github.com/Bilibili/ijkplayer/issues/3686setOption配置相關
mIjkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0);
"mediacodec"等配置項位於:ff_ffplayer_options.h以及option_table.h
如何利用 AVDictionary 配置引數 :http://blog.csdn.net/encoder1234/article/details/54582676
AVClass 與 AVOption :http://blog.csdn.net/leixiaohua1020/article/details/44268323
AVClass就是AVOption和目標結構體之間的“橋樑”。
AVClass中儲存了AVOption型別的陣列option,用於儲存選項資訊。
AVClass有一個特點就是它必須位於其支援的結構體的第一個位置
metadata相關
對於metadata的操作封裝於 ijkmeta.c , metadata資訊宣告在ijkmeta.h中, java層對應IjkMediaMeta.java
IjkMediaMeta分兩層,外層metadata,內層:video、audio、subtitle三個metadata,共4個metadata
顯然 ijkmeta.c 中只針對播放流程,也就是隻負責從視訊中獲取 metadata ,然後在播放時獲取 metadata 資訊
儲存時應該將metadata資訊寫入視訊,首先要搞清楚需要寫入哪些metadata資訊?
應該是:凡是播放時需要獲取的都需要寫入,這樣才能正常播放
How to set header metadata to encoded video?
https://stackoverflow.com/questions/17024192/how-to-set-header-metadata-to-encoded-video
avformat.h有對metadata的用法介紹
h264編碼器特有的設定域
具體見:libx264.coptions
/* priv_data 屬於每個編碼器特有的設定域,用 av_opt_set 設定*/ /** *preset : 編碼模式 * ultrafast,superfast, veryfast, faster, fast, medium, slow, slower, veryslow * fast 節省約 10% encoding time10s視訊100s * faster 25% * ultrafast 55% 10s視訊16s * 但越快質量越低 */ av_opt_set(enc_ctx->priv_data, "preset", "ultrafast", 0); /** * lookahead:編碼位元速率控制所需要鎖定的幀個數 */ av_opt_set(enc_ctx->priv_data, "lookahead", "0", 0); /** * 使用2pass編碼模式 * 1pass和2pass的區別在於1pass只需要編碼一次, * 2pass需要編碼兩次。2pass的優點在於可編碼更小的檔案,缺點在於所花費時間比1pass更多 */ av_opt_set(enc_ctx->priv_data, "2pass", "0", 0); /** * 無延時輸出 */ av_opt_set(enc_ctx->priv_data, "zerolatency", "1", 0);
執行緒相關
ijk執行緒相關操作位於ijksdl_thread.c,封裝了pthread庫
以解碼為例:
d->decoder_tid = SDL_CreateThreadEx(&d->_decoder_tid, fn, arg, name);
在 SDL_CreateThreadEx 方法其實是呼叫 pthread_create 方法
ijk 封裝了執行緒的初始化和銷燬操作,見 ijksdl_thread.c 的 SDL_RunThread方法
訊息機制
native_setup 時設定迴圈讀訊息函式, mp->msg_loop = msg_loop ,初始化訊息佇列在 FFPlayer 中
在 ijkmp_prepare_async_l 時建立執行緒執行迴圈讀訊息函式
音訊輸出
新建播放器例項(native_setup)時,根據ffp->opensles 初始化 opensles 或 androidTrack 輸出裝置
在 opensles 或 androidTrack 中,通過回撥 sdl_audio_callback 方法從音訊幀緩衝佇列中讀取音訊資料,播放之
比如在 ijksdl_aout_android_opensles.c 中 audio_cblk 就是 sdl_audio_callback 方法
聲道切換
ijkplayer如何切換音軌,以及獲取音軌資訊:https://github.com/Bilibili/ijkplayer/issues/3811
IjkMediaPlayer.java 跟軌道相關的方法 :
getTrackInfo(獲取所有軌道)、getSelectedTrack(獲取當前軌道)、selectTrack(選擇軌道)
selectTrack(選擇軌道)可實現切換軌道操作
對應呼叫 ff_ffplayer.c 的 ffp_set_stream_selected 方法
此方法中通過呼叫 stream_component_close、stream_component_open 實現軌道切換
SDL_CreateCond 與 SDL_CreateThreadEx
ff_ffplay.c 中有一句程式碼: is->continue_read_thread = SDL_CreateCond()
乍一看變數名會以為 SDL_CreateCond 方法是用來建立執行緒...不對! ,SDL_CreateThreadEx 才是用來建立執行緒的
SDL_CreateCond 方法呼叫了 pthread_cond_init 方法,含義為:建立條件變數
條件變數相關:
1.初始化條件變數 pthread_cond_init
2.阻塞在條件變數上pthread_cond_wait
3.解除在條件變數上的阻塞pthread_cond_signal
4.阻塞直到指定時間pthread_cond_timedwait
5.釋放阻塞的所有執行緒pthread_cond_broadcast
6.釋放條件變數pthread_cond_destroy
詳細:https://blog.csdn.net/ithomer/article/details/6031723
ijksdl_mutex.c 中封裝了互斥鎖相關操作
如何暫停
IjkMediaPlayer.java 中的 _pause 本地方法呼叫 ijkplayer_jni.c 中的 IjkMediaPlayer_pause
然後通過ijk的訊息機制傳送暫停訊號: ffp_notify_msg1(mp->ffplayer, FFP_REQ_PAUSE);
然後呼叫到 ff_ffplay.c 的 ffp_pause_l 方法,最後呼叫到 stream_toggle_pause_l 方法
其中兩個關鍵操作:1.is->paused 置 true ; 2.呼叫 SDL_AoutPauseAudio 方法
其實在讀包、解碼、渲染/播放這個工作線中,只要有一處停止工作,其他地方自然會阻塞住即可,
但是看程式碼發現多處都有根據is->paused來停止工作的邏輯,列一下自認為比較關鍵的地方:
對於視訊:在視訊渲染執行緒的 video_refresh 方法中:
if (is->paused) goto display;
如果暫停了,就一直顯示當前幀,跳過後面的取下一幀操作,不取下一幀,幀佇列滿後自然就會停止解碼、讀包操作。
對於音訊: 呼叫 SDL_AoutPauseAudio 方法後不再回調 sdl_audio_callback 方法(參見上面的《音訊輸出》)