FFMPEG 3.4.2 - ffplay源代碼分析 (一)

1.例子

這里的說明使用如下的例子:

./ffplay avm.mp4

2. read_thread()

線程read_thread負(fù)責(zé)demux端姚,它的流程如下圖:

  • avformat_alloc_context分配AVFormatContext梨水。這是demux的上下文惠呼;
  • avformat_open_input()解析文件两芳,確定文件的封裝格式(即mux類型)宋舷;
  • aformat_find_stream_info()繼續(xù)解析AVFormatContext中包含的stream状植,根據(jù)stream類型確定其decoder,并創(chuàng)建AVCodecContext颁井,這是decode的上下文;
  • 如果指定了播放位置蠢护,avformat_seek_file()將播放位置移動(dòng)到指定位置雅宾;
  • av_find_best_stream()查找指定stream類型的最佳質(zhì)量的stream。
  • stream_component_open()創(chuàng)建新的線程video_thread。Video_thread負(fù)責(zé)decode。
  • 最后暖途,read_thread在循環(huán)中,調(diào)用av_read_frame()讀packet蜀变,并調(diào)用packet_queue_put(),放入queue中蘸劈,供video_thread讀取昏苏。

2.1 avformat_alloc_context()

avformat_alloc_context調(diào)用avformat_get_context_defaults()給AVFormatContext設(shè)置缺省參數(shù),其中包括AVFormtContext::io_open()和AVFormatContext::io_close()威沫。

2.2 avformat_open_input() - input_input() - io_open_default()

avformat_open_input()調(diào)用av_probe_input_format2()確定文件的封裝格式贤惯,這點(diǎn)后面再提。這里先看io_open_default()如何打開要播放的鏈接棒掠。有兩個(gè)層次的Context:AVIOContext和URLContext孵构。ffio_xxx()負(fù)責(zé)IO層,ffurl_xxx()負(fù)責(zé)URL層烟很。

  • url_find_protocol()首先提取播放鏈接的前綴颈墅。如http://.../a.mp4,前綴就是http雾袱。這里只有文件名avm.mp4恤筛,沒有前綴,則默認(rèn)為“file”芹橡,所以avm.mp4與file:avm.mp4是等同的毒坛。
  • ffurl_get_protocols得到已注冊(cè)的URL protocol列表。url_find_protocol()根據(jù)前綴在這個(gè)列表中查找林说。前綴”file”對(duì)應(yīng)的是ff_file_protocol煎殷。
  • ffurl_alloc()調(diào)用url_alloc_for_protocol()分配相應(yīng)的URLContext。URLContext保存真正的文件句柄fd腿箩。
  • ffurl_connect()調(diào)用file_open()打開文件豪直,調(diào)用file_seek()將開始位置調(diào)整到0。
  • ffio_fdopen()調(diào)用avio_alloc_context()分配IOContext珠移,然后初始化它弓乙。

2.3 avformat_open_input() - input_input() - av_probe_input_buffer2()

這里的重要函數(shù)是av_probe_input_format3()末融。它根據(jù)文件名及文件內(nèi)容確定文件封裝格式。它有兩種工作模式唆貌,根據(jù)參數(shù)is_opened值決定滑潘,is_opened表示文件是否已經(jīng)打開垢乙。

AVInputFormat *av_probe_input_format3 (AVProbeData *pd, int is_opened,  int* score_ret);

如果is_opened為false锨咙,則av_probe_input_format3()只根據(jù)文件名查找AVInputFormat,否則追逮,av_probe_input_format3()調(diào)用AVInputFormat::read_probe()酪刀,根據(jù)文件內(nèi)容的頭部判斷。對(duì)于mp4钮孵,其read_probe()是mov_probe()骂倘。
av_probe_input_format3()的第一個(gè)參數(shù)pd保存了文件名和文件頭部的內(nèi)容。 avInputFormat可能有多種選擇巴席,它們的優(yōu)先級(jí)不同历涝,用score表示。av_probe_input_format3()返回優(yōu)先級(jí)最高的漾唉,優(yōu)先級(jí)保存在第3個(gè)參數(shù)score_ret中荧库。

init_input()第一次調(diào)用av_probe_input_format2()。這時(shí)文件沒打開赵刑,根據(jù)文件名字找查找封裝格式分衫,沒找到。av_probe_input_buffer2()先調(diào)用avio_read()讀文件內(nèi)容頭部分般此,再第二次調(diào)用av_probe_input_format2()蚪战,這次mov_probe()返回的AVInputFormat是ff_mov_demuxer。

