Android MediaPlayer中的RTSP(三):相关修改总结

18 篇文章 2 订阅
12 篇文章 4 订阅

背景:

前面两章节简单介绍了RTSP协议,FFmpeg中RTSP的简单交互流程。之前提到过,实际应用中,对于不同项目,对FFmpeg需要进行完善,以适应各种特殊情况。

之前列的情况如下:
1、SETUP阶段的协议选择:载流协议是UDP还是TCP,是否使用RTP承载,尝试一种载流协议不支持后的切换流程
2、重定向的完善:正常来说,一般在SETUP阶段后,就不会再有重定向了,因为这样需要重新断开再连接,但是实际使用中有些服务器是会在PLAY阶段去重定向的
3、NAT穿透: 使用UDP载流时,处于内网的客户端该如何进行NAT的打洞,以让服务器能将数据传送给客户端
4、媒体服务器的选择:RTP-Info给的服务器和DESCRIBE/SETUP给的服务器不一样时,如何切换为RTP-Info指定的服务器。
5、与MediaPlayer的整合:PLAY中使用绝对时间描述时,该如何与MediaPlayer整合

接下来进行相关修改的说明。

相关修改

1、SETUP阶段的协议选择

这个比较简单,上一章节说过,SETUP是在connect阶段进行的,解析播放地址url携带的参数,根据携带的”udp”/”tcp”/”http”,设置lower_transport_mask标志位,根据标志位会一个个进行尝试。
而对于已经确定的项目或者服务器,可以根据属性配置或者是服务器类型(服务器类型可以在ff_rtsp_parse_line函数中对"Server:"携带的参数进行判断),在ff_rtsp_make_setup_request指定用什么载流,这样可以避免多余的SETUP重试。

2、重定向的完善

原生FFmpeg并不支持在PLAY命令发送的时候进行重定向操作,一般的服务器也不会有这样的操作,因为如有重定向的需要,connect阶段(DESCRIBE->SETUP)可以进行重定向操作,而如果在PLAY阶段再进行重定向,则需要断开连接再重新连接,会加长播放的起播速度。
当然,实际情况下中兴的服务器是会PLAY进行重定向操作的,按照上面说的流程,需要将其进行断开再重新连接。
对于这种情况的修改,最初的修改是在rtsp_read_header函数内部进行修改,给302这类重定向返回特定错误码,断开连接后重新跳到ff_rtsp_connect函数开头,用新的URL重新连接。
修改如下:

+++ rtspdec.c	(working copy)
@@ -237,7 +237,16 @@
         }
         ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL);
         if (reply->status_code != RTSP_STATUS_OK) {
-            return -1;
+            if(reply->status_code >=300 && reply->status_code < 400 && s->iformat){
+                av_strlcpy(s->filename, reply->location, sizeof(s->filename));
+                if(rt->playseekFlag == 1){
+            	    av_strlcatf(s->filename, sizeof(s->filename),"&playseek=%sZ", rt->playseekTime);
+                }  
+                av_log(s, AV_LOG_ERROR, "Redirecting to %s\n", s->filename);
+                return -(reply->status_code);
+            }
+            else
+                return -1;
         }
         if (rt->transport == RTSP_TRANSPORT_RTP &&
             reply->range_start != AV_NOPTS_VALUE) {
@@ -365,6 +374,8 @@
 {
     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);
@@ -407,9 +418,12 @@
     if (rt->initial_pause) {
          /* do not start immediately */
     } else {
-         if (rtsp_read_play(s) < 0) {
+         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;
         }
     } 

原理是这样,但这样简单修改会导致很多地方崩溃,例如对于已经关闭的连接,stream等,在重新连接的时候会有重新free等操作造成崩溃,有些是已经alloc的成员多次alloc造成崩溃,有些涉及到了底层RTP/TCP层的修改,为了避免对其他模块造成影响,最后没有采取这种方式,而是在更上层进行修改。

上一章节说过rtsp_read_header的入口是在avformat_open_input中,当错误代码是RTSP 重定向的错误代码,则再次调用avformat_open_input,因为AVFormatContext在RTSP demux中就已经free,所以无法将重定向的URL带回去上层,只能用比较丑陋的方式(全局变量)将URL给到avformat_open_input。这样修改能完整地重新走一遍FFmpeg打开文件的流程,好处是修改的地方较少,比较可靠,较为简单,不好的地方则是代码比较混乱。
修改如下:

avformat_open_input:
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;
Index: rtsp.c
===================================================================
--- rtsp.c	(revision 151774)
+++ rtsp.c	(revision 151775)
@@ -538,8 +538,10 @@
                 avformat_free_context(rtpctx);
             } else if (rt->transport == RTSP_TRANSPORT_RDT && CONFIG_RTPDEC)
                 ff_rdt_parse_close(rtsp_st->transport_priv);
