1,首先从ffplay.c main()
is = stream_open(input_filename, file_iformat);
2,在straem_open里面
is->read_tid = SDL_CreateThread(read_thread, is);
3,read_thread
err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
4,avformat_open_input
if ((ret = init_input(s, filename, &tmp)) < 0
真正的好戏是从init_input();开始的。
我们的目标是想要知道怎样和服务器进行RTSP报文交互的,所以第一个要从输入的网址里面解析知道这要走RTSP协议
探测过程:
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
int ret;
AVProbeData pd = { filename, NULL, 0 };
int score = AVPROBE_SCORE_RETRY;
if (s->pb) {
s->flags |= AVFMT_FLAG_CUSTOM_IO;
if (!s->iformat)
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
else if (s->iformat->flags & AVFMT_NOFILE)
av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
"will be ignored with AVFMT_NOFILE format.\n");
return 0;
}
if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
(!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
return score;//实际上rtsp在这个地方就返回了。
if ((ret = avio_open2(&s->pb, filename, AVIO_FLAG_READ | s->avio_flags,//播放本地文件在这个地方就会调用
&s->interrupt_callback, options)) < 0)
return ret;
if (s->iformat)
return 0;
return av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
其实在这个地方以AVFMT_NOFILE分成两类来解析:
如果播放的是本地文件,那会有个实实在在的文件,而rtsp网址,没有任何文件存在的概念。
首先看rtspdec.c
AVInputFormat ff_rtsp_demuxer = {
.name = "rtsp",
.long_name = NULL_IF_CONFIG_SMALL("RTSP input"),
.priv_data_size = sizeof(RTSPState),
.read_probe = rtsp_probe,
.read_header = rtsp_read_header,
.read_packet = rtsp_read_packet,
.read_close = rtsp_read_close,
.read_seek = rtsp_read_seek,
.flags = AVFMT_NOFILE,
.read_play = rtsp_read_play,
.read_pause = rtsp_read_pause,
.priv_class = &rtsp_demuxer_class,
};
再看file.c和http
URLProtocol ff_pipe_protocol = {
.name = "pipe",
.url_open = pipe_open,
.url_read = file_read,
.url_write = file_write,
.url_get_file_handle = file_get_handle,
.url_check = file_check,
.priv_data_size = sizeof(FileContext),
.priv_data_class = &pipe_class,
};
URLProtocol ff_httpproxy_protocol = {
.name = "httpproxy",
.url_open = http_proxy_open,
.url_read = http_buf_read,
.url_write = http_proxy_write,
.url_close = http_proxy_close,
.url_get_file_handle = http_get_file_handle,
.priv_data_size = sizeof(HTTPContext),
.flags = URL_PROTOCOL_FLAG_NETWORK,
};
以上先暂时不管,有个印象就好了,我们来分析他是怎么探测的。
AVInputFormat *av_probe_input_format3(AVProbeData *pd, int is_opened,
int *score_ret)
{
AVProbeData lpd = *pd;
AVInputFormat *fmt1 = NULL, *fmt;
int score, nodat = 0, score_max = 0;
const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
******省略*****
fmt = NULL;
while ((fmt1 = av_iformat_next(fmt1))) {
if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
continue; // file和http就因为这个continue了。而从上面的ff_rtsp_demuxer.flag 可以看出可以进行下一步
score = 0;
if (fmt1->read_probe) {
score = fmt1->read_probe(&lpd);//rtsp的返回score=AVPROBE_SCORE_MAX,有很多虽然到了这一步但是返回时还是0
if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
if (nodat == 0) score = FFMAX(score, 1);
else if (nodat == 1) score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
else score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
}
} else if (fmt1->extensions) {
if (av_match_ext(lpd.filename, fmt1->extensions))
score = AVPROBE_SCORE_EXTENSION;
}
if (av_match_name(lpd.mime_type, fmt1->mime_type))
score = FFMAX(score, AVPROBE_SCORE_MIME);
if (score > score_max) {
score_max = score;
fmt = fmt1;
} else if (score == score_max)//如果是0 的话,在这一步fmt还是等于null,也就是整个探测没有任何意义了,就会执行avio_open2函数了。
fmt = NULL;
}
if (nodat == 1)
score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
*score_ret = score_max;
return fmt;
}
由于rtsp在av_probe_input_format3探测成功,所以现在直接返回到avformat_open_input函数,然后执行到
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0) //由于探测成个这个时候iformat=ff_rtsp_demuxer
goto fail;
所以现在就进入
static int rtsp_read_header(AVFormatContext *s)
{
*********** 省略*********
if (rt->rtsp_flags & RTSP_FLAG_LISTEN) {
ret = rtsp_listen(s);
if (ret)
return ret;
} else {
ret = ff_rtsp_connect(s); //在这里调用:
if (ret)
return ret;
}
*********** 省略*********
return 0;
}
ret = ff_rtsp_connect(s); //在这里调用:
1,ffurl_connect ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL);
2, err = ff_rtsp_setup_input_streams(s, reply);//describe报文
3,err = ff_rtsp_make_setup_request(s, host, port, lower_transport,
rt->server_type == RTSP_SERVER_REAL ?
real_challenge : NULL);