FFMPEG实现带NAT特性IPTV的播放

开发环境及工具: 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.50: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){
。。。。。。。。。。。。。。。。。


欧拉,你看到播放效果了吗,亲^_^
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章