-            else if (CONFIG_RTPDEC)
+            else if (CONFIG_RTPDEC){
+			if(!rtsp_st->transport_priv)
                 rtp_parse_close(rtsp_st->transport_priv);
+            }
         }
         rtsp_st->transport_priv = NULL;
         if (rtsp_st->rtp_handle)
Index: utils.c
===================================================================
--- utils.c	(revision 151774)
+++ utils.c	(revision 151775)
@@ -1018,6 +1018,7 @@
     return av_probe_input_buffer(s->pb, &s->iformat, filename, s, 0, 0);
 }
 
+extern char rtsp_play_redirect_url[1024] = {0};
 
 int avformat_open_input_header(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options, const char *headers)
 {
@@ -1035,6 +1036,10 @@
     if ((ret = av_opt_set_dict(s, &tmp)) < 0)
         goto fail;
         
+     if(rtsp_play_redirect_url[0] ){
+        filename = av_strdup(rtsp_play_redirect_url);
+        av_log(NULL,NULL,"ugly code for RTSP PLAY 302 -%s---\n",filename);
+     memset(rtsp_play_redirect_url,0,sizeof(rtsp_play_redirect_url));}
  av_log(NULL,NULL,"-%s---\n",__FUNCTION__);
     if ((ret = init_input(s, filename, headers, &tmp)) < 0)
         goto fail;
Index: avio.c
===================================================================
--- avio.c	(revision 151774)
+++ avio.c	(revision 151775)
@@ -530,8 +530,12 @@
     int ret = 0;
     if (!h) return 0; /* can happen when ffurl_open fails */
 
+	if(!h->prot)
+		return 0;
     if (h->is_connected && h->prot->url_close)
+	{	
         ret = h->prot->url_close(h);
+	}
 #if CONFIG_NETWORK
     ff_network_close();
 #endif
Index: rtspdec.c
===================================================================
--- rtspdec.c	(revision 151774)
+++ rtspdec.c	(revision 151775)
@@ -45,6 +45,7 @@
 static int _rtsp_read_packet(AVFormatContext *s, AVPacket *pkt) ;
 static int rtsp_read_flush(AVFormatContext *s);
 void rtsp_invoke(void *para);
+char rtsp_play_redirect_url[1024] ;
 
 static void *recv_buffer_task( void *_AVFormatContext)
 {
@@ -386,7 +387,6 @@
     RTSPState *rt = s->priv_data;
     int ret;
     
     av_log(NULL, AV_LOG_INFO, "[%s:%d]\n", __FUNCTION__, __LINE__);
     rt->send_keepalive=0;
     ret = ff_rtsp_connect(s);
@@ -431,26 +431,20 @@
     } 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
+                av_log(NULL, AV_LOG_INFO, "play redirect to :%s",s->filename);
+                av_strlcpy(rtsp_play_redirect_url, s->filename, 1024);
+                rtsp_play_redirect_url[1023] = '\0';
+                return ret;
             }


             return AVERROR_INVALIDDATA;
         }

     } 
     av_log(s, AV_LOG_INFO, "[%s]transport=%d timeout=%d div=%d\n",__FUNCTION__,rt->transport,rt->timeout,rt->keeplive_div);
     return 0;
 }

3、NAT穿透

