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){
。。。。。。。。。。。。。。。。。


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