音視頻流媒體開發(fā)【二十四】ffplay播放器-數(shù)據(jù)讀取線程

音視頻流媒體開發(fā)-目錄

4 數(shù)據(jù)讀取線程

從ffplay框架分析我們可以看到赖临,ffplay有專?的線程read_thread()讀取數(shù)據(jù),且在調(diào)?av_read_frame讀取數(shù)據(jù)包之前需要做例如打開?件卡骂,查找配置解碼器翠忠,初始化?視頻輸出等準(zhǔn)備階段,主要包括三?步驟:

  • 準(zhǔn)備?作
  • For循環(huán)讀取數(shù)據(jù)
  • 退出線程處理
? 準(zhǔn)備?作
  1. avformat_alloc_context 創(chuàng)建上下?
  2. ic->interrupt_callback.callback = decode_interrupt_cb;
  3. avformat_open_input打開媒體?件
  4. avformat_find_stream_info 讀取媒體?件的包獲取更多的stream信息
  5. 檢測是否指定播放起始時間鬓照,如果指定時間則seek到指定位置avformat_seek_file
  6. 查找查找AVStream熔酷,講對應(yīng)的index值記錄到st_index[AVMEDIA_TYPE_NB];
    a. 根據(jù)?戶指定來查找流avformat_match_stream_specifier
    b. 使?av_find_best_stream查找流
  7. 從待處理流中獲取相關(guān)參數(shù)豺裆,設(shè)置顯示窗?的寬度拒秘、?度及寬??
  8. stream_component_open打開?頻、視頻臭猜、字幕解碼器躺酒,并創(chuàng)建相應(yīng)的解碼線程以及進(jìn)?對應(yīng)輸出參數(shù)的初始化。
? For循環(huán)讀取數(shù)據(jù)
  1. 檢測是否退出
  2. 檢測是否暫停/繼續(xù)
  3. 檢測是否需要seek
  4. 檢測video是否為attached_pic
  5. 檢測隊列是否已經(jīng)有?夠數(shù)據(jù)
  6. 檢測碼流是否已經(jīng)播放結(jié)束
    a. 是否循環(huán)播放
    b. 是否?動退出
  7. 使?av_read_frame讀取數(shù)據(jù)包
  8. 檢測數(shù)據(jù)是否讀取完畢
  9. 檢測是否在播放范圍內(nèi)
  10. 到這步才將數(shù)據(jù)插?對應(yīng)的隊列
三 退出線程處理
  1. 如果解復(fù)?器有打開則關(guān)閉avformat_close_input
  2. 調(diào)?SDL_PushEvent發(fā)送退出事件FF_QUIT_EVENT
  3. 消耗互斥量wait_mutex

4.1 準(zhǔn)備?作

準(zhǔn)備?作主要包括以下步驟:

  1. avformat_alloc_context 創(chuàng)建上下?
  2. ic->interrupt_callback.callback = decode_interrupt_cb;
  3. avformat_open_input打開媒體?件
  4. avformat_find_stream_info 讀取媒體?件的包獲取更多的stream信息
  5. 檢測是否指定播放起始時間蔑歌,如果指定時間則seek到指定位置avformat_seek_file
  6. 查找查找AVStream羹应,講對應(yīng)的index值記錄到st_index[AVMEDIA_TYPE_NB];
    a. 根據(jù)?戶指定來查找流avformat_match_stream_specifier
    b. 使?av_find_best_stream查找流
  7. 通過AVCodecParameters和av_guess_sample_aspect_ratio計算出顯示窗?的寬次屠、?
  8. stream_component_open打開?頻园匹、視頻、字幕解碼器帅矗,并創(chuàng)建相應(yīng)的解碼線程以及進(jìn)?對應(yīng)輸出參數(shù)的初始化偎肃。

1 avformat_alloc_context 創(chuàng)建上下?

調(diào)?avformat_alloc_context創(chuàng)建解復(fù)?器上下?

1 // 1\. 創(chuàng)建上下?結(jié)構(gòu)體,這個結(jié)構(gòu)體是最上層的結(jié)構(gòu)體浑此,表示輸?上下?
2 ic = avformat_alloc_context();

最終該ic 賦值給VideoState的ic變量

1 is->ic = ic; // videoState的ic指向分配的ic

2 ic->interrupt_callback

1 /* 2.設(shè)置中斷回調(diào)函數(shù)累颂,如果出錯或者退出,就根據(jù)?前程序設(shè)置的狀態(tài)選擇繼續(xù)check或者直接退出 */
2 /* 當(dāng)執(zhí)?耗時操作時(?般是在執(zhí)?while或者for循環(huán)的數(shù)據(jù)讀取時),會調(diào)?interrupt_callback.callback
3 * 回調(diào)函數(shù)中返回1則代表ffmpeg結(jié)束耗時操作退出當(dāng)前函數(shù)的調(diào)?
4 * 回調(diào)函數(shù)中返回0則代表ffmpeg內(nèi)部繼續(xù)執(zhí)?耗時操作紊馏,直到完成既定的任務(wù)(?如讀取到既定的數(shù)據(jù)包)
5 */
6 ic->interrupt_callback.callback = decode_interrupt_cb;
7 ic->interrupt_callback.opaque = is;

interrupt_callback?于ffmpeg內(nèi)部在執(zhí)?耗時操作時檢查調(diào)?者是否有退出請求料饥,避免?戶退出請求沒有及時響應(yīng)。

怎么去測試在哪?觸發(fā)朱监?

在ubuntu使?gdb進(jìn)?調(diào)試:我們之前講的在ubuntu下編譯ffmpeg岸啡,在lqf@ubuntu:~/ffmpeg_sources/ffmpeg-4.2.1?錄下有ffplay_g,我們可以通過 gdb ./ffplay_g來播放視頻赫编,然后在decode_interrupt_cb打斷點巡蘸。

