AndroidMediaPlayer中的RTSP(⼆):FFmpeg中RTSP代码流程分析
背景:
1、关键结构ff_rtsp_demuxer :
AVInputFormat该结构被称为解复⽤模块,是⾳视频⽂件的⼀个解封装器,对RTSP这种媒体协议,FFmpeg将其当做⼀种封装格式来处理,主要的RTSP协议交互流程也在demux阶段处理。
AVInputFormat ff_rtsp_demuxer = {
"rtsp",
NULL_IF_CONFIG_SMALL("RTSP input format"),
sizeof(RTSPState),
rtsp_probe,
rtsp_read_header,
rtsp_read_packet,
rtsp_read_close,
rtsp_read_seek,
.flags = AVFMT_NOFILE,
.read_play = rtsp_read_play,
.read_pause = rtsp_read_pause,
.priv_class = &rtsp_demuxer_class,
};
当我们调⽤av_register_all接⼝时,便会将ff_rtsp_demuxer 注册进来,可以看到flags设置为AVFMT_NOFILE。
在FFmpeg中,当muxers和demuxers的flags有设AVFMT_NOFILE时,AVIOContext(表⽰字节流输⼊/输出的上下⽂)这个成员变量就不需要设置,因为muxers和demuxers会使⽤⾃⼰的⽅式处理输⼊/输出。
简单来说,就是RTSP的输⼊解析不是使⽤URLProtocol这种⽂件类型的I/O来进⾏,⽽是在demuxer中处理。
connect和join的区别所以,我们最需要关注的是ff_rtsp_demuxer 对应的处理函数。
av_register_all:
REGISTER_MUXDEMUX(RTSP, rtsp);
2、打开⽂件rtsp_read_header
当上层调⽤avformat_open_input函数打开⼀个rtsp播放地址时,会调⽤到read_header,代码如下:
avformat_open_input:
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
调⽤协议对应demux的read_header函数,读取播放格式头并初始化。
在ff_rtsp_demuxer 中,rtsp_read_header函数主要代码如下:
rtsp_read_header:
{
RTSPState *rt = s->priv_data;
int ret;
redirect:
av_log(NULL, AV_LOG_INFO, "[%s:%d]\n", __FUNCTION__, __LINE__);
rt->send_keepalive=0;
ret = ff_rtsp_connect(s);
if (ret)
return ret;
.
.....
if (rt->initial_pause) {
/* do not start immediately */
} else {
ret = rtsp_read_play(s);
if (ret < 0) {
ff_rtsp_close_streams(s);
ff_rtsp_close_connections(s);
if(ret <=-300&& ret > -400)//add by wusc for redirect
goto redirect;
return AVERROR_INVALIDDATA;
}
}
......
}
rtsp_read_header主要进⾏以下两个⼯作:
1、connect流程,包括OPTIONS ->DESCRIBE ->SETUP 过程。
2、根据配置是否进⼊PLAY 。
2.1 connect阶段流程
ff_rtsp_connect函数完成OPTIONS ->DESCRIBE ->SETUP 过程。
对于每⼀个命令前端的回复,FFmpeg会进⾏解析:
if ((ret = ff_rtsp_read_reply(s, reply, content_ptr, 0, method) ) < 0){
av_log(s, AV_LOG_INFO, "ff_rtsp_read_reply %s error status_code: %d\n", method,
reply->status_code);
return ret;
}
ff_rtsp_read_reply:
ff_rtsp_parse_line(reply, p, rt, method);
av_strlcat(rt->last_reply, p, sizeof(rt->last_reply));
av_strlcat(rt->last_reply, "\n", sizeof(rt->last_reply));
RTSP交互主要的代码流程如下:
1、解析播放地址url携带的参数,根据携带的"udp"/“tcp”/“http”,设置lower_transport_mask标志位 ,lower_transport_mask 代表setup阶段,底层RTP数据的传输,⽀持哪⼏种协议(UDP/TCP or 都⽀持)
if (!strcmp(option, "udp")) {
lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP);
} else if (!strcmp(option, "multicast")) {
lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP_MULTICAST);
} else if (!strcmp(option, "tcp")) {
lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP);
} else if(!strcmp(option, "http")) {
lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_TCP);
rt->control_transport = RTSP_MODE_TUNNEL;
} else if (!strcmp(option, "filter_src")) {
rt->filter_source = 1;
2、打开RTSP交互的TCP链接,⽆论RTP数据流是基于UDP还是TCP,RTSP协议交互的部分都是基于TCP的,所以先把url打上"tcp"标签,打开tcp链接。如果没有指定端⼝号,则RTSP默认端⼝号是554。
ff_rtsp_connect:
/* open the tcp connection */
ff_url_join(tcpname, sizeof(tcpname), "tcp", NULL, host, port, NULL);
if(rt->use_protocol_mode){
av_strlcat(tcpname, "?rcvbuf_size=1024", sizeof(tcpname));
}
int ret=ffurl_open(&rt->rtsp_hd, tcpname, AVIO_FLAG_READ_WRITE);
3、发送OPTIONS请求
ff_rtsp_connect:
ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL);
4、发送DESCRIBE请求,并解析前端反馈的SDP消息,SDP消息的解析可以参考上篇⽂章。
请求
ff_rtsp_connect:
if (s->iformat && CONFIG_RTSP_DEMUXER)
err = ff_rtsp_setup_input_streams(s, reply);
ff_rtsp_setup_input_streams:
ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content);
解析SDP
ff_rtsp_setup_input_streams:
/* now we got the SDP description, we parse it */
ret = ff_sdp_parse(s, (const char *)content);
5、发送SETUP请求
ff_rtsp_make_setup_request是SETUP请求的核⼼代码,下⾯分析它的主要代码逻辑:
1)lower_transport 指定的是RTP传输的载流协议,根据上⾯设置的lower_transport_mask 来确定。
int lower_transport = ff_log2_tab[lower_transport_mask &
~(lower_transport_mask - 1)];
float value = 0.0;
int ret = -1;
av_log(NULL, AV_LOG_INFO, "[%s:%d]lowtrans=%d,lowtransm=%d,value=%d\n", __FUNCTION__,__LINE__, lower_transport,lower_transport_mask ,value);
err = ff_rtsp_make_setup_request(s, host, port, lower_transport,
rt->server_type == RTSP_SERVER_REAL ?
real_challenge : NULL);
2)根据SDP消息指定的传输协议rt->transport,设置SETUP请求载流协议的前缀
ff_rtsp_make_setup_request:
if (rt->transport == RTSP_TRANSPORT_RDT)
trans_pref = "x-pn-tng";
else if (rt->transport == RTSP_TRANSPORT_RAW)
trans_pref = "RAW/RAW";
else if (NULL != strstr(rt->server, "ZXUS") || NULL != strstr(rt->server, "ZMSS"))//wusc add for ZXUS/ZMSS server
trans_pref = "MP2T/RTP";
else
trans_pref = "RTP/AVP";
3)根据载流协议的不同,后续的处理流程也有些不同,我们以最常见的TCP和UDP协议来分析。
TCP⽅式Transport: MP2T/RTP/TCP;unicast;interleaved=0-1
如果采⽤TCP⽅式传送,
a.设置传输⽅式,即前缀后加上/TCP(MP2T/RTP/TCP),同时,设置为单播unicast
b.设置interleaved参数,如interleaved=0-1。因为传送的RTP,RTCP包都在同⼀个链路上,需要区分,所以有了interleaved,0表⽰是RTP的通道,1表⽰是RTCP的通道,interleaved值有两个:0和1,0表⽰RTP包,1表⽰RTCP包,接收端根据interleaved的值来区别是哪种数据包。
ff_rtsp_make_setup_request:
else if (lower_transport == RTSP_LOWER_TRANSPORT_TCP) {
......
snprintf(transport, sizeof(transport) - 1,
"%s/TCP;", trans_pref);
if (rt->transport != RTSP_TRANSPORT_RDT)
av_strlcat(transport, "unicast;", sizeof(transport));
av_strlcatf(transport, sizeof(transport),
"interleaved=%d-%d",
interleave, interleave + 1);
interleave += 2;
}
UDP⽅式Transport: MP2T/RTP/UDP;unicast;client_port=5000-5001
如果采⽤UDP⽅式传送,
a.⾸先也需要⼀对RTSP端⼝来作为RTP通道及RTCP通道,ff_url_join打上"rtp"标签,ffurl_open打开rtp传送通道。
b.设置传输⽅式,即前缀后加上/UDP(MP2T/RTP/UDP)。设置为单播unicast。
c.UDP需要携带client_port参数,将⾃⼰使⽤的RTP和RTCP端⼝号发送给前端,这样前端才知道需要把RTP数据发送给哪个端⼝。
ff_rtsp_make_setup_request:
while (j <= RTSP_RTP_PORT_MAX) {
ff_url_join(buf, sizeof(buf), "rtp", NULL, host, -1,
"?localport=%d", j);
/* we will use two ports per rtp stream (rtp and rtcp) */
j += 2;
if (ffurl_open(&rtsp_st->rtp_handle, buf, AVIO_FLAG_READ_WRITE) == 0){
av_log(NULL, AV_LOG_INFO, "[%s]ffurl_open,handle=%d\n", __FUNCTION__, rtsp_st->rtp_handle);
goto rtp_opened;
}
......
rtp_opened:
port = rtp_get_local_rtp_port(rtsp_st->rtp_handle);
have_port:
snprintf(transport, sizeof(transport) - 1,
"%s/UDP;", trans_pref);
if (rt->server_type != RTSP_SERVER_REAL)
av_strlcat(transport, "unicast;", sizeof(transport));
av_strlcatf(transport, sizeof(transport),
"client_port=%d", port);
if (rt->transport == RTSP_TRANSPORT_RTP &&
!(rt->server_type == RTSP_SERVER_WMS && i > 0))
av_strlcatf(transport, sizeof(transport), "-%d", port + 1);
6.SETUP 回复消息解析
如果设置的载流协议不⽀持,前端会回复461
RTSP/1.0 461 Unsupported transport
Server: ZXUSS100 1.0
CSeq: 3
对于461,FFmpeg会取消失败的这种载流协议,设置新的lower_transport_mask ,直到lower_transport_mask == 0(即客户端设置的⼏种协议都已经试完)。
ff_rtsp_connect:
err = ff_rtsp_make_setup_request(s, host, port, lower_transport,
rt->server_type == RTSP_SERVER_REAL ?
real_challenge : NULL);
if (err < 0)
goto fail;
lower_transport_mask &= ~(1 << lower_transport);
if (lower_transport_mask == 0 && err == 1) {
err = AVERROR(EPROTONOSUPPORT);
goto fail;
}
} while (err);
SETUP 消息发送成功的情况下,
1)、对于TCP载流的⽅式,FFmpeg会将前端服务器回复的interleaved参数作为正式的RTP/RTCP通道编号。
ff_rtsp_make_setup_request:
case RTSP_LOWER_TRANSPORT_TCP:
rtsp_st->interleaved_min = reply->transports[0].interleaved_min;
rtsp_st->interleaved_max = reply->transports[0].interleaved_max;
break;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论