开发环境及工具: ubuntu 12.04 32位机器 wireshark
知识点:NAT,网络地址转换,主要是为了实现地址复用的一个玩意,对接服务器是ZTE的,给了个很老的NAT文档,基本没啥用,还是靠抓包,已有的能播放的播放器的抓包如下(点播),
协议交互:
DESCRIBE rtsp://58.223.255.214:554/vod/84136900020005436260.mpg?userid=adtest1&stbip=114.221.131.188&clienttype=1&mediaid=0000000003020005244432&ifcharge=1&time=20130424102533+08&life=172800&usersessionid=2875009&vcdnid=vcdn001&boid=001&srcboid=001&columnid=1000000E&backupagent=58.223.255.214:554&ctype=1&playtype=0&Drm=0&EpgId=epg_nj_001&programid=84136900020005436260&contname=&fathercont=&bp=0&authid=5703268124&tscnt=0&tstm=0&tsflow=0&ifpricereqsnd=1&nodelevel=3&usercharge=723A0360F38E98FA21A457BE1AB9A8DB RTSP/1.0
CSeq: 1
User-Agent: CTC RTSP 1.0
x-zmssFecCDN: yes
x-NAT:192.168.1.145:43445 --------------------------------rtsp信令连接的地址
x-zmssRtxSdp: yes
x-Index:
RTSP/1.0 302 Found ---------------------------------------重定向跑到location
Server: ZXUSS100 1.0
Location: rtsp://180.96.175.84:554/vod/84136900020005436260.mpg?userid=adtest1&mediaid=0000000003020005244432&ifcharge=1&clienttype=1&time=20130424102533+08&life=172800&usersessionid=2875009&ifpricereqsnd=1&Bp=0&vcdnid=vcdn001&Drm=0&ctype=1&boid=001&columnid=1000000E&fathercont=&playtype=0&stbip=114.221.131.188&srcboid=001&backupagent=58.223.255.214:554&EpgId=epg_nj_001&contname=&tscnt=0&tstm=0&tsflow=0&authid=5703268124&programid=84136900020005436260&usercharge=6F5039A17DCFE4C5FE3DBA006F288BB4&nodelevel=2&orignode=unit09111309453150&whyrt=3
CSeq: 1
DESCRIBE rtsp://180.96.175.84:554/vod/84136900020005436260.mpg?userid=adtest1&mediaid=0000000003020005244432&ifcharge=1&clienttype=1&time=20130424102533+08&life=172800&usersessionid=2875009&ifpricereqsnd=1&Bp=0&vcdnid=vcdn001&Drm=0&ctype=1&boid=001&columnid=1000000E&fathercont=&playtype=0&stbip=114.221.131.188&srcboid=001&backupagent=58.223.255.214:554&EpgId=epg_nj_001&contname=&tscnt=0&tstm=0&tsflow=0&authid=5703268124&programid=84136900020005436260&usercharge=6F5039A17DCFE4C5FE3DBA006F288BB4&nodelevel=2&orignode=unit09111309453150&whyrt=3 RTSP/1.0
CSeq: 2
User-Agent: CTC RTSP 1.0
x-zmssFecCDN: yes
x-NAT:192.168.1.145:35317
x-zmssRtxSdp: yes
x-Index:
RTSP/1.0 200 OK
Server: ZXUSS100 1.0
Cache-Control: must-revalidate
Content-Base: rtsp://180.96.175.84:554/vod/84136900020005436260.mpg/
Content-Length: 311
Content-Type: application/sdp
CSeq: 2
Date: Wed, 24 Apr 2013 02:25:33 GMT
Expires: Wed, 24 Apr 2013 02:25:33 GMT
v=0
o=- 1929210747 0 IN IP4 0.0.0.0
s=ZMSS RTSP Server
c=IN IP4 0.0.0.0
b=AS:1600
t=0 0
a=control:*
a=range:npt=0.00000-6013.91016
m=video 0 RTP/AVPF 33 96
a=control:trackID=1
a=rtpmap:33 MP2T/90000
a=3GPP-Adaptation-Support:5
a=rtcp-fb:33 nack
a=rtpmap:96 rtx/90000
a=fmtp:96 apt=33;rtx-time=0
SETUP rtsp://180.96.175.84:554/vod/84136900020005436260.mpg/ RTSP/1.0
CSeq: 3
Transport: MP2T/RTP/UDP;unicast;destination=192.168.1.145;client_port=65140-65141,MP2T/TCP;unicast;destination=192.168.1.145;interleaved=0-1,MP2T/RTP/TCP;unicast;destination=192.168.1.145;interleaved=0-1,MP2T/UDP;unicast;destination=192.168.1.145;client_port=65140-65141
User-Agent: CTC RTSP 1.0
x-NAT:192.168.1.145:65140------------------------------真实接收数据流的地址
RTSP/1.0 200 OK
Server: ZXUSS100 1.0
x-KeepAliveInterval: 5000 --------------------------------打洞心跳
CSeq: 3
Date: Wed, 24 Apr 2013 02:25:33 GMT
Expires: Wed, 24 Apr 2013 02:25:33 GMT
Session: 196609438
Transport: MP2T/RTP/UDP;unicast;destination=192.168.1.145;client_port=65140-65141;server_port=11656-11657;source=180.96.175.50
PLAY rtsp://180.96.175.84:554/vod/84136900020005436260.mpg?userid=adtest1&mediaid=0000000003020005244432&ifcharge=1&clienttype=1&time=20130424102533+08&life=172800&usersessionid=2875009&ifpricereqsnd=1&Bp=0&vcdnid=vcdn001&Drm=0&ctype=1&boid=001&columnid=1000000E&fathercont=&playtype=0&stbip=114.221.131.188&srcboid=001&backupagent=58.223.255.214:554&EpgId=epg_nj_001&contname=&tscnt=0&tstm=0&tsflow=0&authid=5703268124&programid=84136900020005436260&usercharge=6F5039A17DCFE4C5FE3DBA006F288BB4&nodelevel=2&orignode=unit09111309453150&whyrt=3 RTSP/1.0
CSeq: 4
Session: 196609438--------------------------------------------关键信息,服务器靠它来判断是哪个用户发来的打洞消息
Scale: 1.000000
Range: npt=0.000-
User-Agent: CTC RTSP 1.0
RTSP/1.0 200 OK
Server: ZXUSS100 1.0
x-KeepAliveInterval: 5000
CSeq: 4
Range: npt=0.00000-
Scale: 1.0
Session: 196609438
RTP-Info: url=rtsp://180.96.175.50:11656/vod/84136900020005436260.mpg/trackID=1
192.168.1.145
:65140==》
180.96.175.5
0:
11656
打洞消息如下:
改UDP消息格式是:常量(ZXV10STB)+sessionid+客户端IP+ 接收数据流端口+信令连接端口,关键信息是sessionid
00000000
5a 58 56 31 30 53 54 42
0b b8 05 9e
c0 a8 01 91
ZXV10STB ........
00000010
fe 74
89 f5
00 00 00 00 00 00 00 00 00 00 00 00 .t...... ........
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........
00000050 00 00 00 00
ffmpeg对rtsp支持的不是很好,建议先调测live555,让整个播放流程走通,再对比抓包修改ffmpeg代码。
ffmpeg代码修改知识点:
a、ffmpeg对于rtsp协议会首先发送option心跳消息,而实际应用上没有免费的午餐,用户必须先通过describe消息进行认证,
因此ff_rtsp_connect函数要进行修改
#if 0
ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL);
if (reply->status_code != RTSP_STATUS_OK) {
err = AVERROR_INVALIDDATA;
goto fail;
}
#endif
b、要支持NAT,describe消息中要携带nat信息,函数ff_rtsp_setup_input_streams 要修改
int rtspfd = rt->rtsp_hd_out->fd;
int len = sizeof(struct sockaddr_in);
struct sockaddr_in sin;
if ( getsockname ( rtspfd, (struct sockaddr *)&sin, (socklen_t *)&len ) == 0 )
INFO("socket port number: %d", ntohs ( sin.sin_port ) );
/* describe the stream */
snprintf(cmd, sizeof(cmd),
"Accept: application/sdp\r\nx-NAT:%s:%d\r\n", inet_ntoa(sin.sin_addr), ntohs ( sin.sin_port ));
c、传输协议类型,ffmpeg要明确表明支持MP2T/RTP(其实就是ts),否则那个傻服务器会给你461哦
d、打洞消息,在信令链路建立成功后要记得进行NAT穿透最关键的一步,打通隧道(打洞)
int send_nat_data(AVFormatContext *s)
{
int i;
int len = sizeof(struct sockaddr_in);
struct sockaddr_in sin;
RTSPState *rt = s->priv_data;
if ( getsockname (rt->rtsp_hd_out->fd, (struct sockaddr*)&sin, (socklen_t *)&len ) == 0 )
;//INFO("socket port number: %s, %d", inet_ntoa(sin.sin_addr),ntohs ( sin.sin_port ) );
unsigned char buf[128] = {0};
int sesstionid = atoi(rt->session_id);
unsigned char sessionstr[5] = {0};
sessionstr[0] = sesstionid >> 24;
sessionstr[1] = (sesstionid >> 16) & 0xff;
sessionstr[2] = (sesstionid >> 8) & 0xff;
sessionstr[3] = sesstionid &0xff;
unsigned int addrid = ntohl(sin.sin_addr.s_addr);
unsigned char addrstr[5] = {0};
addrstr[0] = addrid >> 24;
addrstr[1] = (addrid >> 16) & 0xff;
addrstr[2] = (addrid >> 8) & 0xff;
addrstr[3] = addrid & 0xff;
for ( i = 0; i < rt->nb_rtsp_streams; ++i) {
RTSPStream *rtsp_st = rt->rtsp_streams[i];
unsigned short udpportid = ff_rtp_get_local_rtp_port(rtsp_st->rtp_handle);
unsigned char udpportstr[3] = {0};
udpportstr[0] = udpportid >> 8;
udpportstr[1] = udpportid & 0xff;
unsigned short tcpportid = ntohs(sin.sin_port);
unsigned char tcpportstr[3] = {0};
tcpportstr[0] = tcpportid >> 8;
tcpportstr[1] = tcpportid & 0xff;
snprintf((char *)buf,sizeof(buf), "ZXV10STB%s%s%s%s",
sessionstr, addrstr, udpportstr, tcpportstr);
int ret = ffurl_write(rtsp_st->rtp_handle, buf, 84);
}
return 0;
}
e、nat穿透是UDP的包,注意哦,是UDP,容易走丢,对没错。所以在收不到数据包的时候要继续打洞哦
static int udp_read_packet(AVFormatContext *s, RTSPStream **prtsp_st,
uint8_t *buf, int buf_size, int64_t wait_end)
{
。。。。。。。。。。。
} else if (n == 0) {
if(++timeout_cnt >= MAX_TIMEOUTS)
return AVERROR(ETIMEDOUT);
send_nat_data(s);
} else if (n < 0 && errno != EINTR){
。。。。。。。。。。。。。。。。。
}
欧拉,你看到播放效果了吗,亲^_^