avio_read()調(diào)用IO層的io_read_packet()铐懊,和URL層的file_read()讀取指定大小的文件內(nèi)容邀桑。

如下是以上過程涉及的類:

2.4 avformat_open_input() - AVInputFormat::read_header()

avformat_open_input()先調(diào)用avio_skip()跳過指定字節(jié),這里是0字節(jié)科乎。接著調(diào)用AVInputFormat::reader_header()分析文件頭部壁畸。這里調(diào)用的是mov_read_header(),它會(huì)創(chuàng)建MOV層的上下文MovContext喜喂。

2.5 avformat_open_input() - avformat_find_stream_info()

avformat_find_stream_info()調(diào)用av_parser_init()瓤摧,為每個(gè)Stream找到AVCodecParserContext,這是用來從連續(xù)的stream數(shù)據(jù)中分割frame的玉吁。
avcodec_parameters_to_context()將Codec上下文從內(nèi)部使用的結(jié)構(gòu)AVCodecParameters照弥,復(fù)制到作為對(duì)外接口的AVCodecContext中。
find_probe_decoder()根據(jù)codec_id查找解碼器AVCodec进副,這里調(diào)用avcodec_find_decoder_by_name()这揣,根據(jù)名字在已注冊(cè)的Codec列表中找到ff_h264_decoder悔常。
avcodec_open2()調(diào)用AVCodec::init()初始Codec的上下文。這里是h264_decode_init()創(chuàng)建H264Context并初始化给赞。

以上過程涉及到的類如下:

2.6 avformat_open_input() - avformat_seek_file()

avformat_seek_file()調(diào)整播放位置机打。av_rescale()將對(duì)外使用的時(shí)間單位轉(zhuǎn)換成內(nèi)部使用的。位置調(diào)整之后片迅,之前的frame就不需要了残邀。ff_read_frame_flush()用于清除之前的frame。最后調(diào)用mov_read_seek()調(diào)整位置柑蛇。

2.7 avformat_open_input() - av_find_best_stream()

可能包括多個(gè)同類型的stream芥挣,av_find_best_stream()為每個(gè)類型選出最好的一個(gè)stream。

2.8 avformat_open_input() - stream_component_open()

stream_component_open()首先創(chuàng)建avcodec_alloc_context3()創(chuàng)建AVCodecContext并初始化耻台,調(diào)用avcodec_parameters_to_context()復(fù)制內(nèi)部參數(shù)空免,調(diào)用avcodec_find_decoder()找到decoder,調(diào)用avcodec_open2()初始化AVCodecContext盆耽。

最后是應(yīng)用層面的數(shù)據(jù)結(jié)構(gòu)VideoState蹋砚,用decoder_init()初始化。在decoder_start()中摄杂,用packet_queue_start()啟動(dòng)packet queue坝咐,用SDL_CreateThread()啟動(dòng)新線程video_thread。

2.9 avformat_open_input() - av_read_frame()

read_thread在一個(gè)循環(huán)中調(diào)用av_read_frame()讀packet匙姜,調(diào)用packet_queue_put()寫入packet queue畅厢。
av_read_frame調(diào)用read_frame_internal(),最終調(diào)用mov_read_packet()讀取packet氮昧。它可能將packet返回框杜,也可能暫時(shí)保存在AVFormatContext::AVFormatInternal::packet_buffer中。在后一種情況下袖肥,下一次調(diào)用av_read_frame()會(huì)返回暫存的packet咪辱。

涉及到的類如下圖:

3.video_thread()

線程video_thread負(fù)責(zé)decode,它在循環(huán)中調(diào)用get_video_frame()從packet queue中讀packet椎组,decode為frame油狂,然后調(diào)用queue_picture()將frame推入frame queue。流程如下圖:

3.1 get_video_frame()

decoder_decoder_frame()負(fù)責(zé)decode frame寸癌。packet_queue_get()從packet queue中取出packet专筷。
在avcodec_send_packet()中,實(shí)際負(fù)責(zé)的是decode_receive_frame_internal()蒸苇,它最終調(diào)用h264_decode_frame()磷蛹。decode后的frame可能返回,也可能暫存在AVCodecInternal::buffer_frame中溪烤。在后一種情況下味咳,下一次調(diào)用decode_receive_frame_internal()會(huì)直接返回暫存的frame庇勃。

3.2 queue_picture()

queue_picture()將decode得到的frame寫入frame queue。frame_queue_peek_writable()得到可寫的位置槽驶,frame_queue_push()將寫位置推前一步责嚷。

涉及的類如下圖:

4. main thread / display thread

