林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

时间:2021-10-04 15:51:37

Section I Problem Specification

翻译老师要求:

也可以直接看我另一篇博客 题目:Project 10 – RTSP Windows Client: RFC 2326
RSTP的windows客户端:RFC 2326

A. Overview

Introduction of RTSP

The Real Time Streaming Protocol (RTSP, RFC 2326, http://www.cs.columbia.edu/~hgs/rtsp/) is a network control protocol designed for use in entertainment and communications systems to control streaming media servers. The protocol is used for establishing and controlling media sessions between end points. Clients of media servers issue VCR-like commands, such as play and pause, to facilitate real-time control of playback of media files from the server.


译:A.总览
1.介绍RTSP
实时流传输协议(RTSP,RFC 2326)是一种在娱乐和交流系统中被用于控制流媒体服务器的网络控制协议。该协议被用于在两个端点(译者:最好简单理解为一个播放MP3的播放器的客户端,和一个服务端)之间建立和控制媒体的交流。媒体的客户端能够发出类似于VCR的命令行,例如播放、暂停,因此有利于实时控制来自服务器的媒体文件的播放。



The transmission of streaming data itself is not a task of the RTSP protocol. Most RTSP servers use the Real-time Transport Protocol (RTP, RFC 3550, 3551, http://www.cs.columbia.edu/~hgs/rtp/) for media stream delivery; however some vendors implement proprietary transport protocols.
While similar in some ways to HTTP, RTSP defines control sequences useful in controlling multimedia playback. While HTTP is stateless, RTSP has state; an identifier is used when needed to track concurrent sessions. Like HTTP, RTSP uses TCP to maintain an end-to-end connection and, while most RTSP control messages are sent by the client to the server, some commands travel in the other direction (i.e. from server to client).

译:RTSP并不负责流数据的传输。大多数RTSP服务器使用实时传输协议(RTP, RFC 3550, 3551)传输流数据。然而大多数供应商也会考虑使用自己发明的协议。
某种意义上讲与HTTP协议相似,RTSP定义了有利于控制多媒体播放的控制序列。然后HTTP没有状态,而RTSP是有状态的(译者:RTSP协议在播放音乐时有播放、暂停的状态)。如果需要,会有一个标示符用于跟踪并发的会话。类似于HTTP,RTSP使用TCP维持端到端的联接,并且,当大多数RTSP控制消息被客户端发送至服务端的同时,也有一些命令从服务端发送到客户端。


Presented here are the basic RTSP requests. Some typical HTTP requests, like the OPTIONS request, are also available. The default transport layer port number is 554.
OPTIONS
An OPTIONS request returns the request types the server will accept.
DESCRIBE
A DESCRIBE request includes an RTSP URL (rtsp://...), and the type of reply data that can be handled. The default port for the RTSP protocol is 554 for both UDP and TCP transports. This reply includes the presentation description, typically in Session Description Protocol (SDP, RFC 4566) format. Among other things, the presentation description lists the media streams controlled with the aggregate URL. In the typical case, there is one media stream each for audio and video.
SETUP
A SETUP request specifies how a single media stream must be transported. This must be done before a PLAY request is sent. The request contains the media stream URL and a transport specifier. This specifier typically includes a local port for receiving RTP data (audio or video), and another for RTCP data (meta information). The server reply usually confirms the chosen parameters, and fills in the missing parts, such as the server's chosen ports. Each media stream must be configured using SETUP before an aggregate play request may be sent.
PLAY
A PLAY request will cause one or all media streams to be played. Play requests can be stacked by sending multiple PLAY requests. The URL may be the aggregate URL (to play all media streams), or a single media stream URL (to play only that stream). A range can be specified. If no range is specified, the stream is played from the beginning and plays to the end, or, if the stream is paused, it is resumed at the point it was paused.
PAUSE
A PAUSE request temporarily halts one or all media streams, so it can later be resumed with a PLAY request. The request contains an aggregate or media stream URL. A range parameter on a PAUSE request specifies when to pause. When the range parameter is omitted, the pause occurs immediately and indefinitely.
TEARDOWN
A TEARDOWN request is used to terminate the session. It stops all media streams and frees all session related data on the server.
GET_PARAMETER
The GET_PARAMETER request retrieves the value of a parameter of a presentation or stream specified in the URI. The content of the reply and response is left to the implementation. GET_PARAMETER with no entity body may be used to test client or server liveness ("ping").

译:下面列出来的都是基本的RTSP请求消息,另外一些典型的HTTP的请求消息,比如OPTIONS,也是可以使用的。默认的传输层的端口号是554.
OPTIONS:使用该请求消息,将会返回服务器将要接受的请求类型。

DESCRIBE:该请求消息包括一个RTSP的URL(rtsp://...),并且能够被处理的回应数据的类型。对于UDP和TCP的传输,RTSP默认的端口号是554。回应包括了会话描述协议格式(SDP, RFC 4566)的表示描述,此外,表示描述还列出了被聚合URL控制的媒体流。在典型的情况下,这里各有一个媒体流负责音频和视频。

SETUP:该请求消息详细说明了一个单独的媒体流将如何被传输。该消息必须先于PLAY消息发送。该消息应该包含媒体流的URL和一个传输的标示符。这个标示符应该包含一个本地的端口用于接收RTP的数据(包括音频或者视频),和另一个端口用于接收RTCP数据(元信息)。服务端作出回应时,通常会确认一些参数,并且填满遗失的部分,包括服务端的端口。每一个媒体流都必须确认已经在一个聚合的PLAY请求消息发送之前已经使用了SETUP请求消息

PLAY:该请求消息将使得一个或者多个媒体流被播放了。如果多个PLAY消息被发送了,将会使用栈的结构来处理它。URL也可以是一个聚合的URL(即是:播放所有的媒体流),或者一个单独的媒体流URL(只播放一个流)。播放范围也是可以指定的,如果没有指定范围,那么这个流将会被从头播放结尾,或者如果流被暂停了,将会在它暂停的地方开始播放。

PAUSE:该请求消息能够暂时暂停一个或者多个媒体流,所以这个暂停可以被之后的PLAY消息回复。这个请求包含一个聚合或者单独的媒体流URL。存在于PAUSE里面的参数可以指定何时暂停。如果不指定何时暂停,那么这个暂停将会立刻发生。

TEARDOWN:该请求消息将用于暂停这个会话,它将会暂停所有的媒体流,并且释放所有存在于服务器与会话相关的数据。

GET_PARAMETER:该请求消息可以得到一个表示描述或者以URL指定的流的所有参数的值。我们还可以指定回应的内容和反应。一个没有实体内容的GET_PARAMETER将会用于测试客户端或者服务器的是否活着(类似于ping)。

Introduction of LIVE555

We will use LIVE555 as the streaming server for testing your RTSP Windows client player.LIVE555 is an open source (LGPL) C++ library for multimedia streaming. This code forms a set of C++ libraries for multimedia streaming, using open standard protocols (RTP/RTCP, RTSP, SIP). The libraries are already being used to implement applications such as: 
  • "the LIVE555 Media Server" (a RTSP server application, http://www.live555.com/mediaServer/);
  • "liveCaster"(http://www.live555.com/liveCaster/) and "playRTPMPEG" (http://www.live555.com/multikit/playRTPMPEG.html)(for streaming MP3 audio using RTP/RTCP); and "vobStreamer"(http://www.live555.com/vobStreamer/) (for streaming DVD content using RTP/RTCP/RTSP). 
  • The libraries can also be used to stream, receive, and process MPEG, H.264, H.263+, DV or JPEG video, and several audio codecs. They can easily be extended to support additional (audio and/or video) codecs, and can also be used to build basic RTSP or SIP clients and servers, and have been used to add streaming support to existing media player applications, such as "VLC"(http://www.videolan.org/vlc/) and "MPlayer"(http://www.live555.com/mplayer/).

译:我们将会使用LIVE5555作为流提供服务器用于测试你的基于RTSP协议的客户端播放器。LIVE5555一个开源(LGPL) 的C++库,主要用于多媒体流。代码都是一个C++库的集合,使用了开放的标准协议(RTP/RTCP, RTSP, SIP)服务多媒体流。使用该库实现的应用程序有如下:


  • "the LIVE555 Media Server" (一个基于RTSP的服务端应用程序);
  • "liveCaster"and "playRTPMPEG" (使用 RTP/RTCP协议播放MP3的音频);
  • "vobStreamer"(使用RTP/RTCP/RTSP播放DVD流的内容). 

这个库也可以用于处理流的接收,还有处理如下格式:MPEG, H.264, H.263+, DV, JPEG video以及其它的几种多媒体编码格式。该库可轻易的扩展,支持其他的音频或者视频的格式,当然也可以用于建立基本的RTSP或SIP的客户端或者服务端,并且还能被用于改善已存的媒体播放器,使其可以播放流,比如:VLC,MPlayer。

Specification

Your assignment is to write a RTSP client that should work with LIVE555 Media Server.
1.  Your implementation should follow the specification in the Real Time Streaming Protocol (RTSP, RFC 2326, http://tools.ietf.org/html/rfc2326), so that your version of RTSP Client is able to work together with the LIVE555 MediaServer.
2.  Your implementation should follow the specification in the Real-time Transport Protocol  (RTP, RFC 3550, 3551)  and RTP Payload Format for MPEG1/MPEG2 Video (RFC 2250), so that your version of RTSP Client is able to handle RTP packets correctly. You may achieve this by writing your own code or just using a third party library, such as:
The JRDPLIB is an open-source project  for  packetizing/de-packetizing video/audio data over RTP (http://research.edm.uhasselt.be/~jori/page/index.php?n=CS.Jrtplib).(The JRTPLIB has routines for RTCP, but RTCP is not needed in your implementation because you are not required to encode/decode the audio/video stream data dynamically.), you can also refer to a programming instruction(http://blog.csdn.net/blog51/article/details/2408531) in Chinese.   
A  tiny  RTP library  (http://icourse.cuc.edu.cn/networkprogramming/assignments/RTSP/70565912.blog.51cto.com/Rtsp.rar) introduced by http://70565912.blog.51cto.com.

译:
你的作业主要是写一个基于RTSP的客户端,这个客户端能够和LIVE 媒体服务器通信。
1、你在实现的过程中一定遵守实时流传输协议(RTSP, RFC 2326)的具体说明,如此你开发出来的客户端才可以与LIVE555媒体服务器通信。
2、你在实现的过程中一定遵守实时传输协议(RTP, RFC 3550, 3551)的具体说明,此外,RTP的有效载荷格式必须遵守MPEG1/MPEG2(RFC 2250)的具体说明。如此,你的RTSP客户端才能够正确处理RTP包。你可以完全自己写代码实现,当然也可以使用现存的类库,比如:
JRDPLIB 是一个开源项目,用于封装或者解码传输于RTP的视频或者音频数据。(JRTPLIB 经常使用RTCP,但是RTCP对我们的实现来说并不是必须的,因为我们并不要求动态解码或者编码视频或者音频流。当然,你也可以参考一些编码指导,中文的哟)一个小小的RTP库,其介绍请猛击此处。


3.  The example version RTSP Client (openRTSP, http://www.live555.com/openRTSP/) is a  linux  command-line program that can be used to open, stream, receive, and (optionally) record media streams that are specified by a RTSP URL - i.e., an URL that begins with rtsp://.
Your newly developed application should be a MFC application on Windows platform, butyou are free tochoose any mechanisms for socket I/Omultiplexing (Blocking with Multi-threading, the select( ) system call, asynchronous programming with WSAAsyncSelect or any other models). It is important that you should implement the RTSP client to be independent of the LIVE555  libraries (that means using any source codes of LIVE555 in your implementation is not permitted.).
4.  Your RTSP Client Must accomplish basic and advanced functions:
Basic function: Visit the on-demand file stream from the Live555MediaServer with URL:“rtsp://127.0.0.1:8554/mp3AudioTest”  (Windows  version)  or “rtsp://127.0.0.1:554/test.mp3” (Linux version).  The file “test.mp3”  will be ready for your RTSP request. The basic task of your RTSP Client is that it can record the steam as a local media file.
l  Advanced function: The advanced task of your RTSP Client is that it can play the media file (mp3 only) from the on-demand streaming server. You can do this by taking advantage of the libvlc-library, which is the core component of the VLC media player.  An example can be found at http://www.codeproject.com/Articles/38952/VLCWrapper-A-Little-C-wrapper-Around-libvlc.  Please refer to libvlc (http://www.videolan.org/vlc/libvlc.html) and find the libs on the VideoLan site (ftp://ftp.videolan.org/pub/videolan/vlc/2.0.4/win32/ ) in special ZIP files intended for developers. Note: You should use VLC to play mp3 only, please write your own code to interpret RTSP/RTP and stream down the mp3 file.  
5.  Implementations thatdo not use Object-Oriented Programming will not be accepted. That also means itis not acceptable towrite thisproject in a single function (or even just a couple of functions).


3、有一个RTSP客户端的例子(openRTSP),这是一个基于linux命令行的程序,可以用来打开流、接收流,以RTSP URL的格式来记录媒体流(这样的URL往往是这样的:rtsp://)
你自己开发的一个程序应该是基于windows平台的MFC框架下的应用程序,然而,你可以*选择任何基于I/O多路复用的socket通信机制(这个要找老师确定一下),无论是使用多线程来阻塞,或者使用select()系统调用,使用异步的WSAAsyncSelect 和其他任何模式)。你必须独立于LIVE555库来实现你的RTSP客户端,也就是说不能调用LIVE555库中与通信相关的函数,或者使用LIVE555库中的代码。

4、 你的RTSP的客户端应该完成基本和高级的功能:

基本功能:按需使用URL访问Live555Media服务端的资源文件。在windows上,URL应该类似于:rtsp://127.0.0.1:8554/mp3AudioTest。在Linux上,应该类似于:rtsp://127.0.0.1:554/test.mp3,当然,在服务端肯定存在test.mp3的文件来满足客户端的请求。你完成的RTSP客户端的基本功能应该能后将传输过来的流,保存在本地。

高级功能:按需从服务端传过来的流中播放音频文件(只是MP3格式)。为了此项功能,你可以好好利用:libvlc库,这是VLC播放器的核心组件。一个例子可供您使用:猛击可得。请您尽可能的参考libvlc的说明,并且请到网站VideoLan下载专门供开发人员使用的zip压缩文件。注意:你只可以使用VLC去播放Mp3文件,但是请写出代码,用于完成RTSP/RTP的传输和下载流保存到本地。

5、必须使用面向对象的思维来完成代码的实现。当然,你不能将一个工程写一个单一的函数中(或者一大堆函数)。

Notes:
You  can  download the live555  on  the course website. There are two  versions (Linux  @ http://icourse.cuc.edu.cn/networkprogramming/assignments/RTSP/live-linux.rar  and  Windows@ http://icourse.cuc.edu.cn/networkprogramming/assignments/RTSP/live-wondows.rar  ),  both include source and executable files. You can use the source code to analyze the RTSP protocol, and run the executable file to test your client. 
If you choose the Linux version, you  can  find the server (live555MediaServer)  under “live/mediaServer/”, and if you choose the Windows version, you can find the server (server.exe) under “live/bin/”.And you can use “live/testProgs/testMp3Receiver.cpp” as the demo of the client. But a more sophisticated version of multimedia player, "VLC" can be used for a clear demonstration.
Download latest VLC @  http://www.videolan.org/vlc/download-windows.html  or http://icourse.cuc.edu.cn/networkprogramming/assignments/RTSP/VLC/vlc-2.0.4-win32.zip  and setup VLC.
2)  Open the Open Media Dialog by click menu [Media] -> [Open Network Stream]
3)  In the blank, type “rtsp://computing.cuc.edu.cn/Angel.mp3” for a test run.


注意:
你可能下载Live555在本课程的网页上,这里有两个版本,一个是在linux平台下使用,一个是在windows平台下使用。都含有代码和可执行文件。你可以使用其中的源码来分析RTSP的协议,并且启动可执行文件,用于测试你的客户端。

如果你选择Linux的版本,那么你可以在目录:“live/mediaServer/”下得到服务端可执行文件,即:live555MediaServer。如果你选择的windows的版本,你可以在目录:“live/bin/”下找到可执行文件server.exe。当然,你也利用一个小小的客户端的例子进行分析,代码位于:“live/testProgs/testMp3Receiver.cpp”。但是一个更高级的多媒体播放工具将能向您更清晰的展示功能,类似于一下步骤:

1、下载最新版本的VLC,并且安装。站点1,站点2

2、打开open media对话框,通过点击菜单栏的 [Media] -> [Open Network Stream]。

3、在空白处,输入:rtsp://computing.cuc.edu.cn/Angel.mp3,可测试播放曲目test。如下图所示。
林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

Grading

Your project will be tested to make sure it works properly with the live555 media server. 
Here is a rough breakdown of the grading:
  • Basic function goes well                    50%
  • Advanced function goes well              20%
  • Dealing with impolite requests, unexpected messages         10%
  • Error handling, Style/Code structure, etc.            20%
  • Extra credits: 
  • Ø  Well defined project report                  +5 p
  • Ø  Elegant GUI interface                  +5 p
Note: 20% of your project grade depends on the how "well your code is written". These points include the following:
  • Error handling (check every system call for an error!).
  • Safe code (avoiding buffer overflow, etc).
  • How well we can understand your code. There is no required format for your code; there is no requirement like "you must have one comment for every 2.35 lines of code". Feel free to provide whatever level of commenting you believe is appropriate to make sure that other competent programmers could easily understand and make changes to your code.

译:
你的项目将会被测试,能够很要的与Live555服务器通信,并且工作良好。
下面是一个大致的评分细则:
  • 基本功能运行良好                  50%
  • 高级功能运行良好                   20%
  • 处理了不合理的请求和不可预料的情况    10%
  • 错误处理,代码结构等                                  20%
  • 额外加分: 
  • Ø  优秀的完成了作业报告            +5 p
  • Ø  漂亮的UI界面                    +5 p
注意:20%的分数将依据你的代码写的好不好,这主要是指:

  • 错误处理,检查每一个有问题的系统调用。
  • 代码安全,避免缓冲区溢出等。
  • 代码是否让人容易理解,虽然没有明确要求你代码该如何写,比如没有如此的要求:你必须每2.35行代码就写一句注释。请*发挥地添加注释,只要你相信这样做能够使得别的程序员能够很好地理解你的代码、利用你的代码、进一步修改你的代码。

(全文完)

自己的理解:

RTSP

虽然老师上面已经介绍了很多了,但是我觉得还是应该首先来回答什么是,当然是自己的理解RTSP。
RTSP是下列单位共同开发
  • RealNetworks
  • Netscape Communications
  • 哥伦比亚大学
并且,继承于HTTP协议框架,拥有着HTTP的大部分特性。HTTP传送HTML,而RTSP传送的是多媒体数据(用于控制)。HTTP请求由客户机发出,服务器作出响应;使用RTSP时,客户机和服务器都可以发出请求,即RTSP可以是双向的。


对于我来说,RTSP可以抽出这么几个要素。
  • (Real Time Stream Protocol,实时流协议)
  • 应用层协议(TCP/IP协议体系)
  • 控制声音或影像的多媒体串流协议
  • 音频与视频的受控、点播成为可能
  • 遥控器: RTSP充当多媒体服务器的网络远程控制。
  • 自行选择使用TCP或UDP来传送串流内容
从各层协议来看:
林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326
我们发现其属于应用层。底层协议既可以是TCP,也可以是UDP。与其他的应用层协议如HTTP,RTP/RTCP,RSVP 这样的之间属于协作关系。
关于RTSP的控制传输方式,可以分为三类。
  1. 用于多个请求/响应传输的持久传输连接:在RTSP连接期间,RTSP用户可打开或关闭多个对服务器的可传输连接以发出RTSP请求
  2. 用于单个请求/响应传输的一个连接
  3. 无连接模式:可使用无连接传输协议,如UDP。

那RTSP究竟发送的是什么呢?实际上就是文本,RTSP协议是基于文本的协议,使用ISO 10646字符集,采用了UTF-8编码,每一行的结束由CRLF标识别 。 RTSP协议的消息种类,与HTTP协议类似,包括:
  • 请求消息
  • 响应消息
请求消息是类似于这样的:
Request-Line= Method SP Request-URI SP RTSP-Version CRLF
其中:
  • SP表示空格
  • Request-URI用来指定资源的位置
  • CRLF代表换行
  • RTSP-Version= “RTSP” “/” 一位数字“.” 一位数字,如:RTSP/1.0
  • Method如下解释
RTSP定了很多方法:方法是对URI对应的资源的操作手段,RFC 2326中定义如下方法:
林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

发了请求,就该有响应,响应的格式是类似于这样的:
  1. Response=Status-Line 
  2. *(general-header 
  3. |response-header 
  4. |entity-header ) 
  5. CRLF
  6. [ message-body ]
其中:Status-Line =   RTSP-Version SP Status-Code SP Reason-Phrase CRLF 
如:RTSP/1.0 200 OK

状态行里面的Status-Code 也需要跟多的解释: 参见下图,其中100表示编码,后面的短语解释了具体的原因()Reason-Phrase )。 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

关于每一个方法的解释再次就省略了,这是因为在翻译老师的文档之中已经了解了很多的内容了。 但是作为一个例子,我觉得也有很必要认识一下,以setup为例子: 客户端发送服务端可以是这样的:
  1.   SETUPrtsp://192.168.10.115:554/live.sdp/trackID=1RTSP/1.0
  2.       CSeq: 3
  3.       Transport: RTP/AVP/TCP;unicast;interleaved=0-1
而服务端发回给客户端可以是这样的:
  1.  RTSP/1.0 200 OK     
  2.  CSeq: 3    
  3.  Date: Wed, 10 Nov 2010 11:28:10 GMT     
  4.  Session: 18119504;      
  5. Transport: RTP/AVP/TCP;interleaved=0-1;unicast;mode=play
当然每一个字段都有更多的含义,因为在老师的课上已经讲过了。在此就不细说了。

对于非常有必要的内容,我决定解释一下RTSP完整的URL。 如此便是是一个完整的URL:rtsp URL = ( ”rtsp:” | ”rtspu:” | ”rtsps:” )”//” host [ ”:” port ] [ abs path ]
  • rtsp = 使用可信的底层传输协议,例如TCP
  • rtspu = 使用不可信的底层传输协议,例如UDP
  • rtsps = 使用可信加密传输协议,例如TCP + TLS
  • host = 服务器的IP地址
  • port = 服务器的端口,该字段在忽略的下默认与服务器的554端口进行连接
  • abs path = 所申请实时流的地址
一个简单的示例如下: rtsp://media.example.com:554/twister/audiotrack abs path = twister/audiotrack。 twister表示一个标识(Presentation) , 标识(Presentation)由一个或多个实时流组成。 audiotrack表示标识(Presentation)中其中一个实时流的名称。 从这个名称可以看出,我们要取的是一个音频流。 如果abs path = twister/videotrack,则表示我们要取的是twister的视频流。
有的服务器也支持下面的URL形式:    rtsp://media.example.com:554/twister     
该URL表示取标识(Presentation)的视频流和音频流。

最后我觉得很有必要理解一下RTSP的状态机:不然客户端一会发play,一会发pause,给人混乱的感觉
实际上,RTSP是一个状态协议,一个会话从开始到结束,客户端和服务器都在维护一个状态,状态的定义基于由URL和会话ID唯一确定的对象。
在此过程中会话(session),作为RTSP交互的全过程非常重要的字段。比如,一个电影的观看过程。
会话(session)包括
  • 由客户端建立连续媒体流传输机制(SETUP)
  • 使用播放(PLAY)
  • 录制(RECORD)开始传送流
  • 用停止(TEARDOWN)关闭流。
那么请看客户端的状态机,如下图所示,有以下要点请注意:
  • 客户端的状态转变是以收到请求的成功响应
  • 发一个setup,请求成功,就变成了ready。
  • (2XX)作为转变条件。如果收到3XX响应,状态立刻回到Init。而如果收到失败响应
  • (4XX)则不改变状态。从服务器接受到REDIRECT和接受到3XX是等效的。


林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

服务器状态转移如下图所示,其中不得不注意的要点有:
  • 服务器的状态转变是以接受到请求并且发出响应为转移条件。
  • 比如收到客户端的setup,那么服务端给客户端回一个成功的响应,那么服务端就转移状态
  • 如果服务器处于Playing 或者 Recording状态,在一定的间隔内没有接收到客户端的健康信息(如RTCP报告或者RTSP命令),会回到Init状态并且结束这次RTSP会话。
林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326


RTP与RTCP

实际上本次程序和RTP的关系不是特别大,因为我们只是使用RTSP交互,交互的过程很重要,然后,服务器那边把数据用RTP包传过来。我们这边接住了存下来就可以。 但是对RTP的理解是非常重要的。 RTP即为:实时传输协议RTP(Realtime Transport Protocol),是针对Internet上多媒体数据流的一个传输协议。RTP被定义为在一对一或一对多的传输情况下工作,其目的是提供实时媒体流服务。其典型应用建立在UDP上。RTP本身只保证实时数据的传输,并不能为按顺序传送数据包提供可靠的传送机制,也不提供流量控制或拥塞控制。它依靠RTCP提供这些服务。 下面简要的分析一些其格式: 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326
  • Ver.(2 bits)是目前协议的版本号码,目前版号是 2。
  • P(1 bit)是用于RTP 数据包(packet)退出点的预留空间,视数据包是否需要多余的填塞空间。
  • X(1 bit)是否在使用延伸空间于数据包之中。.
  • CC(4 bits)包含了 CSRC 数目用于修正标头(fixed header).
  • M (1 bit) 是用于应用等级以及其原型(profile)的定义。如果不为零表示目前的数据有特别的程序解译
  • PT(7 bits)是指payload的格式并决定将如何去由应用程序加以解译。
  • SSRC 是同步化来源。
  • Sequence Number - 每发送一个 RTP 数据包,序列号增加1。接收方可以依次检测数据包的丢失并恢复数据包序列。 但并不是说RTP保证所有帧能够按照序号送达。
  • Timestamp - 反映 RTP 数据包中的第一个八位组的采样时间。采样时间必须通过时钟及时提供线性无变化增量获取,以支持同步和抖动计算。 
  • CSRC - 贡献源标识符。识别该数据包中的有效载荷的贡献源。 0~15个,每个CSRC 32位。此列表显示了对RTP数据包中的负载数据有贡献的数据源的SSRC。 
RTCP就是实时传输控制协议RTCP(Realtime Transport Control Protocol),负责管理传输质量。它在当前应用进程之间交换控制信息。在RTP会话期间,各参与者周期性地传送RTCP包,包中含有已发送的数据包的数量、丢失的数据包的数量等统计资料,
因此,服务器可以利用这些信息动态地改变传输速率,甚至改变有效载荷类型。一般RTP和RTCP配合使用,能以有效的反馈和最小的开销使传输效率最佳化,故特别适合传送网上的实时数据。
实际上,RTP协议从上层接收流媒体信息码流,装配成RTP数据包发送给下层,下层协议提供RTP和RTCP的分流:如在UDP中,RTP使用一个偶数号端口,则相应的RTCP使用其后的奇数号端口。RTP数据包没有长度限制,它的最大包长只受下层协议的限制。
RTCP包种类有如下几种种类:
  • SR:Sender report,现行的发送者的传输和接收的统计数据
  • RR:Receiver report,由不是现行的发送者的接收统计数据
  • SDES:一些源描述项,包括CNAME;
  • BYE:指出参与结束;
  • APP:应用程序特定功能

VLC

就是一个开源的播放器,我们调用其代码来播放我们下载的歌曲。这一部分不是我们的编码的重点。我只是单纯的调用了api而已。 根据老师的要求,我们的重点在下载、传输,而不是播放。

Section II Solution Method and Design

关于设计方面实际上也是非常简单的,先考虑线程问题,到底需要多少个线程呢?首先界面是肯定需要一个界面的,后台播放也需要一个线程,还有一个线程是用于接受从服务器传来的数据的。故三个线程。
实际上,类的设计也没有什么特别的。这是因为各个类的关系并不是错综复杂的,从实际的逻辑来考虑,我们无非就是使用首先要用socket联上服务器,然后往发服务器发送数据,接着发送符合RTSP的字节,当然这些字节都有具体的含义,接着再看一个线程来接受服务器的数据,拿到服务器的数据之后再存在本地,存在本地之后再用一个播放机制来播放歌曲。这个播放机制就是VLC类库以及其方法。 所以我们需要3个类来完成这个工作
  • socket联网类
  • 与RTSP相关的类
  • 播放(VLC)的类
其中VLC不是我们自己写的,只是调用类库的api而已。实际的情况是使用了两个类的,当然这两个类是别人封装的。我直接借用了一下而已。

类图

我们可以清晰的看到使用了什么方法和字段,对这些我认为没有必要重复解释。 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

时序图

所有关键的部分都是来自于一个播放button。当输入好了rtsp的时候,点击播放将会产生下述流程。当然这只是基本流程,实际情况复杂的多,比如多线程问题,但是我觉得在此处画出多线程有点多此一举。我现在更想清楚的表达我的程序做了什么,毕竟老师的时间有限。 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

Section III Test Cases and Results Analysis

解释界面

除了能够一眼就能够看懂的区域以外,主要解释一下中间空白的log区域。 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

输入错误的内容

当我们输入错误的不符合要求的RTSP的时候,会有提示信息。 林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

输入正确的URL但不存在的歌曲

这时,我们可以log窗口看到足够多的提示信息,比如
  • rtsp://computing.cuc.edu.cn/Angel321.mp3。
这首angel321.mp3是不存在的内容。但是当输入进去的时候会有什么显示呢。林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326
我们可以清晰的看到,log框显示了很多提示,而且我们可以清晰的看到是发送Describe信息的时候发生了错误,服务器发回了一个stream not found的信息。让我们能够更好的掌握程序的情况。

正确的url

当我们输入正确的URL,我们可以看到以下信息,由于非常快,所以只每一个方法的具体细节没有看到。直接到播放阶段。我们能看到在右上的小框框内看到歌曲的名字,看到已经下载的情况,在log框内看到也可以看到歌曲开始播放了,并且耳机也有声音。林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

我们也可以在项目目录下看到下载的歌曲,可以看出现在已经下载了1.128kb了。
林老师最后一次大作业:Project 10 – RTSP Windows Client: RFC 2326

Section IV Conclusion

1、虽然在刚开始的时候觉得这是一个庞大的工程,一切看起来毫无头绪,但是在稍稍思考和同学讨论过后发现,事情其实并不是那么复杂,因为我们要做的事情还是非常单一,我们无非就是连接到老师的服务器,然后发送一些规定好了的字段,然后再接受数据存储下来而已。
2、对于类的划分需要更深刻的理解,在本次程序,由我划分的关键的类有两个,一个是联网的Socket类,还有一个是RTSP类,虽然感觉上没有什么错,但是我总感觉与我们实际学习面对对象开发过程中的思路有一点不一致和奇怪。在面对对象的开发过程中,最常谈到的是将现实世界的实体抽象成一个类,比如说将人可以划分为一个类,然后中国人可以是人这个类的子类,那么美国人也可以是人这个类的子类,父类的方法子类可以继承,比如“说”这个方法。但是应该是这个程序有点小,所有不太适合这样的开发方式,试着思考,对于每一个RTSP的类来讲,都有一个方法是options,当一但调用这个方法的时候将会发送相应的option字段给服务器,服务器会将字段返回给一个字段,并且服务器和客户端的状态机都有所改变,那么在我的程序是里是将这些字段全部打印出来。这里就需要一个思维的转变,那么就是 “说”这个方法是人类的一个动作,所以它可以成为人类的一个方法。但是对于RTSP来讲,option不能说是其的一个动作,那么这就让我理解的非常辛苦了。简单的讲,我有这样一个概念,当使用面对对象的思维编程的时候,我认为一个类的方法和属性必须是从客观世界抽象而得来的,绝不是生搬硬套。我就认为option属于RTSP就是有点生搬硬套,但是如果有个方法“说”属于人类,那么我就觉得非常合理。对于这一方面,我认为好像是我编程的实践机会太少来吧。因为从客观世界提炼出面对对象的编程,这应该是最简单的一件事,然而,更多更多的情况,不能从客观世界提炼出。那么就靠程序员的更多的积累。
3、本次程序的完成时间过长,这是因为中途掺杂了很多事,开导师会或者是准备考试,那么程序就是断断续续完成的,所以很多感悟如果没有及时记录下载就转瞬即逝了。这样貌似让我忘了自己的承诺,那就是维护这个博客的事,原来以为一篇博客必须有开始有结尾,内容充实,思想丰富。但是现在有点觉得不是这样的,我应该是有一点感悟就应该立刻记录下来。

Section V References

MSDN
VLC
RSTP
深入理解计算机系统(原书第2版) 作者: (美)Randal E.Bryant / David O'Hallaron 译者: 龚奕利 / 雷迎春 出版年: 2010年

和同学讨论过各种各样的问题,他们是:王亚彤、李建方、李晨、沙飞等等,感谢他们。

Section VI Appendix

点击播放按钮

void CmediaPlayDlg::OnBnClickedOk()
{

if (RTSP_URL!="")//如果不为空,说明不是第一次,那么肯定是因为暂停键之后才想继续播放。
{
vlcPlayer_.Play();
CButton_play.EnableWindow(FALSE);
CButton_pause.EnableWindow(TRUE);
CButton_stop.EnableWindow(TRUE);
AddStringToLog("继续播放.....\r\n");
}else{
Cedit_Url.GetWindowText(RTSP_URL);
int flag=RTSP_URL.Find("rtsp://");
if(RTSP_URL==""||flag==-1)//判断是否为空
{
MessageBox("请输入正确的RTSP URL\r\n比如:\r\nrtsp://computing.cuc.edu.cn/Angel.mp3");
}else{
RTSP::RTSP_URL=RTSP_URL;
int n1=RTSP_URL.Find("//");
int n2=RTSP_URL.Find("/",n1+2);
CString hostName;
hostName = RTSP_URL.Mid(n1+2,n2-n1-2);//从输入中找到正确主机名
rtsp.MusicName=RTSP_URL.Mid(n2+1,RTSP_URL.GetLength());
AddStringToLog("hostName:"+hostName+"\r\n\r\n");
Socket socket;
hostIp=socket.getIpByhostName(hostName);
socket.InitSocket();
socket.BuildSocket(hostIp,"554");//554是默认的端口
socket.ConnectSocket();

AddStringToLog("Send Command:option.....\r\nGOT:\r\n");
string strTemp = rtsp.Options();
AddStringToLog(strTemp.c_str());

AddStringToLog("Send Command:Describe....\r\nGOT:\r\n");
strTemp = rtsp.Describe();
AddStringToLog(strTemp.c_str());
CString strTemp1;
strTemp1.Format("%s", strTemp.c_str());
int temp=strTemp1.Find("404");
if (temp!=-1)//如果等于-1表示没有找到,如果不等于-1表示找到,表示出错了
{
AddStringToLog("发生错误,请看最近一条信息。");
return;
}
AddStringToLog("\r\nSend Command:Setup....\r\nGOT:\r\n");
strTemp = rtsp.Setup();
AddStringToLog(strTemp.c_str());
//事先开启下载数据的线程
AfxBeginThread(getDataThread,(LPVOID)this,0,0,0,NULL);

AddStringToLog("\r\nSend Command:Play....\r\nGOT:\r\n");
strTemp = rtsp.Play();
AddStringToLog(strTemp.c_str());

//拿到数据之后开始播放
AfxBeginThread(playThread,(LPVOID)this,0,0,0,NULL);

//变一下button的颜色
CButton_play.EnableWindow(FALSE);
CButton_pause.EnableWindow(TRUE);
CButton_stop.EnableWindow(TRUE);

}
}

}


播放线程

UINT CmediaPlayDlg::playThread(LPVOID lpParam)   
{

CmediaPlayDlg *Cdialog=(CmediaPlayDlg*)lpParam;
Cdialog->AddStringToLog("为了播放效果、缓冲3秒.....\r\n");
Sleep(3000);
string outputFilePathAndName="..\\"+rtsp.MusicName;
const char *ch = outputFilePathAndName.c_str();
CString file=ch;
vlcPlayer_.OpenMedia((LPCTSTR)file);
vlcPlayer_.Play();
Cdialog->AddStringToLog("开始播放.....\r\n");

return 1;
}


接受数据线程

UINT CmediaPlayDlg::getDataThread(LPVOID lpParam)   
{
//拿到播放歌曲的名称
string outputFilePathAndName="..\\"+rtsp.MusicName;
const char *ch = outputFilePathAndName.c_str();
//每次下载的时候都把之前下载的删除掉,这是为了能够正常测试,但是实际上工程上不会怎么做。
if (PathFileExists(ch))
{
CFile TempFile;
TempFile.Remove(ch);
}

CmediaPlayDlg *Cdialog=(CmediaPlayDlg*)lpParam;
Cdialog->CListBox_log.AddString("开始下载......");
Cdialog->CListBox_musicName.AddString(rtsp.MusicName.c_str());
RTPSession rtpSession;
RTPUDPv4TransmissionParams transmissionParams;
RTPSessionParams rtpSessionParams;

//设置时间戳单元,根据传输的负载类型设置相应的时间戳单元
rtpSessionParams.SetOwnTimestampUnit(1.0/10.0);
rtpSessionParams.SetAcceptOwnPackets(true);
transmissionParams.SetPortbase(1554);
rtpSession.Create(rtpSessionParams,&transmissionParams);

//这一段是打开文件,往里面写东西,所以最好是只打开一次,不要反复的关闭文件

ofstream outputFile(ch,ios::out|ios::binary|ios::app);
if(!outputFile)
{
Cdialog->AddStringToLog("#ERROR:output File Fail...");
outputFile.clear();
return 0;
}
ifstream in(ch, ios::in|ios::binary);

//为了使服务端那边知道我们这边正常的接收了数据。
//我们将每次接收到一个数据包就记录一下,然后定期向服务器发一个option命令。
//告诉服务器客户端还活着
int packetCount=0;
do{
//开始接收数据包

rtpSession.BeginDataAccess();
if (rtpSession.GotoFirstSourceWithData())
{
do
{
RTPPacket *pack;

while ((pack = rtpSession.GetNextPacket()) != NULL)
{

packetCount++;
if(packetCount%2000==0){
rtsp.GetParameter(); //告诉服务器、客户端还活着
}

char *buf=(char *)pack->GetPayloadData(); //获取数据指针
outputFile.write(buf+4,pack->GetPayloadLength()-4);

rtpSession.DeletePacket(pack);
//下一段是为了读取下载的mp3文件,为了能够实时的显示在界面上,表示下载了多少
long l,m;
l = in.tellg();
in.seekg (0, ios::end);
m = in.tellg();
char fileSize[20];
char *pfileSize;
pfileSize=ltoa((m-1)/1024,fileSize,10);
CString str1;
Cdialog->CListBox_log.ResetContent();
str1.Format("已下载:%s KB",pfileSize);
Cdialog->CListBox_log.AddString(str1);
}
} while (rtpSession.GotoNextSourceWithData());
}
rtpSession.EndDataAccess();
rtpSession.Poll();

}while(1);
Cdialog->CListBox_log.ResetContent();
Cdialog->CListBox_log.AddString("下载完成......");
outputFile.close();
in.close();

rtpSession.BYEDestroy(RTPTime(10,0),0,0);
return 1;

}


RTSP类

#pragma once

#include "mediaPlay.h"
#include <string>
#include "Socket.h"
using namespace std;
#define BUF_SIZE 20000
class RTSP
{
public:
RTSP(void);
~RTSP(void);
static string RTSP_URL;
string sessionNo;
string content_base;
string MusicName;
string Options();
void GetParameter();
string Describe();
string Setup();
string Play();
string Pause();
string TearDown();

};

#include "StdAfx.h"
#include "RTSP.h"

RTSP::RTSP(void)
{
}

RTSP::~RTSP(void)
{
}
string RTSP::Options()
{
Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:Options fail...");
stringToSend.clear();
stringToSend="OPTIONS "+RTSP_URL+" RTSP/1.0\r\n"+"CSeq: 1\r\n\r\n";


if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}
stringTemp=dataFromServer;
return stringTemp;
}




string RTSP::Describe()
{

Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:Describe fail...");
stringToSend.clear();
stringToSend="DESCRIBE "+RTSP_URL+" RTSP/1.0\r\n"+"CSeq: 2\r\n"+"Accept: application/sdp\r\n\r\n";


if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}

stringTemp=dataFromServer;
CString strTemp=dataFromServer;
int n1 = strTemp.Find("Base:");
int n2 = strTemp.Find("Content-Type",n1+5);
content_base = strTemp.Mid(n1+5,n2-n1-7);
return stringTemp;

}


string RTSP::Setup()
{
Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:Setup fail...");
stringToSend.clear();
stringToSend="SETUP "+content_base+"track1"+" RTSP/1.0\r\n"+"CSeq: 3\r\n"+"Transport: RTP/AVP;unicast;client_port=1554-1555\r\n\r\n";


if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}
CString strTemp=dataFromServer;
int n1 = strTemp.Find("Session:");
int n2 = strTemp.Find("\r",n1+8);
sessionNo = strTemp.Mid(n1+8,n2-n1-8);

stringTemp=dataFromServer;
return stringTemp;

}




string RTSP::Play()
{
Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:Setup fail...");
stringToSend.clear();
stringToSend="PLAY "+content_base+" RTSP/1.0\r\n"+"CSeq: 4\r\n"+"Session: "+sessionNo+"\r\nRange: npt=0.000-\r\n\r\n";


if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}

stringTemp=dataFromServer;
return stringTemp;

}


void RTSP::GetParameter()
{
Socket socket;
string stringToSend;
stringToSend.clear();
stringToSend="GET_PARAMETER "+content_base+" RTSP/1.0\r\n"+"CSeq: 5\r\n"+"Session: "+sessionNo+"\r\n\r\n";
socket.Send(stringToSend.c_str(),stringToSend.length());
}



string RTSP::Pause()
{
Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:Pause fail...");
stringToSend.clear();
stringToSend="PAUSE "+content_base+" RTSP/1.0\r\n"+"CSeq: 6\r\n"+"Session: "+sessionNo+"\r\n\r\n";


if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}

stringTemp=dataFromServer;
return stringTemp;
}




string RTSP::TearDown()
{

Socket socket;
char dataFromServer[BUF_SIZE];
string stringToSend;
string stringTemp("*ERROR:send Command:TearDown fail...");
stringToSend.clear();
stringToSend="TEARDOWN "+content_base+" RTSP/1.0\r\n"+"CSeq: 7\r\n"+"Session: "+sessionNo+"\r\n\r\n";

if(socket.Send(stringToSend.c_str(),stringToSend.length())==false)
{
return stringTemp;
}
if(socket.Recv(dataFromServer,BUF_SIZE)==false)
{
return stringTemp;
}

stringTemp=dataFromServer;
return stringTemp;

}


socket类

#pragma once

#include <winsock2.h>
#include <string>
#pragma comment(lib, "Ws2_32.lib")

class Socket
{
public:
Socket(void);
~Socket(void);

bool InitSocket();
bool BuildSocket(u_long ip,CString port);
bool ConnectSocket();
u_long getIpByhostName(CString hostName);

bool Send(const char *c,UINT count);
int Recv(char *c,UINT count);
bool Close();
static Socket SocketToConnect;
static SOCKET socketNo;
SOCKADDR_IN socketAddr;

private:

};


#include "StdAfx.h"
#include "Socket.h"
#include <iostream>
#include <string>
#include <ws2tcpip.h>
#include "mediaPlay.h"
#include "mediaPlayDlg.h"
using namespace std;
Socket::Socket(void)
{
}

Socket::~Socket(void)
{
}

SOCKET Socket::socketNo;

u_long Socket::getIpByhostName(CString hostName)
{

WORD wVersionRequested;//获取服务器Ip地址
WSADATA wsaData;
PHOSTENT hostinfo;
wVersionRequested=MAKEWORD(2,2);
WSAStartup(wVersionRequested,&wsaData);
hostinfo=gethostbyname(hostName);
return inet_addr(inet_ntoa(*(struct in_addr*)*hostinfo->h_addr_list));

}
bool Socket::InitSocket()
{
WSADATA wsaData;
int iResult;
if((iResult=WSAStartup(MAKEWORD(2,2),&wsaData))!=NO_ERROR)
{
return false;
}
socketNo=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

if(socketNo==INVALID_SOCKET)
{
return false;
}
return 1;

}
bool Socket::BuildSocket(u_long ip,CString port)
{
socketAddr.sin_family=AF_INET;
socketAddr.sin_port=htons((u_short)atoi(port));
socketAddr.sin_addr.s_addr=ip;

return 1;
}


bool Socket::ConnectSocket()
{
int iResult=0;

iResult=connect(socketNo, (SOCKADDR*)&socketAddr, sizeof(socketAddr));

if (iResult==SOCKET_ERROR)
{
return false;
}
return true;

}


bool Socket::Close()
{
closesocket(socketNo);
return true;
}



bool Socket::Send(const char *c,UINT length)
{

if(send(socketNo,c,length,0)==SOCKET_ERROR)
{
return false;
}

return true;
}



int Socket::Recv(char *c,UINT length)
{
memset(c,'\0',length);
return recv(socketNo,c,length,0);

}