使用UDP载流时,根据组网的情况,处于内网的客户端可能需要进行NAT的打洞,以让服务器能将数据传送给客户端 。
FFmpeg中本身有这样的处理机制,不同版本函数名称不同,ff_rtp_send_punch_packets/rtp_send_punch_packets。原生代码中,打洞是在SETUP之后,PLAY之前,但对于中兴服务器,需要将其放在PLAY之后,而且打洞机制需要和心跳一样,间隔一段时间就发送一次。怎么发送,和第4点修改一起说明。

4、媒体服务器的选择

按照RTSP协议,媒体服务器在DESCRIBE/SETUP中都会给我们服务器地址,
RTP-Info是服务器回复我们的媒体服务器信息,但是对于实际情况RTP-Info给的服务器和DESCRIBE/SETUP给的服务器并不一样,而且按照标准RTSP流程接收DESCRIBE/SETUP指定地址并无流是,我们就得考虑使用RTP-Info给的服务器地址了,同样的,如果是UDP载流,需要发送NAT包。
RTP-Info字段的解析及媒体服务器地址切换修改如下:

Index: rtsp.c
===================================================================
--- rtsp.c	(revision 151103)
+++ rtsp.c	(revision 151104)
@@ -781,8 +781,17 @@
         p++;
         get_word_sep(value, sizeof(value), ";, ", &p);
         read++;
-        if (!strcmp(key, "url"))
+        if (!strcmp(key, "url")){
             av_strlcpy(url, value, sizeof(url));
+
+            //wusc add for zte server rtpinfo
+            if(NULL != strstr(rt->server, "ZXUS") || NULL != strstr(rt->server, "ZMSS")){
+                memset(rt->rtpinfo_url,0,sizeof(rt->rtpinfo_url));  
+                av_strlcpy(rt->rtpinfo_url, value, sizeof(rt->rtpinfo_url));
+                av_log(NULL, AV_LOG_ERROR, "ZXUS/ZMSS  server should ignore transports source\n");
+            }
+        }
+
         else if (!strcmp(key, "seq"))
             seq = strtoul(value, NULL, 10);
         else if (!strcmp(key, "rtptime"))
@@ -1173,7 +1182,7 @@
                     /* 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);
+			av_log(NULL, AV_LOG_INFO, "[%s]ffurl_open,handle=%d\n", __FUNCTION__, rtsp_st->rtp_handle);
                         goto rtp_opened;
                     }
                 }
@@ -1307,6 +1316,7 @@
                 ff_url_join(url, sizeof(url), "rtp", NULL, host,
                             reply->transports[0].server_port_min, "%s", options);
             }

             if (!(rt->server_type == RTSP_SERVER_WMS && i > 1) &&
                 rtp_set_remote_url(rtsp_st->rtp_handle, url) < 0) {
                 err = AVERROR_INVALIDDATA;
@@ -1318,8 +1328,12 @@
              */
             if (!(rt->server_type == RTSP_SERVER_WMS && i > 1) && s->iformat &&
                 CONFIG_RTPDEC){
-                    rtp_send_zte_punch_packets(rtsp_st->rtp_handle,rt->rtsp_hd);//ZTE NAT
-                    rtp_send_punch_packets(rtsp_st->rtp_handle);
+                    if(NULL != strstr(rt->server, "ZXUS") || NULL != strstr(rt->server, "ZMSS")){
+                        av_log(s, AV_LOG_ERROR, "ZXUS/ZMSS  server send NAT after PLAY\n");
+                    }else{
+                        rtp_send_zte_punch_packets(rtsp_st->rtp_handle,rt->rtsp_hd);//ZTE NAT
+                        rtp_send_punch_packets(rtsp_st->rtp_handle);
+                    }
                 }
             break;
         }
Index: rtsp.h
===================================================================
--- rtsp.h	(revision 151103)
+++ rtsp.h	(revision 151104)
@@ -373,6 +373,7 @@
     //add by wusc for UT Apk eg:playseek=20180423T115645Z
     int playseekFlag;
     char playseekTime[128];