avformat_open_input的觸發(fā)
1 #0 decode_interrupt_cb (ctx=0x7ffff7e36040) at fftools/ffplay.c:271
2 #1 0x00000000007d99b7 in ff_check_interrupt (cb=0x7fffd00014b0)
3 at libavformat/avio.c:667
4 #2 retry_transfer_wrapper (transfer_func=0x7dd950 <file_read>, size min=1,
5 size=32768, buf=0x7fffd0001700 "", h=0x7fffd0001480)
6 at libavformat/avio.c:374
7 #3 ffurl_read (h=0x7fffd0001480, buf=0x7fffd0001700 "", size=32768)
8 at libavformat/avio.c:411
9 #4 0x000000000068cd9c in read_packet_wrapper (size=<optimized out>,
10 buf=<optimized out>, s=0x7fffd00011c0) at libavformat/aviobuf.c:535
11 #5 fill_buffer (s=0x7fffd00011c0) at libavformat/aviobuf.c:584
12 #6 avio_read (s=s@entry=0x7fffd00011c0, buf=0x7fffd0009710 "",
13 size=size@entry=2048) at libavformat/aviobuf.c:677
14 #7 0x00000000006b7780 in av_probe_input_buffer2 (pb=0x7fffd00011c0,
15 fmt=0x7fffd0000948,
16 filename=filename@entry=0x31d50e0 "source.200kbps.768x320.flv",
17 logctx=logctx@entry=0x7fffd0000940, offset=offset@entry=0,
18 max_probe_size=1048576) at libavformat/format.c:262
19 #8 0x00000000007b631d in init_input (options=0x7fffdd9bcb50,
20 filename=0x31d50e0 "source.200kbps.768x320.flv", s=0x7fffd0000940)
21 at libavformat/utils.c:443
22 #9 avformat_open_input (ps=ps@entry=0x7fffdd9bcbf8,
23 filename=0x31d50e0 "source.200kbps.768x320.flv", fmt=<optimizedout>,

可以看到是在libavformat/avio.c:374?有觸發(fā)到

avformat_find_stream_info的觸發(fā)
1 #0 decode_interrupt_cb (ctx=0x7ffff7e36040) at fftools/ffplay.c:2715
2 #1 0x00000000007b25bc in avformat_find_stream_info (ic=0x7fffd0000940,
3 options=0x0) at libavformat/utils.c:3693
4 #2 0x00000000004a6ea9 in read_thread (arg=0x7ffff7e36040)

從該調(diào)?棧可以看出來 avformat_find_stream_info也會觸發(fā)ic->interrupt_callback的調(diào)?擂送,具體可以看代碼(libavformat/utils.c:3693?)

av_read_frame的觸發(fā)
1 #0 decode_interrupt_cb (ctx=0x7ffff7e36040) at fftools/ffplay.c:271
2 #1 0x00000000007d99b7 in ff_check_interrupt (cb=0x7fffd00014b0)
3 at libavformat/avio.c:667
4 #2 retry_transfer_wrapper (transfer_func=0x7dd950 <file_read>, size_min=1,
5 size=32768, buf=0x7fffd0009710 "FLV\001\005", h=0x7fffd0001480)
6 at libavformat/avio.c:374
7 #3 ffurl_read (h=0x7fffd0001480, buf=0x7fffd0009710 "FLV\001\005",size=32768)
8 at libavformat/avio.c:411
9 #4 0x000000000068cd9c in read_packet_wrapper (size=<optimized out>,
10 buf=<optimized out>, s=0x7fffd00011c0) at libavformat/aviobuf.c:535
11 #5 fill_buffer (s=0x7fffd00011c0) at libavformat/aviobuf.c:584
12 #6 avio_read (s=s@entry=0x7fffd00011c0, buf=0x7fffd00dbf6d "\177",size=45,
13 size@entry=90) at libavformat/aviobuf.c:677
14 #7 0x00000000007a99d5 in append_packet_chunked (s=0x7fffd00011c0,
15 pkt=pkt@entry=0x7fffdd9bca00, size=size@entry=90)
16 at libavformat/utils.c:293
17 #8 0x00000000007aa969 in av_get_packet (s=<optimized out>,
18 pkt=pkt@entry=0x7fffdd9bca00, size=size@entry=90)
19 at libavformat/utils.c:317
20 #9 0x00000000006b350a in flv_read_packet (s=0x7fffd0000940,
21 pkt=0x7fffdd9bca00) at libavformat/flvdec.c:1295
22 #10 0x00000000007aad6d in ff_read_packet (s=s@entry=0x7fffd0000940,
23 pkt=pkt@entry=0x7fffdd9bca00) at libavformat/utils.c:856
24 ---Type <return> to continue, or q <return> to quit---
25 #11 0x00000000007ae291 in read_frame_internal (s=0x7fffd0000940,
26 pkt=0x7fffdd9bcc00) at libavformat/utils.c:1582
27 #12 0x00000000007af422 in av_read_frame (s=0x7fffd0000940,
28 pkt=pkt@entry=0x7fffdd9bcc00) at libavformat/utils.c:1779
29 #13 0x00000000004a68b1 in read_thread (arg=0x7ffff7e36040)
30 at fftools/ffplay.c:3008

這?的觸發(fā)和avformat_open_input?致悦荒,?家可以??跟蹤調(diào)?棧。

3 avformat_open_input()打開媒體?件

函數(shù)原型:

/**
* Open an input stream and read the header. The codecs are not opened.
* The stream must be closed with avformat_close_input().
*
* @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context).
* May be a pointer to NULL, in which case an AVFormatContext is allocated by this
* function and written into ps.
* Note that a user-supplied AVFormatContext will be freed on failure.
* @param url URL of the stream to open.
* @param fmt If non-NULL, this parameter forces a specific input format.
* Otherwise the format is autodetected.
* @param options A dictionary filled with AVFormatContext and demuxer-private options.
* On return this parameter will be destroyed and replaced with a dict containing
* options that were not found. May be NULL.
*
* @return 0 on success, a negative AVERROR on failure.
*
* @note If you want to use custom IO, preallocate the format context and set its pb field.
*/
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat
*fmt, AVDictionary **options);

avformat_open_input?于打開輸??件(對于RTMP/RTSP/HTTP?絡(luò)流也是?樣嘹吨,在ffmpeg內(nèi)部都抽象為URLProtocol搬味,這?描述為?件是為了?便與后續(xù)提到的AVStream的流作區(qū)分),讀取視頻?件的基本信息蟀拷。

需要提到的兩個參數(shù)是fmt和options碰纬。通過fmt可以強(qiáng)制指定視頻?件的封裝,options可以傳遞額外參數(shù)給封裝(AVInputFormat)问芬。

主要代碼:

1 //特定選項處理
2 if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
3     av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
4     scan_all_pmts_set = 1;
5 }
6 /* 3.打開?件悦析,主要是探測協(xié)議類型,如果是?絡(luò)?件則創(chuàng)建?絡(luò)鏈接等 */
7 err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
8 if (err < 0) {
9     print_error(is->filename, err);
10    ret = -1;
11    goto fail;
12 }
13 if (scan_all_pmts_set)
14     av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
15
16 if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))){
17     av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
18     ret = AVERROR_OPTION_NOT_FOUND;
19     goto fail;
20 }

