
本章主要是分析 数据读取线程read_thread 中的工作。如上图红色框框的部分
从ffplay框架分析我们可以看到,ffplay有专⻔的线程read_thread()读取数据,
且在调⽤av_read_frame 读取数据包之前需要做:
1.例如打开⽂件,
2.查找配置解码器,
3.初始化⾳视频输出等准备阶段,
主要包括三⼤步骤:
- 准备⼯作
- For循环读取数据
- 退出线程处理
一 准备⼯作
准备⼯作主要包括以下步骤:
1.
avformat_alloc_context 创建上下⽂
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,讲对应的index值记录到
st_index
[
AVMEDIA_TYPE_NB
];
a.
根据⽤户指定来查找流
avformat_match_stream_specifier
b.
使⽤av_find_best_stream查找流
7. 通过
AVCodecParameters和
av_guess_sample_aspect_ratio
计算出显示窗⼝的宽、⾼
8.
stream_component_open打开⾳频、视频、字幕解码器,并创建相应的解码线程以及进⾏对应输出参数的初始化。
1. avformat_alloc_context 创建上下⽂
调⽤
avformat_alloc_context创建解复⽤器上下⽂
// 1. 创建上下⽂结构体,这个结构体是最上层的结构体,表示输⼊上下⽂
ic = avformat_alloc_context();
最终该ic 赋值给VideoState的ic变量
is->ic = ic; // videoState的ic指向分配的ic
2 ic->interrupt_callback
ic 这里是AVFormatContext ,
/* 2.设置中断回调函数,如果出错或者退出,就根据目前程序设置的状态选择继续check或者直接退出 */
/* 当open出现阻塞的时候时候,会调用interrupt_callback.callback
* 回调函数中返回1则代表ffmpeg结束阻塞可以将操纵权交给用户线程并返回错误码
* 回调函数中返回0则代表ffmpeg继续阻塞直到ffmpeg正常工作为止,所以要退出死等则需要返回1
*/
ic->interrupt_callback.callback = decode_interrupt_cb;
ic->interrupt_callback.opaque = is;
那么这个
interrupt_callback 是个啥?
interrupt_callback⽤于ffmpeg内部在执⾏耗时操作时检查调⽤者是否有退出请求,避免⽤户退出请求没 有及时响应。
在什么时候触发这个 callback 呢?这个知道什么时候触发就可以了
avformat_open_input的触发
avformat_find_stream_info的触发
av_read_frame的触发
在QT中debug 是看不到什么时候触发的,测试方法如下:
怎么去测试在哪⾥触发? 在ubuntu使⽤gdb进⾏调试:我们之前讲的在ubuntu下编译ffmpeg,在 lqf@ubuntu:~/ffmpeg_sources/ffmpeg-4.2.1⽬录下有ffplay_g,我们可以通过 gdb ./ffplay_g来播 放视频,然后在decode_interrupt_cb打断点。
举例:avformat_open_input的触发
1 #0 decode_interrupt_cb (ctx=0x7ffff7e36040) at fftools/ffplay.c:271
5
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=0x7fffd000094
0)
21 at libavformat/utils.c:443
22 #9 avformat_open_input (ps=ps@entry=0x7fffdd9bcbf8,
23 filename=0x31d50e0 "source.200kbps.768x320.flv", fmt=<optimized
out>,
可以看到是在libavformat/avio.c:374⾏有触发到

3.avformat_open_input()打开媒体⽂件
Open an input stream and read the header,打开文件后读取头部。这里有个问题,有些格式其实是没有头部的,因此只调用avformat_open_input是不行的,还需要调用avformat_find_stream_info,avformat_find_stream_info方法是会读取数据的一部分,因此通常情况下 ,这两函数会一起使用。
avformat_open_input⽤于打开输⼊⽂件(对于RTMP/RTSP/HTTP⽹络流也是⼀样,在ffmpeg内部都 抽象为URLProtocol,这⾥描述为⽂件是为了⽅便与后续提到的AVStream的流作区分),读取视频⽂件 的基本信息。
需要提到的两个参数是fmt和options。通过fmt可以强制指定视频⽂件的封装,options可以传递额外参数 给封装(AVInputFormat)。
主要代码:
//特定选项处理
if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
scan_all_pmts_set = 1;
}
/* 3.打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等 */
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
if (err < 0) {
print_error(is->filename, err);
ret = -1;
goto fail;
}
if (scan_all_pmts_set)
av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
ret = AVERROR_OPTION_NOT_FOUND;
goto fail;
}
scan_all_pmts是mpegts的⼀个选项,表示扫描全部的ts流的"Program Map Table"表。这⾥在没有设定 该选项的时候,强制设为1。最后执⾏avformat_open_input。
参数的设置最终都是设置到对应的解复⽤器,⽐如:

scan_all_pmts是mpegts的⼀个选项,表示扫描全部的ts流的"Program Map Table"表。这⾥在没有设定 该选项的时候,强制设为1。最后执⾏avformat_open_input。
使⽤gdb跟踪options的设置,在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)
参数的设置最终都是设置到对应的解复⽤器,⽐如:


4. avformat_find_stream_info()
在打开了⽂件后,就可以从AVFormatContext中读取流信息了。⼀般调用avformat_find_stream_info获 取完整的流信息。为什么在调⽤了avformat_open_input后,仍然需要调⽤avformat_find_stream_info 才能获取正确的流信息呢?看下avformat_find_stream_info方法的注释就知道了。
* Read packets of a media file


2188

被折叠的 条评论
为什么被折叠?



