基于liveMedia的RTSP/RTP编程

来源:互联网 发布:淘宝品牌手表 编辑:程序博客网 时间:2024/05/15 14:48

 liveMedia项目的源代码包括四个基本的库,各种测试代码以及IVE555 Media Server。

四个基本的库分别是UsageEnvironment&TaskScheduler,groupsock,liveMedia,BasicUsageEnvironment。

UsageEnvironment和TaskScheduler类用于事件的调度,实现异步读取事件的句柄的设置以及错误信息的输出。另外,还有一个HashTable类定义了一个通用的hash表,其它代码要用到这个表。这些都是抽象类,在应用程序中基于这些类实现自己的子类。

groupsock类是对网络接口的封装,用于收发数据包。正如名字本身,Groupsock主要是面向多播数据的收发的,它也同时支持单播数据的收发。Groupsock定义了两个构造函数
    Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
              Port port, u_int8_t ttl);
    Groupsock(UsageEnvironment& env, struct in_addr const& groupAddr,
              struct in_addr const& sourceFilterAddr,
              Port port);
前者是用于SIM(source-independent multicast)组,后者用于SSM(source-specificmulticast)组。groupsock库中的Helper例程提供了读写socket等函数,并且屏蔽了不同的操作系统之间的区别,这是在GroupsockHelper.cpp文件中实现的。

liveMedia库中有一系列类,基类是Medium,这些类针对不同的流媒体类型和编码。

各种测试代码在testProgram目录下,比如openRTSP等,这些代码有助于理解liveMedia的应用。

LIVE555 Media Server是一个纯粹的RTSP服务器。支持多种格式的媒体文件:

      * TS流文件,扩展名ts。
      * PS流文件,扩展名mpg。
      * MPEG-4视频基本流文件,扩展名m4e。
      * MP3文件,扩展名mp3。
      * WAV文件(PCM),扩展名wav。
      * AMR音频文件,扩展名.amr。
      * AAC文件,ADTS格式,扩展名aac。

用live555开发应用程序
基于liveMedia的程序,需要通过继承UsageEnvironment抽象类和TaskScheduler抽象类,定义相应的类来处理事件调度,数据读写以及错误处理。live项目的源代码里有这些类的一个实现,这就是“BasicUsageEnvironment”库。BasicUsageEnvironment主要是针对简单的控制台应用程序,利用select实现事件获取和处理。这个库利用Unix或者Windows的控制台作为输入输出,处于应用程序原形或者调试的目的,可以用这个库用户可以开发传统的运行与控制台的应用。

通过使用自定义的“UsageEnvironment”和“TaskScheduler”抽象类的子类,这些应用程序就可以在特定的环境中运行,不需要做过多的修改。需要指出的是在图形环境(GUI toolkit)下,抽象类 TaskScheduler 的子类在实现doEventLoop()的时候应该与图形环境自己的事件处理框架集成。

先来熟悉在liveMedia库中Source,Sink以及Filter等概念。Sink就是消费数据的对象,比如把接收到的数据存储到文件,这个文件就是一个Sink。Source就是生产数据的对象,比如通过RTP读取数据。数据流经过多个'source'和'sink's,下面是一个示例:

      'source1' -> 'source2' (a filter) -> 'source3' (a filter) -> 'sink'

从其它Source接收数据的source也叫做"filters"。Module是一个sink或者一个filter。

数据接收的终点是Sink类,MediaSink是所有Sink类的基类。MediaSink的定义如下:

class MediaSink: public Medium {
public:
    static Boolean lookupByName(UsageEnvironment& env, char const* sinkName,
                                MediaSink*& resultSink);

    typedef void (afterPlayingFunc)(void* clientData);
    Boolean startPlaying(MediaSource& source,
                         afterPlayingFunc* afterFunc,
                         void* afterClientData);
    virtual void stopPlaying();

    // Test for specific types of sink:
    virtual Boolean isRTPSink() const;

    FramedSource* source() const {return fSource;}

protected:
    MediaSink(UsageEnvironment& env); // abstract base class
    virtual ~MediaSink();

    virtual Boolean sourceIsCompatibleWithUs(MediaSource& source);
        // called by startPlaying()
    virtual Boolean continuePlaying() = 0;
        // called by startPlaying()

    static void onSourceClosure(void* clientData);
        // should be called (on ourselves) by continuePlaying() when it
        // discovers that the source we're playing from has closed.

    FramedSource* fSource;

private:
    // redefined virtual functions:
    virtual Boolean isSink() const;

private:
    // The following fields are used when we're being played:
    afterPlayingFunc* fAfterFunc;
    void* fAfterClientData;
};

Sink类实现对数据的处理是通过实现纯虚函数continuePlaying(),通常情况下continuePlaying调用fSource->getNextFrame来为Source设置数据缓冲区,处理数据的回调函数等,fSource是MediaSink的类型为FramedSource*的类成员;

基于liveMedia的应用程序的控制流程如下:

应用程序是事件驱动的,使用如下方式的循环

      while (1) {
          通过查找读网络句柄的列表和延迟队列(delay queue)来发现需要完成的任务
          完成这个任务
      }

对于每个sink,在进入这个循环之前,应用程序通常调用下面的方法来启动需要做的生成任务:

      someSinkObject->startPlaying();

任何时候,一个Module需要获取数据都通过调用刚好在它之前的那个Module的FramedSource::getNextFrame()方法。这是通过纯虚函数FramedSource:oGetNextFrame()实现的,每一个Source module都有相应的实现。