scan_all_pmts是mpegts的?個選項此衅,表示掃描全部的ts流的"Program Map Table"表她按。這?在沒有設(shè)定該選項的時候,強(qiáng)制設(shè)為1炕柔。最后執(zhí)?avformat_open_input。

使?gdb跟蹤options的設(shè)置媒佣,在av_opt_set打斷點

(gdb) b av_opt_set
(gdb) r
#0 av_opt_set_dict2 (obj=obj@entry=0x7fffd0000940,
options=options@entry=0x7fffdd9bcb50, search_flags=search_flags@entry=0)
at libavutil/opt.c:1588
#1 0x00000000011c6837 in av_opt_set_dict (obj=obj@entry=0x7fffd0000940,
options=options@entry=0x7fffdd9bcb50) at libavutil/opt.c:1605
#2 0x00000000007b5f8b in avformat_open_input (ps=ps@entry=0x7fffdd9bcbf8,
filename=0x31d23d0 "source.200kbps.768x320.flv", fmt=<optimized out>,
options=0x2e2d450 <format_opts>) at libavformat/utils.c:560
#3 0x00000000004a70ae in read_thread (arg=0x7ffff7e36040)
at fftools/ffplay.c:2780
......
(gdb) l
1583
1584 if (!options)
1585 return 0;
1586
1587 while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) {
1588 ret = av_opt_set(obj, t->key, t->value, search_flags);
1589 if (ret == AVERROR_OPTION_NOT_FOUND)
1590 ret = av_dict_set(&tmp, t->key, t->value, 0);
1591 if (ret < 0) {
1592 av_log(obj, AV_LOG_ERROR, "Error setting option %s to value %s.\n", t->key, t->value);
(gdb) print **options
$3 = {count = 1, elems = 0x7fffd0001200}
(gdb) print (*options)->elems
$4 = (AVDictionaryEntry *) 0x7fffd0001200
(gdb) print *((*options)->elems)
$5 = {key = 0x7fffd0001130 "scan_all_pmts", value = 0x7fffd0001150 "1"}
(gdb)

參數(shù)的設(shè)置最終都是設(shè)置到對應(yīng)的解復(fù)?器匕累,?如:

mpegts.c

image.png

flvdec.c

image.png

4 avformat_find_stream_info()

在打開了?件后,就可以從AVFormatContext中讀取流信息了默伍。?般調(diào)?avformat_find_stream_info獲取完整的流信息欢嘿。為什么在調(diào)?了avformat_open_input后,仍然需要調(diào)?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. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
* The logical file position is not changed by this function;
* examined packets may be buffered for later processing.
*
* @param ic media file handle
* @param options If non-NULL, an ic.nb_streams long array of pointers to
* dictionaries, where i-th member contains options for
* codec corresponding to i-th stream.
* On return each dictionary will be filled with options that were not found.
* @return >=0 if OK, AVERROR_xxx on error
*
* @note this function isn't guaranteed to open all the codecs, so
* options being non-empty at return is a perfectly normal behavior.
*
* @todo Let the user decide somehow what information is needed so that
* we do not waste time getting stuff the user does not need.
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

該函數(shù)是通過讀取媒體?件的部分?jǐn)?shù)據(jù)來分析流信息炼蹦。在?些缺少頭信息的封裝下特別有?,?如說MPEG(?應(yīng)該說ts更準(zhǔn)確)(FLV?件也是需要讀取packet 分析流信息)狸剃。?被讀取?以分析流信息的數(shù)據(jù)可能被緩存掐隐,供av_read_frame時使?,在播放時并不會跳過這部分packet的讀取。

5 檢測是否指定播放起始時間

如果指定時間則seek到指定位置avformat_seek_file虑省。

可以通過 ffplay -ss 設(shè)置起始時間匿刮,時間格式hh:mm:ss,?如ffplay -ss 00:00:30 test.flv 則是從30秒的起始位置開始播放探颈。

具體調(diào)?流程熟丸,可以在opt_seek 函數(shù)打斷點進(jìn)?測試

1 { "ss", HAS_ARG, { .func_arg = opt_seek }, "seek to a given position in seconds", "pos" },
2 { "t", HAS_ARG, { .func_arg = opt_duration }, "play \"duration\" seconds of audio/video", "duration" },
1 /* if seeking requested, we execute it */
2 /* 5. 檢測是否指定播放起始時間 */
3 if (start_time != AV_NOPTS_VALUE) {
4     int64_t timestamp;
5
6     timestamp = start_time;
7     /* add the stream start time */
8     if (ic->start_time != AV_NOPTS_VALUE)
9     timestamp += ic->start_time;
10     // seek的指定的位置開始播放
11     ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);
12     if (ret < 0) {
13         av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position%0.3f\n",
14         is->filename, (double)timestamp / AV_TIME_BASE);
15     }
16 }

6 查找查找AVStream

?個媒體?件,對應(yīng)有0n個?頻流伪节、0n個視頻流光羞、0~n個字幕流,?如這?我們?了2_audio.mp4是有2個?頻流怀大,1個視頻流

具體現(xiàn)在那個流進(jìn)?播放我們有兩種策略:

  1. 在播放起始指定對應(yīng)的流
  2. 使?缺省的流進(jìn)?播放
1 在播放起始指定對應(yīng)的流

ffplay是通過通過命令可以指定流

{ "ast", OPT_STRING | HAS_ARG | OPT_EXPERT, {
&wanted_stream_spec[AVMEDIA_TYPE_AUDIO] }, "select desired audio stream",
"stream_specifier" },
{ "vst", OPT_STRING | HAS_ARG | OPT_EXPERT, {
&wanted_stream_spec[AVMEDIA_TYPE_VIDEO] }, "select desired video stream",
"stream_specifier" },
{ "sst", OPT_STRING | HAS_ARG | OPT_EXPERT, {
&wanted_stream_spec[AVMEDIA_TYPE_SUBTITLE] }, "select desired subtitle stream",
"stream_specifier" },

可以通過

  • -ast n 指定?頻流(?如我們在看電影時纱兑,有些電影可以?持普通話和英?切換,此時可以?該命令進(jìn)?選擇)
  • -vst n 指定視頻流
  • -vst n 指定字幕流

講對應(yīng)的index值記錄到st_index[AVMEDIA_TYPE_NB]叉寂;

2 使?缺省的流進(jìn)?播放

如果我們沒有指定萍启,則ffplay主要是通過 av_find_best_stream 來選擇,其原型為:

1 /**
2 * Find the "best" stream in the file.
3 * The best stream is determined according to various heuristics as the most
4 * likely to be what the user expects.
5 * If the decoder parameter is non-NULL, av_find_best_stream will find the
6 * default decoder for the stream's codec; streams for which no decoder can
7 * be found are ignored.
8 *
9 * @param ic media file handle
10 * @param type stream type: video, audio, subtitles, etc.
11 * @param wanted_stream_nb user-requested stream number,
12 * or -1 for automatic selection
13 * @param related_stream try to find a stream related (eg. in the same
14 * program) to this one, or -1 if none
15 * @param decoder_ret if non-NULL, returns the decoder for the
16 * selected stream
17 * @param flags flags; none are currently defined
18 * @return the non-negative stream number in case of success,
19 * AVERROR_STREAM_NOT_FOUND if no stream with the requested type
20 * could be found,
21 * AVERROR_DECODER_NOT_FOUND if streams were found but no decoder
22 * @note If av_find_best_stream returns successfully and decoder_ret is not
23 * NULL, then *decoder_ret is guaranteed to be set to a valid AVCodec.
24 */
25 int av_find_best_stream(AVFormatContext *ic,
26 enum AVMediaType type, //要選擇的流類型
27 int wanted_stream_nb, //?標(biāo)流索引
28 int related_stream, //相關(guān)流索引
29 AVCodec **decoder_ret,
30 int flags);

具體代碼流程

1 //根據(jù)?戶指定來查找流
2 for (i = 0; i < ic->nb_streams; i++) {
3     AVStream *st = ic->streams[i];
4     enum AVMediaType type = st->codecpar->codec_type;
5     st->discard = AVDISCARD_ALL;
6     if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1)
7         if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0)
8             st_index[type] = i;
9  }
10 for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
11    if (wanted_stream_spec[i] && st_index[i] == -1) {
12         av_log(NULL, AV_LOG_ERROR, "Stream specifier %s does not match any %s stream\n", wanted_stream_spec[i], av_get_media_type_string(i));
13         st_index[i] = INT_MAX;
14     }
15 }
16 //利?av_find_best_stream選擇流屏鳍,
17 if (!video_disable)
18     st_index[AVMEDIA_TYPE_VIDEO] =
19     av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
20                         st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
21 if (!audio_disable)
22     st_index[AVMEDIA_TYPE_AUDIO] =
23     av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
24                         st_index[AVMEDIA_TYPE_AUDIO],
25                         st_index[AVMEDIA_TYPE_VIDEO],
26                         NULL, 0);
27 if (!video_disable && !subtitle_disable)
28     st_index[AVMEDIA_TYPE_SUBTITLE] =
29     av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
30                         st_index[AVMEDIA_TYPE_SUBTITLE],
31                         (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
32                         st_index[AVMEDIA_TYPE_AUDIO] :
33                         st_index[AVMEDIA_TYPE_VIDEO]),
34                         NULL, 0);

如果?戶沒有指定流勘纯,或指定部分流,或指定流不存在钓瞭,則主要由av_find_best_stream發(fā)揮作?驳遵。

如果指定了正確的wanted_stream_nb,?般情況都是直接返回該指定流山涡,即?戶選擇的流堤结。

如果指定了相關(guān)流,且未指定?標(biāo)流的情況鸭丛,會在相關(guān)流的同?個節(jié)?中查找所需類型的流竞穷,但?般結(jié)果,都是返回該類型第1個流鳞溉。

7 通過AVCodecParameters和av_guess_sample_aspect_ratio計算出顯示窗?的寬瘾带、?
1 //7 從待處理流中獲取相關(guān)參數(shù),設(shè)置顯示窗?的寬度熟菲、?度及寬??
2 if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
3      AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];
4      AVCodecParameters *codecpar = st->codecpar;
5       /*根據(jù)流和幀寬??猜測幀的樣本寬??看政。
6      * 由于幀寬??由解碼器設(shè)置,但流寬??由解復(fù)?器設(shè)置抄罕,因此這兩者可能不相等允蚣。
7      * 此函數(shù)會嘗試返回待顯示幀應(yīng)當(dāng)使?的寬??值。
8      * 基本邏輯是優(yōu)先使?流寬??(前提是值是合理的)呆贿,其次使?幀寬??嚷兔。
9      * 這樣,流寬??(容器設(shè)置,易于修改)可以覆蓋幀寬??谴垫。
10     */
11     AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);
12     if (codecpar->width) {
13         // 設(shè)置顯示窗?的??和寬??
14         set_default_window_size(codecpar->width, codecpar->height, sar);
15     }
16 }

具體流程如上所示章母,這?實質(zhì)只是設(shè)置了default_width、default_height變量的??翩剪,沒有真正改變窗?的??乳怎。真正調(diào)整窗???是在視頻顯示調(diào)?video_open()函數(shù)進(jìn)?設(shè)置。

8 stream_component_open()

經(jīng)過以上步驟前弯,?件打開成功蚪缀,且獲取了流的基本信息,并選擇?頻流恕出、視頻流询枚、字幕流。接下來就可以所選流對應(yīng)的解碼器了浙巫。

1 /* open the streams */
2 /* 5.打開視頻金蜀、?頻解碼器。在此會打開相應(yīng)解碼器的畴,并創(chuàng)建相應(yīng)的解碼線程渊抄。 */
3 if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
4     stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
5 }
6
7 ret = -1;
8 if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
9     ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
10 }
11 if (is->show_mode == SHOW_MODE_NONE) {
12     //選擇怎么顯示,如果視頻打開成功丧裁,就顯示視頻畫?护桦,否則,顯示?頻對應(yīng)的頻譜圖
13     is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
14 }
15
16 if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
17     stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);
18 }

?頻煎娇、視頻二庵、字幕等流都要調(diào)?stream_component_open,他們直接有共同的流程缓呛,也有差異化的流程催享,差異化流程使?switch進(jìn)?區(qū)分。具體原型

int stream_component_open(VideoState *is, int stream_index)哟绊;
stream_index

看下 stream_component_open .函數(shù)也?較?睡陪,逐步分析:

1 /* 為解碼器分配?個編解碼器上下?結(jié)構(gòu)體 */
2 avctx = avcodec_alloc_context3(NULL);
3 if (!avctx)
4     return AVERROR(ENOMEM);
5 /* 將碼流中的編解碼器信息拷?到新分配的編解碼器上下?結(jié)構(gòu)體 */
6 ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
7 if (ret < 0)
8     goto fail;
9 // 設(shè)置pkt_timebase
10 avctx->pkt_timebase = ic->streams[stream_index]->time_base;