main thread首先做初始化工作,然后調(diào)用event_loop()進(jìn)入display階段掂铐。

初始化工作包括:

  • av_register_all()注冊(cè)ffmpeg的各種庫(kù)罕拂,如demux,decode等堡纬。
  • parse_options()解析命令行
  • SDL_Init()初始化SDL聂受,SDL_CreateWindow()創(chuàng)建SDL窗口,SDL_CreateRenderer()創(chuàng)建SDL渲染對(duì)象烤镐。SDL_GetRenderInfo()得到渲染對(duì)象的信息。
  • stream_open()調(diào)用frame_queue_init()初始化packet queue棍鳖,調(diào)用frame_queue_init()初始化frame queue炮叶,調(diào)用init_clock()初始化同步時(shí)鐘。最后調(diào)用SDL_CreateThread()創(chuàng)建和啟動(dòng)新的線程read_thread渡处。

4.1 event_loop()

在refresh_loop_wait_event()中镜悉,再循環(huán)中最多0.01秒調(diào)用一次video_refresh()。Video_refresh()讀取frame并刷新顯示医瘫。
frame_queue_nb_remaining()得到frame_queue中的frame數(shù)目侣肄;
frame_queue_peek_last()和frame_queue_peek()分別得到frame queue中的最后兩個(gè)frame。Frame_queue_next()將讀位置推進(jìn)一步醇份。
video_display()顯示frame稼锅。
調(diào)用SDL_PumpEvents()和SDL_PeepEvents()得到SDL event。這是SDL的要求僚纷,不然SDL會(huì)以為用戶無動(dòng)作矩距,所以將SDL窗口變暗。SDL Event返回給refresh_loop_wait_event()怖竭,在這里event锥债,如用戶輸入,會(huì)得到處理痊臭,如調(diào)整播放位置哮肚,改變音量等等。

4.2 event_loop() - video_display()

video_display()幾乎只與SDL有關(guān)广匙。

  • video_open()設(shè)置SDL窗口允趟。
  • SDL_SetRenderDrawColor()和SDL_SetRenderClear()清理渲染器的背景。
  • video_image_display()調(diào)用frame_queue_peek_last()從frame queue中得到最后一個(gè)frame艇潭,調(diào)用calculate_display_rect()計(jì)算顯示范圍拼窥,調(diào)用upload_texture()創(chuàng)建紋理并將frame中的YUV分量戏蔑,渲染到紋理中,調(diào)用SDL_RenderCopyEx()將紋理復(fù)制到渲染器鲁纠。
  • SDL_RenderPresent()顯示渲染器中的內(nèi)容总棵。

相關(guān)鏈接

FFMPEG 3.4.2 - ffmpeg源代碼分析 (一)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (二)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (三)
FFMPEG 3.4.2 - ffmpeg源代碼分析 (四)- x264
FFMPEG 3.4.2 - ffplay源代碼分析 (一)
FFMPEG 3.4.2 - ffplay源代碼分析 (二)
FFMPEG 3.4.2 - ffplay源代碼分析 (三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市改含,隨后出現(xiàn)的幾起案子情龄,更是在濱河造成了極大的恐慌,老刑警劉巖捍壤,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骤视,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡鹃觉,警方通過查閱死者的電腦和手機(jī)专酗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盗扇,“玉大人祷肯,你說我怎么就攤上這事×屏ィ” “怎么了佑笋?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)斑鼻。 經(jīng)常有香客問我蒋纬,道長(zhǎng),這世上最難降的妖魔是什么坚弱? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任蜀备,我火速辦了婚禮,結(jié)果婚禮上史汗,老公的妹妹穿的比我還像新娘琼掠。我一直安慰自己,他們只是感情好停撞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布瓷蛙。 她就那樣靜靜地躺著,像睡著了一般戈毒。 火紅的嫁衣襯著肌膚如雪艰猬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天埋市,我揣著相機(jī)與錄音冠桃,去河邊找鬼。 笑死道宅,一個(gè)胖子當(dāng)著我的面吹牛食听,可吹牛的內(nèi)容都是我干的胸蛛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼樱报,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼葬项!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迹蛤,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤民珍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后盗飒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚷量,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年逆趣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝶溶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汗贫,死狀恐怖身坐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情落包,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布摊唇,位于F島的核電站咐蝇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏巷查。R本人自食惡果不足惜有序,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岛请。 院中可真熱鬧旭寿,春花似錦、人聲如沸崇败。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽后室。三九已至缩膝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岸霹,已是汗流浹背疾层。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贡避,地道東北人痛黎。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓予弧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親湖饱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掖蛤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容