1. 为什么有 RTSP? 这要从 RTP 说起。RTP 是实时传输协议。具体请参考 RFC3550。RTP 在实际应用中,是被动推送的方式。 即如下图的流程。
则将会产生如下对话:(M: Media Server; C: Client)
C:谁给我发视频呢? M:我! C:谁让你给我发视频的? M:主人。 C:我现在又不用,你发给我干嘛? M:主人让我发的,我也不知道为啥要发。 C:…… M:我只能持续的发,发啊发….. C:发你妹~!我死了。 M:那我还是要发,我又不知道你死了,发啊发…… C,M:主人傻×。
RTP 的被动发送方式,使得不管客户端是否准备好接收视频流,是否异常死掉,媒体服务都会不断往外发送视频流。这种处理方式是不符合实际业务逻辑的。正常的业务逻辑应该是,客户端要视频,媒体服务才发送视频;客户端不要视频了,媒体服务停止发送视频;客户端异常死掉了,媒体服务应该能检测到,并停止发送视频。 所以,实际解决方式有两种: I. 发送到组播组,由网络设备控制。即,媒体服务发送到一个组播地址,客户端加入到组播组后,由网络设备发送视频。当客户端退出组播组,网络设备停止发送给客户端。所有的控制均由组播协议IGMP 相关进行控制。 II. 通过 RTSP 协议。其实 RTSP 协议就是一个媒体服务的控制协议。请参考RFC2326。请翻译下面一段话。 The Real Time Streaming Protocol, or RTSP, is an application-level protocol for control over the delivery of data with real-time properties.
2. 下载 Live555 源码及 VLC1.1.9 版本 下载 Live555 源码,并解压。 下载slamtv60.264,并放置到 live 源码下的 mediaServer 目录。 下载VLC1.1.9版本
3. 编译 Live555 不编译 testProg 及 proxyServer 目录,没用也耗时。修改 Makefile.tail 文件,注释加'#'号 vim Makefile.tail all: cd $(LIVEMEDIA_DIR) ; $(MAKE) cd $(GROUPSOCK_DIR) ; $(MAKE) cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE) cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE) #cd $(TESTPROGS_DIR) ; $(MAKE) cd $(MEDIA_SERVER_DIR) ; $(MAKE) #cd $(PROXY_SERVER_DIR) ; $(MAKE) install: cd $(LIVEMEDIA_DIR) ; $(MAKE) install cd $(GROUPSOCK_DIR) ; $(MAKE) install cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE) install cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE) install #cd $(TESTPROGS_DIR) ; $(MAKE) install cd $(MEDIA_SERVER_DIR) ; $(MAKE) install #cd $(PROXY_SERVER_DIR) ; $(MAKE) install clean: cd $(LIVEMEDIA_DIR) ; $(MAKE) clean cd $(GROUPSOCK_DIR) ; $(MAKE) clean cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE) clean cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE) clean #cd $(TESTPROGS_DIR) ; $(MAKE) clean cd $(MEDIA_SERVER_DIR) ; $(MAKE) clean #cd $(PROXY_SERVER_DIR) ; $(MAKE) clean 修改 config.linux,修改编译器 vim config.linux C_COMPILER = gcc CPLUSPLUS_COMPILER = g++ LINK = g++ -o 生成 Makefile,执行命令 ./genMakefile linux 编译程序,执行命令 make 经过一长串文字之后,会在 mediaServer 目录下,生成 live555MediaServer。若没生成,请从头执行。
4. 打开 Live555 的调试信息
修改 UsageEnvironment/include 目录下的 UsageEnvironment.hh 文件,加入宏定义 vim UsageEnvironment/include/UsageEnvironment.hh #ifndef _USAGE_ENVIRONMENT_HH #define _USAGE_ENVIRONMENT_HH #define DEBUG 1 // added by jeremy #ifndef _USAGEENVIRONMENT_VERSION_HH 重新编译,执行命令 make 这样 Live555 里面很多 fprintf(stderr, ...)的信息,就能显示了。
5. 启动 Live555 执行命令 cd mediaServer ./live555MediaServer > log.txt 2>&1 重启一个命令行窗口,cd 到 live 源码目录下的 mediaServer 目录,执行命令 cat log.txt 查看 rtsp 地址及端口
解释命令 > log.txt 是将打印信息保存到 log.txt 文件中。 2>&1 是将 stderr 输出到 stdout 从图可以看到 rtsp 访问地址为 rtsp://192.168.1.198:8554/slamtv60.264。 解释 where <filename> is a file present in the current directory. 请自己翻译。 8554 是因为没用 sudo 用户,无法绑定到 554 端口。 最新开的命令行窗口,执行命令 tail -f log.txt 此命令将会不断刷新 log.txt 的内容到屏幕上。
6. 启动客户端 宿主机 windoz 上,开始->运行->cmd 执行命令 cd vlc-1.1.9 vlc -vvv --extraintf=logger rtsp://192.168.1.198:8554/slamtv60.264 最后一条命令,是用命令行开启 vlc,并在后台开启调试窗口,同时打开 URL。 如果操作无问题,那么这时应该播放视频了。请播放 30 秒后,按 VLC 的停止播放按钮。 7. 获取日志 Live555 日志都保存到了虚拟机 log.txt 中 VLC 的日志都在后台,通过拖选,右键,复制到剪贴板后,新建个文本文件,进行保存。 8. 分析 Live555 的日志 从 Live555 的 log.txt 日志,我们可以看到整个的处理流程。 accept 连接并显示内容 accept()ed connection from 192.168.1.98 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 132 new bytes:OPTIONS rtsp://192.168.1.198:8554/slamtv60.264 RTSP/1.0 CSeq: 2 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) 可以看到地址是 192.168.1.98,使用的库是 LibVLC/1.1.9,它使用的 Live555的库是 2011.01.06 版本。 解析获取到 Option 请求,url 是 slamtv60.264 parseRTSPRequestString() succeeded, returning cmdName "OPTIONS", urlPreSuffix "", urlSuffix "slamtv60.264", CSeq "2", Content-Length 0, with 0 bytes following the message. send 回应信息 sending response: RTSP/1.0 200 OK CSeq: 2 Date: Fri, Sep 27 2013 03:44:08 GMT Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER Live555 支持的请求有 OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER 接收到请求 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 158 new bytes:DESCRIBE rtsp://192.168.1.198:8554/slamtv60.264 RTSP/1.0 CSeq: 3 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Accept: application/sdp 解析为 Describe,并解析了 url 对应的文件的信息 parseRTSPRequestString() succeeded, returning cmdName "DESCRIBE", urlPreSuffix "", urlSuffix "slamtv60.264", CSeq "3", Content-Length 0, with 0 bytes following the message. H264VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error) Parsed 22-byte NAL-unit (nal_ref_idc: 3, nal_unit_type: 7 ("Sequence parameter set")) profile_idc: 77 constraint_setN_flag: 64 level_idc: 51 seq_parameter_set_id: 0 log2_max_frame_num_minus4: 3 pic_order_cnt_type: 0 log2_max_pic_order_cnt_lsb_minus4: 4 max_num_ref_frames: 1 gaps_in_frame_num_value_allowed_flag: 0 pic_width_in_mbs_minus1: 23 pic_height_in_map_units_minus1: 17 frame_mbs_only_flag: 1 frame_cropping_flag: 0 vui_parameters_present_flag: 1 BEGIN vui_parameters aspect_ratio_info_present_flag: 0 overscan_info_present_flag: 0 video_signal_type_present_flag: 0 chroma_loc_info_present_flag: 0 timing_info_present_flag: 1 num_units_in_tick: 2 time_scale: 102 fixed_frame_rate_flag: 1 Set frame rate to 25.500000 fps Presentation time: 1380253448.318998 22 bytes @1380253448.318998, fDurationInMicroseconds: 0 ((0*1000000)/25.500000) Parsed 4-byte NAL-unit (nal_ref_idc: 3, nal_unit_type: 8 ("Picture parameter set")) Presentation time: 1380253448.318998 4 bytes @1380253448.318998, fDurationInMicroseconds: 0 ((0*1000000)/25.500000) Parsed 3017-byte NAL-unit (nal_ref_idc: 2, nal_unit_type: 1 ("Coded slice of a non-IDR picture")) Presentation time: 1380253448.318998 *****This NAL unit ends the current access unit***** 3017 bytes @1380253448.318998, fDurationInMicroseconds: 39215 ((1*1000000)/25.500000) Parsed 3081-byte NAL-unit (nal_ref_idc: 2, nal_unit_type: 1 ("Coded slice of a non-IDR picture")) Presentation time: 1380253448.358213 *****This NAL unit ends the current access unit***** 3081 bytes @1380253448.358213, fDurationInMicroseconds: 39215 ((1*1000000)/25.500000) Parsed 2836-byte NAL-unit (nal_ref_idc: 2, nal_unit_type: 1 ("Coded slice of a non-IDR picture")) Presentation time: 1380253448.397428 *****This NAL unit ends the current access unit***** 2836 bytes @1380253448.397428, fDurationInMicroseconds: 39215 ((1*1000000)/25.500000) 回应 Describe 请求 sending response: RTSP/1.0 200 OK CSeq: 3 Date: Fri, Sep 27 2013 03:44:08 GMT Content-Base: rtsp://192.168.1.198:8554/slamtv60.264/ Content-Type: application/sdp Content-Length: 525 v=0 o=- 1380253448318916 1 IN IP4 192.168.1.198 s=H.264 Video, streamed by the LIVE555 Media Server i=slamtv60.264 t=0 0 a=tool:LIVE555 Streaming Media v2013.09.18 a=type:broadcast a=control:* a=range:npt=0-a=x-qt-text-nam:H.264 Video, streamed by the LIVE555 Media Server a=x-qt-text-inf:slamtv60.264 m=video 0 RTP/AVP 96 c=IN IP4 0.0.0.0 b=AS:500 a=rtpmap:96 H264/90000 a=fmtp:96 packetization-mode=1;profile-level-id=4D4033;sprop-parametersets=Z01AM5JUDAS0IAAAAwBAAAAM0eMGVA==,aO48gA== a=control:track1 application/sdp 说明下面从 v=0 开始,是 264 文件的 sdp 信息。 可以看到客户端接收端口是 61480-61481 接收到请求 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 189 new bytes:SETUP rtsp://192.168.1.198:8554/slamtv60.264/track1 RTSP/1.0 CSeq: 4 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Transport: RTP/AVP;unicast;client_port=61480-61481 解析请求并回应 parseRTSPRequestString() succeeded, returning cmdName "SETUP", urlPreSuffix "slamtv60.264", urlSuffix "track1", CSeq "4", Content-Length 0, with 0 bytes following the message. sending response: RTSP/1.0 200 OK CSeq: 4 Date: Fri, Sep 27 2013 03:44:08 GMT Transport: RTP/AVP;unicast;destination=192.168.1.98;source=192.168.1.198;client_port=61480-61481;server_port=6970-6971 Session: BB58AEA2 协议 RTP/AVP,目的地 192.168.1.98,源 192.168.1.198,客户端端口 61480-61481,服务端 端口 6970-6971,session 号 BB58AEA2 接收到请求 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 168 new bytes:PLAY rtsp://192.168.1.198:8554/slamtv60.264/ RTSP/1.0 CSeq: 5 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Session: BB58AEA2 Range: npt=0.000- Range 是从 0 秒开始到最后。对应 sdp 里面的 a=range:npt=0-这一行。 解析请求,并回应 parseRTSPRequestString() succeeded, returning cmdName "PLAY", urlPreSuffix "slamtv60.264", urlSuffix "", CSeq "5", Content-Length 0, with 0 bytes following the message. RTCPInstance[0x15795c0]::RTCPInstance() schedule(1.257133->1380253449.694797) sending REPORT sending RTCP packet 80c80006 a05b17ca d5ef7d88 700b7803 66a12610 00000000 00000000 81ca0004 a05b17ca 01064e69 755a6169 00000000 H264VideoStreamParser::parse() EXCEPTION (This is normal behavior - *not* an error) sending response: RTSP/1.0 200 OK CSeq: 5 Date: Fri, Sep 27 2013 03:44:08 GMT Range: npt=0.000-Session: BB58AEA2 RTP-Info: url=rtsp://192.168.1.198:8554/slamtv60.264/track1;seq=41987;rtptime=1721837108 其中还启用了 RTCPInstance,进行 RTCP 监听,发送 RTCP 的 packet,内容就是从 80c80006 的一串数字。 再次解析文件 Parsed 22-byte NAL-unit (nal_ref_idc: 3, nal_unit_type: 7 ("Sequence parameter set")) profile_idc: 77 constraint_setN_flag: 64 level_idc: 51 seq_parameter_set_id: 0 log2_max_frame_num_minus4: 3 pic_order_cnt_type: 0 log2_max_pic_order_cnt_lsb_minus4: 4 max_num_ref_frames: 1 gaps_in_frame_num_value_allowed_flag: 0 pic_width_in_mbs_minus1: 23 pic_height_in_map_units_minus1: 17 frame_mbs_only_flag: 1 frame_cropping_flag: 0 vui_parameters_present_flag: 1 BEGIN vui_parameters aspect_ratio_info_present_flag: 0 overscan_info_present_flag: 0 video_signal_type_present_flag: 0 chroma_loc_info_present_flag: 0 timing_info_present_flag: 1 num_units_in_tick: 2 time_scale: 102 fixed_frame_rate_flag: 1 Set frame rate to 25.500000 fps Presentation time: 1380253448.432657 22 bytes @1380253448.432657, fDurationInMicroseconds: 0 ((0*1000000)/25.500000) Parsed 4-byte NAL-unit (nal_ref_idc: 3, nal_unit_type: 8 ("Picture parameter set")) Presentation time: 1380253448.432657 4 bytes @1380253448.432657, fDurationInMicroseconds: 0 ((0*1000000)/25.500000) Parsed 3017-byte NAL-unit (nal_ref_idc: 2, nal_unit_type: 1 ("Coded slice of a non-IDR picture")) Presentation time: 1380253448.432657 *****This NAL unit ends the current access unit***** 3017 bytes @1380253448.432657, fDurationInMicroseconds: 39215 ((1*1000000)/25.500000) 后台,已经开始发送视频了,没有相关的日志打印。 接收到请求 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 158 new bytes:GET_PARAMETER rtsp://192.168.1.198:8554/slamtv60.264/ RTSP/1.0 CSeq: 6 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Session: BB58AEA2 请求为 GET_PARAMETER 解析并回应请求 parseRTSPRequestString() succeeded, returning cmdName "GET_PARAMETER", urlPreSuffix "slamtv60.264", urlSuffix "", CSeq "6", Content-Length 0, with 0 bytes following the message. sending response: RTSP/1.0 200 OK CSeq: 6 Date: Fri, Sep 27 2013 03:44:08 GMT Session: BB58AEA2 Content-Length: 10 2013.09.18 后面都是解析 264 文件,类似下面的日志信息。有兴趣的可以对着代码深入研究。 Parsed 3081-byte NAL-unit (nal_ref_idc: 2, nal_unit_type: 1 ("Coded slice of a non-IDR picture")) Presentation time: 1380253448.471872 *****This NAL unit ends the current access unit***** 3081 bytes @1380253448.471872, fDurationInMicroseconds: 39215 ((1*1000000)/25.500000) 收到保活信息并确认 [0x15795c0]saw incoming RTCP packet (from address 192.168.1.98, port 61481) 81c90007 8000353d a05b17ca 00ffffff 0001a452 00000069 7d88700b 000104b6 81ca0004 8000353d 01094a65 72656d79 2d504300 RR RTSP client session (id "BB58AEA2", stream name "slamtv60.264"): Liveness indication validated RTCP subpacket (type 2): 1, 201, 0, 0x8000353d UNSUPPORTED TYPE(0xca) validated RTCP subpacket (type 2): 1, 202, 12, 0x8000353d validated entire RTCP packet 请留意 incomming 的 RTCP 包的地址及端口。RTCP 并不是从 554 发送的。 Liveness indication,保活指示 validated entire RTCP packet,确认整个 RTCP 包 发送保活信息 sending REPORT sending RTCP packet 80c80006 a05b17ca d5ef7d8b 24804103 66a4dee9 000000e6 00041352 81ca0004 a05b17ca 01064e69 755a6169 00000000 schedule(1.528875->1380253452.671793) 停止播放时接收到的请求 RTSPClientConnection[0x154eca0]::handleRequestBytes() read 153 new bytes:TEARDOWN rtsp://192.168.1.198:8554/slamtv60.264/ RTSP/1.0 CSeq: 7 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Session: BB58AEA2 解析并回应请求 parseRTSPRequestString() succeeded, returning cmdName "TEARDOWN", urlPreSuffix "slamtv60.264", urlSuffix "", CSeq "7", Content-Length 0, with 0 bytes following the message. RTCPInstance[0x15795c0]::~RTCPInstance() sending BYE sending RTCP packet 80c80006 a05b17ca d5ef7da8 b7925bb8 66cd7c32 000009c1 002d961c 81cb0001 a05b17ca sending response: RTSP/1.0 200 OK CSeq: 7 Date: Fri, Sep 27 2013 03:44:40 GMT RTCP 发送 BYE 信息。 关掉连接 RTSPClientConnection[0x154eca0]::handleRequestBytes() read -1 new bytes (of 10000); terminating connection! 9. 分析 VLC1.1.9 的日志 上面的 OPTIONS、DISCRIBE SETUP PLAY TEARDOWN 命令的请求和发送,均能在 VLC 的日志里面 找到对应。举个栗子: Sending request: OPTIONS rtsp://192.168.1.198:8554/slamtv60.264 RTSP/1.0 CSeq: 2 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Received 152 new bytes of response data. Received a complete OPTIONS response: RTSP/1.0 200 OK CSeq: 2 Date: Fri, Sep 27 2013 03:44:08 GMT Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARA METER [031da168] live555 demux debug: RTP subsession 'video/H264' Sending request: SETUP rtsp://192.168.1.198:8554/slamtv60.264/track1 RTSP/1.0 CSeq: 4 User-Agent: LibVLC/1.1.9 (LIVE555 Streaming Media v2011.01.06) Transport: RTP/AVP;unicast;client_port=61480-61481 Received 204 new bytes of response data. Received a complete SETUP response: RTSP/1.0 200 OK CSeq: 4 Date: Fri, Sep 27 2013 03:44:08 GMT Transport: RTP/AVP;unicast;destination=192.168.1.98;source=192.168.1.198;client_ port=61480-61481;server_port=6970-6971 Session: BB58AEA2 超时时间 [031da168] live555 demux debug: We have a timeout of 60 seconds 60 秒超时?没有仔细研究。看日志或许是这样。 使用 rtcp 保活 [031da168] live555 demux debug: tk->rtpSource->hasBeenSynchronizedUsingRTCP() 所有 rtcp 相关的操作,没有体现在日志中,所以无法看到发送 rr 包。 10. 关于 RTSP 的保活 RTSP 的保活有两种方式: 发送 RTCP 包,如上所述。详情参考 RTCP RFC3605(http://tools.ietf.org/html/rfc3605) 发送 PLAY 和 GET_PARAMETER 请求 在 RTSP 的 RFC 里面有这样两段话,说的很清楚,请自己翻译。 If a stream is playing, such a PLAY request causes no further action and can be used by the client to test server liveness. GET_PARAMETER with no entity body may be used to test client or server liveness ("ping") 11. 把流程图形化
这样的流程图,是不是很符合逻辑。So Perfect。 12. 写在最后 关于保活,还可以做一个实验,在正常播放时,用任务管理器,把 VLC 砍掉(结束进程树),造成客户 端异常死掉的情况,看看 Live555 是如何处理的?这个当课后作业吧。