先是通過 avcodec_alloc_context3 分配了解碼器上下? AVCodecContex ,然后通過avcodec_parameters_to_context 把所選流的解碼參數(shù)賦給 avctx 匿情,最后設(shè)了 time_base .

補(bǔ)充:avcodec_parameters_to_context 解碼時?,avcodec_parameters_from_context則?于編碼信殊。

1 /* 根據(jù)codec_id查找解碼器 */
2 codec = avcodec_find_decoder(avctx->codec_id);
3
4 switch(avctx->codec_type){// 獲取指定的解碼器名字炬称,如果沒有設(shè)置則為NULL
5     case AVMEDIA_TYPE_AUDIO : is->last_audio_stream = stream_index;
6         forced_codec_name = audio_codec_name; break; // 獲取指定的解碼器名字
7     case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index;
8         forced_codec_name = subtitle_codec_name; break; // 獲取指定的解碼器名字
9     case AVMEDIA_TYPE_VIDEO : is->last_video_stream = stream_index;
10         forced_codec_name = video_codec_name; break; // 獲取指定的解碼器名字
11 }
12 }
13 if (forced_codec_name)
14     codec = avcodec_find_decoder_by_name(forced_codec_name);
15 if (!codec) {
16     if (forced_codec_name) av_log(NULL, AV_LOG_WARNING,
17         "No codec could be found with name '%s'\n", forced_codec_name);
18     else av_log(NULL, AV_LOG_WARNING,
19         "No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id));
20     ret = AVERROR(EINVAL);
21     goto fail;
22 }

這段主要是通過 avcodec_find_decoder 找到所需解碼器(AVCodec)。如果?戶有指定解碼器涡拘,則設(shè)置 forced_codec_name 玲躯,并通過 avcodec_find_decoder_by_name 查找解碼器。找到解碼器

后,就可以通過 avcodec_open2 打開解碼器了跷车。(forced_codec_name對應(yīng)到?頻棘利、視頻、字幕不同的傳?的解碼器名字朽缴,如果有設(shè)置善玫,?如ffplay -acodec aac xx.flv, 此時audio_codec_name設(shè)置為"aac",則相應(yīng)的forced_codec_name為“aac”)

最后密强,是?個?的switch-case:

1 switch (avctx->codec_type) {
2     case AVMEDIA_TYPE_AUDIO:
3          sample_rate = avctx->sample_rate;
4          nb_channels = avctx->channels;
5          channel_layout = avctx->channel_layout;
6
7          /* prepare audio output 準(zhǔn)備?頻輸出*/
8          if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
9             goto fail;
10         is->audio_hw_buf_size = ret;
11         is->audio_src = is->audio_tgt;
12         is->audio_buf_size = 0;
13         is->audio_buf_index = 0;
14
15         /* init averaging filter 初始化averaging濾鏡, ?audio master時使? */
16         is->audio_diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB);
17         is->audio_diff_avg_count = 0;
18         /* 由于我們沒有精確的?頻數(shù)據(jù)填充FIFO,故只有在?于該閾值時才進(jìn)?校正?頻同步*/
19         is->audio_diff_threshold = (double)(is->audio_hw_buf_size) /is->audio_tgt.bytes_per_sec;
20
19
21         is->audio_stream = stream_index; // 獲取audio的stream索引
22         is->audio_st = ic->streams[stream_index]; // 獲取audio的stream指針
23         // 初始化ffplay封裝的?頻解碼器
24         decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
25         if ((is->ic->iformat->flags & (AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK)) && !is->ic->iformat->read_seek) {
26             is->auddec.start_pts = is->audio_st->start_time;
27             is->auddec.start_pts_tb = is->audio_st->time_base;
28         }
29         // 啟動?頻解碼線程
30         if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
31             goto out;
32         SDL_PauseAudioDevice(audio_dev, 0);
33         break;
34     case AVMEDIA_TYPE_VIDEO:
35         is->video_stream = stream_index; // 獲取video的stream索引
36         is->video_st = ic->streams[stream_index];// 獲取video的stream指針
37         // 初始化ffplay封裝的視頻解碼器
38         decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
39         // 啟動視頻頻解碼線程
40         if ((ret = decoder_start(&is->viddec, video_thread, "video_decoder", is)) < 0)
41             goto out;
42         is->queue_attachments_req = 1; // 使能請求mp3茅郎、aac等?頻?件的封?
43         break;
44     case AVMEDIA_TYPE_SUBTITLE: // 視頻是類似邏輯處理
45         is->subtitle_stream = stream_index;
46         is->subtitle_st = ic->streams[stream_index];
47
48         decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread);
49         if ((ret = decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is)) < 0)
50             goto out;
20
51         break;
52     default:
53         break;
54 }

即根據(jù)具體的流類型,作特定的初始化或渤。但不論哪種流系冗,基本步驟都包括了ffplay封裝的解碼器的初始化和啟動解碼器線程:

  • decoder_init 初始化解碼器
    • d->avctx = avctx; 綁定對應(yīng)的解碼器上下?
    • d->queue = queue; 綁定對應(yīng)的packet隊列
    • d->empty_queue_cond = empty_queue_cond; 綁定VideoState的continue_read_thread,當(dāng)解碼線程沒有 * packet可讀時喚醒read_thread趕緊讀取數(shù)據(jù)
    • d->start_pts = AV_NOPTS_VALUE; 初始化start_pts
    • d->pkt_serial = -1; 初始化pkt_serial
  • decoder_start啟動解碼器
    • packet_queue_start 啟?對應(yīng)的packet 隊列
    • SDL_CreateThread 創(chuàng)建對應(yīng)的解碼線程

需要注意的是薪鹦,對應(yīng)?頻??掌敬,這?還初始化了輸出參數(shù),這塊在講?頻輸出的時候再重點展開池磁。

以上是準(zhǔn)備的?作奔害,我們再來看for循環(huán)。

4.2 For循環(huán)讀取數(shù)據(jù)

主要包括以下步驟:

  1. 檢測是否退出
  2. 檢測是否暫停/繼續(xù)
  3. 檢測是否需要seek
  4. 檢測video是否為attached_pic
  5. 檢測隊列是否已經(jīng)有?夠數(shù)據(jù)
  6. 檢測碼流是否已經(jīng)播放結(jié)束
    a. 是否循環(huán)播放
    b. 是否?動退出
  7. 使?av_read_frame讀取數(shù)據(jù)包
  8. 檢測數(shù)據(jù)是否讀取完畢
  9. 檢測是否在播放范圍內(nèi)
  10. 到這步才將數(shù)據(jù)插?對應(yīng)的隊列