+    char rtpinfo_url[1024];//for zte server
 } RTSPState;
 
 /**
Index: rtspdec.c
===================================================================
--- rtspdec.c	(revision 151103)
+++ rtspdec.c	(revision 151104)

@@ -553,6 +568,27 @@
 
     /* send dummy request to keep TCP connection alive */
     if ((av_gettime() - rt->last_cmd_time) / 1000000 >= (rt->timeout / keeplive_div) || rt->send_keepalive == 0) {
+         //add by wusc for zte server rtpinfo
+        if(rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP && rt->rtpinfo_url[0] != 0
+           && (NULL != strstr(rt->server, "ZXUS") || NULL != strstr(rt->server, "ZMSS"))){
+            char url[1024],host[20]="";
+            int port=0;
+            av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port,NULL, 0,rt->rtpinfo_url);
+            ff_url_join(url, sizeof(url), "rtp", NULL, host,port , "%s", "");
+            av_log(NULL, AV_LOG_ERROR, "rt->rtpinfo_url %s \nset rtp url[%s] \n",rt->rtpinfo_url,url);
+            for (int i = 0; i < rt->nb_rtsp_streams; i++){
+                RTSPStream *rtsp_st = rt->rtsp_streams[i];
+                RTPDemuxContext *rtpctx = rtsp_st->transport_priv;
+                if (!rtpctx || rtsp_st->stream_index < 0)
+                    continue;
+                
+                int ret = rtp_set_remote_url(rtsp_st->rtp_handle, url) ;
+                av_log(NULL, AV_LOG_ERROR, "ZTE NAT rtp_set_remote_url ret = %d\n",ret);
+                rtp_send_zte_punch_packets(rtsp_st->rtp_handle,rt->rtsp_hd);//ZTE NAT
+                rtp_send_punch_packets(rtsp_st->rtp_handle);
+            }
+        }
+        
         if (rt->server_type == RTSP_SERVER_WMS ||
            (rt->server_type != RTSP_SERVER_REAL &&
             rt->get_parameter_supported)) {

逻辑比较简单,即在PLAY命令成功后解析出RTP-Info携带URL,在PLAY命令返回正确后,重新设置RTP handle的URL(接收媒体数据的URL)为新的URL,调用rtp_send_punch_packets网新地址发送NAT穿透包。
需要注意的是,对于音视频分开的情况,则是nb_rtsp_streams大于1,RTP-Info携带的URL则也大于1,需要分开重新设置,不过本身这种流程并不符合RTSP协议的标准流程,所以该处修改并没考虑这种情况。

5、实时流播放,使用绝对时间描述发送PLAY命令

Android MediaPlayer中,RTSP的播放一般是用于点播,基本用的都是相对时间,而对于一些直播流的时移,则是使用绝对时间来发送PLAY命令,APK会在URL携带playseek字段,客户端则解析该字段进行PLAY操作。
修改如下:

Index: rtsp.c
===================================================================
--- rtsp.c	(revision 149739)
+++ rtsp.c	(revision 149740)
@@ -1374,7 +1387,9 @@
     socklen_t peer_len = sizeof(peer);
     int support_options =1;
     int failed_to_retry =0 ;

+    rt->playseekFlag = 0;
+    memset(rt->playseekTime,0x00,sizeof(rt->playseekTime));
+	
     if (!ff_network_init())
         return AVERROR(EIO);
 redirect:
@@ -1413,7 +1428,22 @@
                 rt->control_transport = RTSP_MODE_TUNNEL;
             } else if (!strcmp(option, "filter_src")) {
                 rt->filter_source = 1;
-            } else {
+            } else if (strstr(option, "playseek=")) {//add by wusc for UT Apk eg:playseek=20180423T115645Z
+                lower_transport_mask |= (1<< RTSP_LOWER_TRANSPORT_UDP);
+		rt->playseekFlag = 1;
+		av_log(NULL, AV_LOG_INFO, "[%s:%d]playseek option:%s", __FUNCTION__,__LINE__, option);
+		char *start = strstr(option, "playseek=") + 9;
+		int len = 0;
+		if(strchr(start, '&')){
+			len = strchr(start, '&') - start;
+		}else{
+			len = strlen(start);
+		}
+		av_strlcpy(rt->playseekTime, start, len);
+		rt->playseekTime[len] = '\0';  
+		av_log(NULL, AV_LOG_INFO, "[%s:%d]len is %d,set playseek:%s", __FUNCTION__,__LINE__,len, rt->playseekTime);
+            }
+	    else {
                 /* Write options back into the buffer, using memmove instead
                  * of strcpy since the strings may overlap. */
                 int len = strlen(option);
Index: rtsp.h
===================================================================
--- rtsp.h	(revision 149739)
+++ rtsp.h	(revision 149740)
@@ -368,6 +368,9 @@
     int use_protocol_mode;
     void *priv_data;
     int send_keepalive;
+    //add by wusc for eg:playseek=20180423T115645Z
+    int playseekFlag;
+    char playseekTime[128];
 } RTSPState;
 
 /**
Index: rtspdec.c
===================================================================
--- rtspdec.c	(revision 149739)
+++ rtspdec.c	(revision 149740)
@@ -186,6 +186,7 @@
             for (i = 0; i < rt->nb_rtsp_streams; i++) {
                 RTSPStream *rtsp_st = rt->rtsp_streams[i];
                 RTPDemuxContext *rtpctx = rtsp_st->transport_priv;

                 if (!rtpctx)
                     continue;
                 ff_rtp_reset_packet_queue(rtpctx);
@@ -198,7 +199,7 @@
         if (rt->state == RTSP_STATE_PAUSED) {
             cmd[0] = 0;
         } else {

@@ -208,20 +209,24 @@
                // seek, fast-forward/fast-rewind triggered seek 
                if (rt->playback_rate_permille != rt->playback_rate_permille_next)
                        rt->playback_rate_permille = rt->playback_rate_permille_next;
-
-               if (rt->playback_rate_permille >= 0) {
-                       // forward
-                       snprintf(cmd, sizeof(cmd),
-                               "Range: npt=%"PRId64".%03"PRId64"-\r\n",
-                               rt->seek_timestamp / AV_TIME_BASE,
-                               rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
-               } else {
-                       // backward
-                       snprintf(cmd, sizeof(cmd),
-                               "Range: npt=%"PRId64".%03"PRId64"-0\r\n",
-                               rt->seek_timestamp / AV_TIME_BASE,
-                               rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
-               }
+               if(rt->playseekFlag == 1){
+                       snprintf(cmd, sizeof(cmd),"Range: clock=%s.00Z-\r\n",rt->playseekTime);
+               }else{
+                       if (rt->playback_rate_permille >= 0) {
+                            // forward
+                            snprintf(cmd, sizeof(cmd),
+					   "Range: npt=%"PRId64".%03"PRId64"-\r\n",
+					   rt->seek_timestamp / AV_TIME_BASE,
+					   rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
+                       } else {
+                             // backward
+                            snprintf(cmd, sizeof(cmd),
+					   "Range: npt=%"PRId64".%03"PRId64"-0\r\n",
+					   rt->seek_timestamp / AV_TIME_BASE,
+					   rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);
+                       }
+	       }
+			   
                length = strlen (cmd);
                snprintf(&cmd [length], sizeof(cmd) - length,
                                "Scale: %d.%d\r\n",

修改也比较简单,在ff_rtsp_connect函数中解析URL options时,如果携带playseek=参数,则将其保存,在PLAY阶段发送时以Range: clock=%xxxxxxxxx.00Z-的形式发送即可。
需要注意的是,如果在PLAY返回重定向时,重定向的地址可能不会携带playseek字段,如果重定向前的有playseek字段,则新的URL也需要携带上。

总结

以上几种是针对特殊前端的一些整合修改,其实实际上还有一些小的点需要注意,例如对于有些服务器设置载流需要发送"MP2T/RTP"前缀,有些服务器不能那么快发送NAT穿透包,有些服务器请求的URL必须携带某些参数等等。
实际情况需要在熟悉RTSP协议的基础上,针对各种情况将其一步步完善,本文提供了一些修改参考,三篇文章对RTSP协议的入门及FFmpeg中RTSP的实现都能有初步了解。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值