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源代碼分析 (三)