1. 檢測是否退出
1 // 1 檢測是否退出
2 if (is->abort_request)
3     break;

當(dāng)退出事件發(fā)?時框仔,調(diào)?do_exit() -> stream_close() -> 將is->abort_request置為1舀武。退出該for循環(huán),并最終退出該線程离斩。

2. 檢測是否暫停/繼續(xù)

這?的暫停银舱、繼續(xù)只是對?絡(luò)流有意義

1 // 2 檢測是否暫停/繼續(xù)
2 if (is->paused != is->last_paused) {
3     is->last_paused = is->paused;
4     if (is->paused)
5         is->read_pause_return = av_read_pause(ic); // ?絡(luò)流的時候有?
6     else
7         av_read_play(ic);
8 }

?如rtsp
av_read_pause

1 /* pause the stream */
2 static int rtsp_read_pause(AVFormatContext *s)
3 {
4     RTSPState *rt = s->priv_data;
5     RTSPMessageHeader reply1, *reply = &reply1;
6
7     if (rt->state != RTSP_STATE_STREAMING)
8         return 0;
9     else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {
10         ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL);
11         if (reply->status_code != RTSP_STATUS_OK) {
12             return ff_rtsp_averror(reply->status_code, -1);
13         }
14     }
15     rt->state = RTSP_STATE_PAUSED;
16     return 0;
17 }

av_read_play

1 static int rtsp_read_play(AVFormatContext *s)
2 {
3     RTSPState *rt = s->priv_data;
4     RTSPMessageHeader reply1, *reply = &reply1;
5     ......
6     ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL);
7     ....
8     rt->state = RTSP_STATE_STREAMING;
9     return 0;
10 }
3. 檢測是否需要seek
1 // 3 檢測是否seek
2 if (is->seek_req) { // 是否有seek請求
3      int64_t seek_target = is->seek_pos;
4      int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
5      int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
6      // FIXME the +-2 is due to rounding being not done in the correct direction in generation
7      // of the seek_pos/seek_rel variables
8      // 修復(fù)由于四舍五?,沒有再seek_pos/seek_rel變量的正確?向上進(jìn)?
9      ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
10     if (ret < 0) {
11         av_log(NULL, AV_LOG_ERROR,
12         "%s: error while seeking\n", is->ic->url);
13     } else {
14         /* seek的時候,要把原先的數(shù)據(jù)情況苟耻,并重啟解碼器驱闷,put flush_pkt的?的是告知解碼線程需要
15         * reset decoder
16         */
17         if (is->audio_stream >= 0) { // 如果有?頻流
18             packet_queue_flush(&is->audioq); // 清空packet隊列數(shù)據(jù)
19             // 放?flush pkt, ?來開起新的?個播放序列, 解碼器讀取到flush_pkt也清空解碼器
20             packet_queue_put(&is->audioq, &flush_pkt);
21         }
22         if (is->subtitle_stream >= 0) { // 如果有字幕流
23             packet_queue_flush(&is->subtitleq); // 和上同理
24             packet_queue_put(&is->subtitleq, &flush_pkt);
25         }
26         if (is->video_stream >= 0) { // 如果有視頻流
27             packet_queue_flush(&is->videoq); // 和上同理
28             packet_queue_put(&is->videoq, &flush_pkt);
29         }
30         if (is->seek_flags & AVSEEK_FLAG_BYTE) {
31             set_clock(&is->extclk, NAN, 0);
32         } else {
33             set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
34         }
35     }
36     is->seek_req = 0;
37     is->queue_attachments_req = 1;
38     is->eof = 0;
39     if (is->paused)
40     step_to_next_frame(is); // 如果本身是pause狀態(tài)的則顯示?幀繼續(xù)暫停
41 }

主要的seek操作通過avformat_seek_file完成(該函數(shù)的具體使?在播放控制seek時做詳解)。根據(jù)avformat_seek_file的返回值诚欠,如果seek成功,需要:

  1. 清除PacketQueue的緩存漾岳,并放??個flush_pkt轰绵。放?的flush_pkt可以讓PacketQueue的serial增1,以區(qū)分seek前后的數(shù)據(jù)(PacketQueue函數(shù)的分析0)尼荆,該flush_pkt也會觸發(fā)解碼器重新刷新解碼器緩存avcodec_flush_buffers()左腔,以避免解碼時使?了原來的buffer作為參考?出現(xiàn)?賽克。
  2. 同步外部時鐘捅儒。在后續(xù)?視頻同步的課程中再具體分析液样。

這?還要注意:如果播放器本身是pause的狀態(tài)振亮,則

if (is->paused)
step_to_next_frame(is); // 如果本身是pause狀態(tài)的則顯示?幀繼續(xù)暫停
4. 檢測video是否為attached_pic
1 // 4 檢測video是否為attached_pic
2 if (is->queue_attachments_req) {
3      // attached_pic 附帶的圖?。?如說?些MP3鞭莽,AAC?頻?件附帶的專輯封?坊秸,所以需要注意的是?頻?件不?定只存在?頻流本身
4      if (is->video_st && is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
5          AVPacket copy = { 0 };
6         if ((ret = av_packet_ref(&copy, &is->video_st->attached_pic)) < 0)
7             goto fail;
8         packet_queue_put(&is->videoq, &copy);
9         packet_queue_put_nullpacket(&is->videoq, is->video_stream);
10     }
11     is->queue_attachments_req = 0;
12 }

AV_DISPOSITION_ATTACHED_PIC 是?個標(biāo)志。如果?個流中含有這個標(biāo)志的話澎怒,那么就是說這個流是 *.mp3等 ?件中的?個 Video Stream 褒搔。并且該流只有?個 AVPacket ,也就是attached_pic 丹拯。這個 AVPacket 中所存儲的內(nèi)容就是這個 *.mp3等 ?件的封?圖?站超。

因此,也可以很好的解釋了?章開頭提到的為什么 st->disposition & AV_DISPOSITION_ATTACHED_PIC 這個操作可以決定是否可以繼續(xù)向緩沖區(qū)中添加 AVPacket 乖酬。

5. 檢測隊列是否已經(jīng)有?夠數(shù)據(jù)

?頻死相、視頻、字幕隊列都不是?限?的咬像,如果不加以限制?直往隊列放?packet算撮,那將導(dǎo)致隊列占??量的內(nèi)存空間,影響系統(tǒng)的性能县昂,所以必須對隊列的緩存??進(jìn)?控制肮柜。

PacketQueue默認(rèn)情況下會有??限制,達(dá)到這個??后倒彰,就需要等待10ms审洞,以讓消費(fèi)者——解碼線程能有時間消耗。