Each'source' module's implementation of "doGetNextFrame()" works byarranging for an 'after getting' function to be called (from an eventhandler) when new data becomes available for the caller.

注意,任何应用程序都要处理从'sources'到'sinks'的数据流,但是并非每个这样的数据流都与从网络接口收发数据相对应。
比如,一个服务器应用程序发送RTP数据包的时候用到一个或多个"RTPSink" modules。这些"RTPSink"modules以别的方式接收数据,通常是文件 "*Source" modules (e.g., to read data from afile), and, as a side effect, transmit RTP packets.

一个简单的RTSP客户端程序
在另一个文章里,给出了这个简单的客户端的程序的代码,可以通过修改Makefile来裁剪liveMedia,使得这个客户端最小化。此客户端已经正常运行。

首先是OPTION
然后是DESCRIBE
      建立Media Session,调用的函数是 MediaSession::createNew,在文件liveMedia/MediaSession.cpp中实现。
      为这个Media Session建立RTPSource,这是通过调用 MediaSubsession::initiate来实现的的,这个方法在liveMedia/MediaSession.cpp中实现。
在然后是SETUP
最后是PLAY

rtp数据的句柄:MultiFramedRTPSource::networkReadHandler 在liveMedia/MultiFramedRTPSource.cpp中
rtcp数据处理的句柄:RTCPInstance::incomingReportHandler 在liveMedia/RTCP.cpp中

rtp数据处理的句柄的设置:MultiFramedRTPSource:oGetNextFrame 在liveMedia/MultiFramedRTPSource.cpp中, 被FileSink::continuePlaying调用在FileSink.cpp中.

rtcp数据处理的句柄设置fRTCPInstance = RTCPInstance::createNew 在/liveMedia/MediaSession.cpp中调用,
createNew调用了构造函数RTCPInstance::RTCPInstance,这个构造函数有如下调用
TaskScheduler::BackgroundHandlerProc* handler = (TaskScheduler::BackgroundHandlerProc*)&incomingReportHandler;  


*********************************************************************************************************************
通过分析live库提供的例子程序OpenRTSP,可以清晰地了解客户端接收来自网络上媒体数据的过程。注意,RTP协议和RTCP协议接收的数据分别是视音频数据和发送/接收状况的相关信息,其中,RTP协议只负责接收数据,而RTCP协议除了接收服务器的消息之外,还要向服务器反馈。
A.        main函数流程
main(int argc,char *argv[])
{
1.            创建BasicTaskScheduler对象
2.            创建BisicUsageEnvironment对象
3.            分析argv参数,(最简单的用法是:openRTSP rtsp://172.16.24.240/mpeg4video.mp4)以便在下面设置一些相关参数
4.            创建RTSPClient对象
5.            由RTSPClient对象向服务器发送OPTION消息并接受回应
6.            产生SDPDescription字符串(由RTSPClient对象向服务器发送DESCRIBE消息并接受回应,根据回应的信息产生SDPDescription字符串,其中包括视音频数据的协议和解码器类型)
7.            创建MediaSession对象(根据SDPDescription在MediaSession中创建和初始化MediaSubSession子会话对象)
8.           while循环中配置所有子会话对象(为每个子会话创建RTPSource和RTCPInstance对象,并创建两个GroupSock对象,分别对应RTPSource和RTCPInstance对象,把在每个GroupSock对象中创建的socket描述符置入BasicTaskScheduler::fReadSet中,RTPSource对象的创建的依据是SDPDescription,例如对于MPEG4文件来说,视音频RTPSource分别对应MPEG4ESVideoRTPSource和MPEG4GenericRTPSource对象。RTCPInstance对象在构造函数中完成将Socket描述符、处理接收RTCP数据的函数(RTCPInstance::incomingReportHandler)以及RTCPInstance本身三者绑定在一个HandlerDescriptor对象中,并置入BasicTaskScheduler::fReadHandler中。完成绑定后会向服务器发送一条消息。)
9.            由RTSPClient对象向服务器发送SETUP消息并接受回应。
10.       while循环中为每个子会话创建接收器(FileSink对象),在FileSink对象中根据子会话的codec等属性缺省产生记录视音频数据的文件名,视音频文件名分别为:video-MP4V-ES-1和audio-MPEG4-GENERIC-2,无后缀名
11.       while循环中为每个子会话的视音频数据装配相应的接收函数,将每个子会话中的RTPSource中的GroupSock对象中的SOCKET描述符,置入BasicTaskScheduler::fReadSet中,并将描述符、处理接收RTP数据的函数(MultiFramedRTPSource::networkReadHandler)以及RTPSource本身三者绑定在一个HandlerDescriptor对象中,并置入BasicTaskScheduler::fReadHandler中,并将FileSink的缓冲区和包含写入文件操作的一个函数指针配置给RTPSource对象,这个缓冲区将会在networkReadHandler中接收来自网络的视音频数据(分析和去掉RTP包头的工作由RTPSource完成),而这个函数指针在networkReadHandler中被调用以完成将缓冲区中的数据写入文件。
12.        由RTSPClient对象向服务器发送PLAY消息并接受回应。
13.        进入while循环,调用BasicTaskScheduler::SingleStep()函数接受数据,直到服务器发送TREADOWN消息给客户端,客户端接收到该消息后释放资源,程序退出。
}