1 // 5 檢測隊列是否已經(jīng)有?夠數(shù)據(jù)
2 /* if the queue are full, no need to read more */
3 /* 緩存隊列有?夠的包待讳,不需要繼續(xù)讀取數(shù)據(jù) */
4 if (infinite_buffer<1 && // 緩沖區(qū)不是?限?
5      (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
6      || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) &&
7      stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) &&
8      stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) {
9      /* wait 10 ms */
10     SDL_LockMutex(wait_mutex);
11     // 如果沒有喚醒則超時10ms退出芒澜,?如在seek操作時這?會被喚醒
12     SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
13     SDL_UnlockMutex(wait_mutex);
14     continue;
15 }

緩沖區(qū)滿有兩種可能:

  1. audioq,videoq创淡,subtitleq三個PacketQueue的總字節(jié)數(shù)達(dá)到了MAX_QUEUE_SIZE(15M痴晦,為什么是15M?這?只是?個經(jīng)驗計算值琳彩,?如4K視頻的碼率以50Mbps計算誊酌,則15MB可以緩存2.4秒,從這么計算實際上如果我們真的是播放4K?源露乏,15MB是偏?的數(shù)值碧浊,有些?源?較坑 同?個?件位置附近的pts差值超過5秒,此時如果視頻要緩存5秒才能做同步瘟仿,那15MB的緩存??就不夠了)
  2. ?頻辉词、視頻、字幕流都已有夠?的包(stream_has_enough_packets)猾骡,注意:3者要同時成?

第?種好理解瑞躺,看下第?種中的stream_has_enough_packets:

1 static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue) {
2     return stream_id < 0 || // 沒有該流
3            queue->abort_request || // 請求退出
4            (st->disposition & AV_DISPOSITION_ATTACHED_PIC) || // 是ATTACHED_PIC
5            queue->nb_packets > MIN_FRAMES // packet數(shù)>25
6            && (!queue->duration || // 滿?PacketQueue總時?為0
7            av_q2d(st->time_base) * queue->duration > 1.0);//或總時?超過1s
8 }

有這么?種情況包是夠?的:

  1. 流沒有打開(stream_id < 0),沒有相應(yīng)的流返回邏輯true
  2. 有退出請求(queue->abort_request)
  3. 配置了AV_DISPOSITION_ATTACHED_PIC
  4. packet隊列內(nèi)包個數(shù)?于MIN_FRAMES(>25)兴想,并滿?PacketQueue總時?為0或總時?超過1s

思路:
1.總數(shù)據(jù)??
2.每個packet隊列的情況幢哨。

6. 檢測碼流是否已經(jīng)播放結(jié)束

?暫停狀態(tài)才進(jìn)?步檢測碼流是否已經(jīng)播放完畢(注意:數(shù)據(jù)播放完畢和碼流數(shù)據(jù)讀取完畢是兩個概念。)

PacketQueue和FrameQueue都消耗完畢嫂便,才是真正的播放完畢

1 // 6 檢測碼流是否已經(jīng)播放結(jié)束
2 if (!is->paused // ?暫停
3     && // 這?的執(zhí)?是因為碼流讀取完畢后 插?空包所致
4     (!is->audio_st // 沒有?頻流
5     || (is->auddec.finished == is->audioq.serial // 或者?頻播放完畢
6     && frame_queue_nb_remaining(&is->sampq) == 0))
7     && (!is->video_st // 沒有視頻流
8     || (is->viddec.finished == is->videoq.serial // 或者視頻播放完畢
9     && frame_queue_nb_remaining(&is->pictq) == 0))) {
10       if (loop != 1 // a 是否循環(huán)播放
11           && (!loop || --loop)) {
12             stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time :0, 0, 0);
13         } else if (autoexit) { // b 是否?動退出
14             ret = AVERROR_EOF;
15             goto fail;
16         }
17 }

這?判斷播放已完成的條件需要同時滿?滿?:

  1. 不在暫停狀態(tài)
  2. ?頻未打開捞镰;或者打開了,但是解碼已解完所有packet毙替,?定義的解碼器(decoder)serial等于PacketQueue的serial岸售,并且FrameQueue中沒有數(shù)據(jù)幀
    PacketQueue.serial -> packet.serail -> decoder.pkt_serial
    decoder.finished = decoder.pkt_serial
    is->auddec.finished == is->audioq.serial 最新的播放序列的packet都解碼完畢
    frame_queue_nb_remaining(&is->sampq) == 0 對應(yīng)解碼后的數(shù)據(jù)也播放完畢
  3. 視頻未打開;或者打開了厂画,但是解碼已解完所有packet凸丸,?定義的解碼器(decoder)serial等于
    PacketQueue的serial,并且FrameQueue中沒有數(shù)據(jù)幀袱院。

在確認(rèn)?前碼流已播放結(jié)束的情況下屎慢,?戶有兩個變量可以控制播放器?為:

  1. loop: 控制播放次數(shù)(當(dāng)前這次也算在內(nèi),也就是最?就是1次了)忽洛,0表示?限次
  2. autoexit:?動退出腻惠,也就是播放完成后?動退出。
    loop條件簡化的?常不友好欲虚,其意思是:如果loop==1集灌,那么已經(jīng)播了1次了,?需再seek重新播放复哆;如果loop不是1欣喧,==0,隨意寂恬,?限次循環(huán)续誉;減1后還?于0(--loop),也允許循環(huán)

a. 是否循環(huán)播放
如果循環(huán)播放初肉,即是將?件seek到起始位置 stream_seek(is, start_time != AV_NOPTS_VALUE ?
start_time : 0, 0, 0); 酷鸦,這?講的的起始位置不?定是從頭開始,具體也要看?戶是否指定了起始播放位置
b. 是否?動退出
如果播放完畢?動退出

7. 使?av_read_frame讀取數(shù)據(jù)包

讀取數(shù)據(jù)包很簡單牙咏,但要注意傳?的packet臼隔,av_read_frame不會釋放其數(shù)據(jù),?是每次都重新申請數(shù)據(jù)妄壶。

1 // 7.讀取媒體數(shù)據(jù)摔握,得到的是?視頻分離后、解碼前的數(shù)據(jù)
2 ret = av_read_frame(ic, pkt); // 調(diào)?不會釋放pkt的數(shù)據(jù)丁寄,都是要??去釋放
8. 檢測數(shù)據(jù)是否讀取完畢
1 // 8 檢測數(shù)據(jù)是否讀取完畢
2 if (ret < 0) {
3     if ((ret == AVERROR_EOF || avio_feof(ic->pb))
4          && !is->eof)
5 {
6         // 插?空包說明碼流數(shù)據(jù)讀取完畢了氨淌,之前講解碼的時候說過刷空包是為了從解碼器把所有幀都讀出來
7         if (is->video_stream >= 0)
8             packet_queue_put_nullpacket(&is->videoq, is->video_stream);
9         if (is->audio_stream >= 0)
10           packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
11         if (is->subtitle_stream >= 0)
12             packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
13         is->eof = 1; // ?件讀取完畢
14     }
15     if (ic->pb && ic->pb->error)
16         break;
17     SDL_LockMutex(wait_mutex);
18     SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
19     SDL_UnlockMutex(wait_mutex);
20     continue; // 繼續(xù)循環(huán) 保證線程的運(yùn)?泊愧,?如要seek到某個位置播放可以繼續(xù)響應(yīng)
21 } else {
22     is->eof = 0;
23 }

數(shù)據(jù)讀取完畢后,放對應(yīng)?頻盛正、視頻删咱、字幕隊列插?“空包”,以通知解碼器沖刷buffer豪筝,將緩存的所有數(shù)據(jù)都解出來frame并去出來痰滋。

然后繼續(xù)在for{}循環(huán),直到收到退出命令续崖,或者loop播放敲街,或者seek等操作。

9. 檢測是否在播放范圍內(nèi)

播放器可以設(shè)置:-ss 起始位置严望,以及 -t 播放時?

1 // 9 檢測是否在播放范圍內(nèi)
2 /* check if packet is in play range specified by user, then queue, otherwise discard */
3 stream_start_time = ic->streams[pkt->stream_index]->start_time;// 獲取流的起始時間
4 pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;// 獲取packet的時間戳
5 // 這?的duration是在命令?時?來指定播放?度
6 pkt_in_play_range = duration == AV_NOPTS_VALUE ||
7 (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
8 av_q2d(ic->streams[pkt->stream_index]->time_base) -
9 (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
10 <= ((double)duration / 1000000);

從流獲取的參數(shù)

  • stream_start_time:是從當(dāng)前流AVStream->start_time獲取到的時間多艇,如果沒有定義具體的值則默認(rèn)為AV_NOPTS_VALUE,即該值是?效的著蟹;那stream_start_time有意義的就是0值墩蔓;
  • pkt_ts:當(dāng)前packet的時間戳,pts有效就?pts的萧豆,pts?效就?dts的奸披;

ffplay播放的參數(shù)
duration: 使?"-t value"指定的播放時?,默認(rèn)值A(chǔ)V_NOPTS_VALUE涮雷,即該值?效不?參考
start_time:使?“-ss value”指定播放的起始位置阵面,默認(rèn)AV_NOPTS_VALUE,即該值?效不?參考

pkt_in_play_range的值為0或1洪鸭。

  • 當(dāng)沒有指定duration播放時?時样刷,很顯然duration == AV_NOPTS_VALUE的邏輯值為1,所以pkt_in_play_range為1览爵;

  • 當(dāng)duration被指定(-t value)且有效時置鼻,主要判斷

1 (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time :0)) *
2 av_q2d(ic->streams[pkt->stream_index]->time_base) -
3 (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
4 <= ((double)duration / 1000000);

實質(zhì)就是當(dāng)前時間戳 pkt_ts - start_time 是否 < duration,這?分為:
stream_start_time是否有效:有效就?實際值蜓竹,?效就是從0開始
start_time 是否有效箕母,有效就?實際值,?效就是從0開始
即是pkt_ts - stream_start_time - start_time < duration (為了簡單俱济,這?沒有考慮時間單位)

10. 到這步才將數(shù)據(jù)插?對應(yīng)的隊列
1 // 10 將?視頻數(shù)據(jù)分別送?相應(yīng)的queue中
2 if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
3     packet_queue_put(&is->audioq, pkt);
4 } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
5 && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
6     //printf("pkt pts:%ld, dts:%ld\n", pkt->pts, pkt->dts);
7     packet_queue_put(&is->videoq, pkt);
8 } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
9     packet_queue_put(&is->subtitleq, pkt);
10 } else {
11     av_packet_unref(pkt);// 不?隊列則直接釋放數(shù)據(jù)
12 }

這?的代碼就很直?了嘶是,將packet放?到對應(yīng)的PacketQueue

4.3 退出線程處理

主要包括以下步驟:

  1. 如果解復(fù)?器有打開則關(guān)閉avformat_close_input
  2. 調(diào)?SDL_PushEvent發(fā)送退出事件FF_QUIT_EVENT
    a. 發(fā)送的FF_QUIT_EVENT退出播放事件由event_loop()函數(shù)相應(yīng),收到FF_QUIT_EVENT后調(diào)?do_exit()做退出操作蛛碌。
  3. 消耗互斥量wait_mutex
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末聂喇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蔚携,更是在濱河造成了極大的恐慌希太,老刑警劉巖克饶,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異誊辉,居然都是意外死亡彤路,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門芥映,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人远豺,你說我怎么就攤上這事奈偏。” “怎么了躯护?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵惊来,是天一觀的道長。 經(jīng)常有香客問我棺滞,道長裁蚁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任继准,我火速辦了婚禮枉证,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘移必。我一直安慰自己室谚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布崔泵。 她就那樣靜靜地躺著秒赤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪憎瘸。 梳的紋絲不亂的頭發(fā)上入篮,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音幌甘,去河邊找鬼潮售。 笑死,一個胖子當(dāng)著我的面吹牛含潘,可吹牛的內(nèi)容都是我干的饲做。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼遏弱,長吁一口氣:“原來是場噩夢啊……” “哼盆均!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起漱逸,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泪姨,失蹤者是張志新(化名)和其女友劉穎游沿,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肮砾,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诀黍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仗处。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眯勾。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖婆誓,靈堂內(nèi)的尸體忽然破棺而出吃环,到底是詐尸還是另有隱情,我是刑警寧澤洋幻,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布郁轻,位于F島的核電站,受9級特大地震影響文留,放射性物質(zhì)發(fā)生泄漏好唯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一燥翅、第九天 我趴在偏房一處隱蔽的房頂上張望骑篙。 院中可真熱鬧,春花似錦权旷、人聲如沸替蛉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躲查。三九已至,卻和暖如春译柏,著一層夾襖步出監(jiān)牢的瞬間镣煮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工鄙麦, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留典唇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓胯府,卻偏偏與公主長得像介衔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子骂因,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

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