live555使用整理

   live555使用、学习及遇到的问题的整理。

1、live555模块

live555使用整理_第1张图片

《1》、UsageEnviroment模块

  系统环境的抽象,主要用于消息的输入输出和用户交互功能,包含抽象类UsageEnviroment,TaskScheduler,DelayQueue类,HashTable。
UsageEnvironment代表了整个系统运行的环境,它提供了错误记录和错误报告的功能,无论哪一个类要输出错误,都需要保存UsageEnvironment的指针。
TaskScheduler类: 实现事件的异步处理、事件处理函数的注册等,它通过维护一个异步读取源实现对诸如通信消息到达等事件的处理,通过DelayQueue实现对其他注册事件的延时调度。TaskScheduler由于全局中只有一个,所以保存在UsageEnvironment中,而所有的类又都保存了UsageEnvironment的指针,所以谁想把自己的任务加入调度中,那是很容易的。
TaskScheduler中实现的三类调度任务:

  • socket任务(HandlerSet类管理)

  • 延迟任务(DelayQueue类管理)

  • Event任务;
    第一个及第三个在加入执行队列后会一直存在,而delay任务在执行完一次后会立即丢掉。

    socket handler保存在BasicTaskScheduler0::HandlerSet* fHndlers中。
    event hander保存在数据BasicTaskScheduler0::TaskFunc* fTriggeredEventHandlers[MAX_NUM_EVENT_TRIGGRES]中。
    delay task保存在队列BasicTaskScheduler0::DelayQueue fDelayQueue中。

HashTable类:实现了哈希表。
HandleSet类: 是一种专门用于执行socket操作的任务(函数),被TaskScheduler用来管理所有的socket任务(增删改查)。在整个系统中都可以用到它。
DelayQueue类:延迟队列,他就是在TaskScheduler中用于管理调度任务的东西。他是一个队列,队列中每一项代表了一个要调度的任务(在fToken变量中保存)。该类同时保存了这个任务离执行时间点的剩余时间。

《2》、BasicUagesEnviroment模块

  该模块是UsageEnvoronment的一个控制台的实现。它针对控制台的输入输出和信号响应进行具体实现。

《3》、Groupsock模块

  主要被设计用于支持多播,仅仅支持UDP协议。理论上讲那些需要操作udp socket的类应保存GroupSocket的实例,但事实上并不是,RTPSink,RTPSource,RTCPInstance等,他们都没有保存GroupSocket型的变量。 他们是通过RTPInterface类来进行socket操作的!

《4》、LiveMedia模块

  该模块是Live555最重要的模块,该模块声明了一个抽象类Medium,其它所有类都派生自该类。

liveMedia主要运转的是一个source和sink的循环。
   source:数据来源的抽象,比如通过RTP读取数据;
   sink:数据消费者的抽象。比如吧接收到的数据存储到文件,该文件就是一个sink。
   source和sink通过RTP子会话(MediaSubSession)联系在一起, 数据的流通可能会经过多个Source和Sink。。
源码文件名以sink结尾的就是sink,像MediaSink.cpp,以source结尾的就是source,如MediaSource.cpp。

TCP传输方式,读取数据发送时注意TCPStreamSink::processBuffer()这个函数。

MediaSink是各种类型的Sink的基类,MediaSource是各种类型Source的基类。各种类型的流媒体格式和编码的支持就是通过对这两个类的派生实现的。

该模块的几个主要类:

  • RTSPClient:该类实现RTSP请求的发送和响应的解析,同时根据解析的结果创建对应的RTP会话。
  • RTSPClientSession:每个该类的实例代表每个客户端的rtsp会话,保存了每个客户的socket。
  • MediaSession:用于表示一个RTP会话,一个MediaSession可能包含多个子会话(MediaSubSession),子会话可以是视频子会话、音频子会话等。
  • RTCPInstance: 此类实现RTCP协议的通信
  • RtspServer:服务器实例,一旦创建就永久存在,其余对象都是由他创建或由它引起它们的创建。直接掌管ServerMediaSession和RTSPClient,只管这两个类的实例的创建不负责销毁。
  • ServerMediaSession:对应一个媒体文件,ServerMediaSession在客户端发出对一个新文件的DESCRIBE时创建。建立ServerMediaSession的同时也创建了ServerMediaSubsession,被ServerMediaSession所管理,代表一个文件中的track。
  • SercverMediaSubSession:代表的是一个静态的流(或者说tack),也就是可以从它里面获取一个流的各种信息,但是不能获取流的传输状态。
    如果添加多个ServerMediaSubsession那么ServerMediaSession与流名字所指定的文件是没有关系的,也就是说ServerMediaSession不会操作文件,文件的操作是放在ServerMediaSubSession中的。
     //创建RTSP服务器,开始监听模客户端的连接
    //注意这里的端口号不是默认的554端口,因此访问URL时,需指定该端口号
    RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
    if (rtspServer == NULL)
    {
        *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
        exit(1);
    }
      char const* descriptionString = "Session streamed by \"h264LiveMediaServer\"";
       //流名字,媒体名
       char const* streamName = "h264ESVideoTest";
       //文件名,当客户端输入的流名字为h264ESVideoTest时,实际上打开的是test.264文件。
       //这里需要特别注意一点,当通过IDE运行h264LiveMediaServer时,live555推送的是项目工作目录中的视频或音频。工作目录也就是和*.vcxproj同级的目录,
       //此时视频应该放在这个目录下。当双击h264LiveMediaServer.exe运行时,视频理所当然的和h264LiveMediaServer.exe放在一个目录。
       char const* inputFileName = "test.264";
         //当客户点播时,要输入流名字streamName,告诉RTSP服务器点播的是哪个流。  
          //创建媒体会话,流名字和文件名的对应关系是通过增加子会话建立起来的。媒体会话对会话描述、会话持续时间、流名字等与会话有关的信息进行管理。  
        //第2个参数:媒体名、3:媒体信息、4:媒体描述  
        ServerMediaSession* sms= ServerMediaSession::createNew(*env, streamName, streamName,descriptionString);
       //添加264子会话 这里的文件名才是真正要打开文件的名字
       //H264VideoFileServerMediaSubsession类派生自FileServerMediaSubsession派生自OnDemandServerMediaSubsession  
       //而OnDemandServerMediaSubsession和PassiveMediaSubsession共同派生自ServerMediaSubsession  
      //关于读取文件之类都在这个类中实现的,如果要将点播改为直播就是要新建类继承此类然后添加新的方法  
       sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName, reuseFirstSource));
      //为rtspserver添加session
       rtspServer->addServerMediaSession(sms);
      //答应信息到标准输出
       announceStream(rtspServer, sms, streamName, inputFileName);

2、live555消息循环

由live555MediaServer.cpp中的main函数,可以知道程序执行env->taskScheduler().doEventLoop()后就一直在里面不停的执行,既是进入消息循环。

void BasicTaskScheduler0::doEventLoop(char* watchVariable) 
{
   // Repeatedly loop, handling readble sockets and timed events:
   while (1) 
   {
     if (watchVariable != NULL && *watchVariable != 0) break;
     SingleStep();
   }
}

SingleStep函数中主要是socket的等待及事件的处理,总结为如下4步:

  • 为所有需要的socket执行select;
  • 找到第一个应执行的socke任务(handler)并执行值;
  • 找到第一个应响应的事件,并执行值;
  • 找到一个应执行的延迟任务并执行值;

在每一步中只会执行三个任务队列(socket任务、延迟任务,Event)中的一个。

3、RTSP服务运作

《1》、创建server,建立TCP侦听socket

DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
UserAuthenticationDatabase* authDatabase,
unsigned reclamationTestSeconds)
{
   int ourSocket = setUpOurSocket(env, ourPort); 
   if (ourSocket == -1) return NULL;
   return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

为了侦听客户端的连接,就需要利用任务调度机制,所以需添加一个socket handler。创建详细见RTSPServer类的构造函数。

  RTSPServer::RTSPServer(UsageEnvironment& env,
		       int ourSocket, Port ourPort,
		       UserAuthenticationDatabase* authDatabase,
		       unsigned reclamationTestSeconds)

《2》、客户端连接处理

当收到客户端的连接时,server会使用RTSPClientSession来保存客户信息。RTSPClientSession的创建见如下函数。

RTSPServer::incomingConnectionHandler(int serverSocket);

   该函数内有一个设置传输包buffer大小的函数increaseSendBufferTo,在RTPInterface类中,传1080P码流时需要调大buffer,不然会数据丢失、花屏。
   RTSSPClientSession需要监听客户端的rtsp请求并回应他,需要在DESCRIBE请求中返回所请求的流的信息,需要在SETUP请求中建立RTP会话,需要在TERDOWN请求中关闭RTP会话等,为了实现这些功能,RTSPClientSession要侦听客户端的请求,因此必须把自己的socket handler加入计划。

响应DESCRIBE请求函数(客户端连接请求)

void RTSPServer::RTSPClientConnection
::handleCmd_DESCRIBE(char const* urlPreSuffix, char const* urlSuffix, char const* fullRequestStr) 

进行url解析、账户密码验证,ServerMediaSession查找或创建等。
该函数中的ServerMediaSession的建立规则:
  RTSPClientSession在收到客户端的DESCRIBE请求时,根据地址中的媒体名字,去查找ServerMediaSession的列表。如果已有对应此媒体名字的ServerMediaSession。则利用它获取SDP信息。如果没有找到,则根据媒体名字中的扩展部分名部分,建立对应的ServerMediaSession。

4、RTP数据打包和发送流程

《1》、打包流程

rtp传输开始于函数MediaSink::startPlaying(),流程如下:

live555使用整理_第2张图片

《2》、发送数据函数

数据发送函数:

void MultiFramedRTPSink::sendPacketIfNecessary() 

这里需要注意的是包缓冲区和读取的帧数据公用一块内存,只是用一些额外的变量指明两者的区域。在传输大分辨率的视频如1080P,需要将这块内存扩大,否则不够用,最终会导致数据丢包、花屏。
改大操作:修改MediaSink.cpp中的

unsigned OutPacketBuffer::maxSize = 800000;  //默认60000

5、使用中遇到的几个问题及解决方法

《1》、播放1080P视频花屏原因:

发送数据包缓冲区及帧数据公用内存不够大。修改策略见3.2和4.2。两个都改后解决问题。

《2》、客户端使用TCP连接时会丢包。

原因:live555发送帧头部信息默认是不强制发送的。
修改如下:

Boolean RTPInterface::sendRTPorRTCPPacketOverTCP(u_int8_t* packet, unsigned  packetSize,
                                        int socketNum, unsigned char  streamChannelId)
{
// Send a RTP/RTCP packet over TCP, using the encoding defined in RFC 2326,  section 10.12:
//  
// (If the initial "send()" of '' succeeds, then  we force
// the subsequent "send()" for the  data to succeed, even if we have to  do so with
// a blocking "send()".)
do {
  u_int8_t framingHeader[4];
  framingHeader[0] = '$';
  framingHeader[1] = streamChannelId;
  framingHeader[2] = (u_int8_t) ((packetSize&0xFF00)>>8);
  framingHeader[3] = (u_int8_t) (packetSize&0xFF);
  //TCP时不强制发送会导致发送帧丢包    (https://live-devel.live555.narkive.com/Vh9KbALI/rtp-over-rtsp-tcp-sending-dropped-frames)  
  //if (!sendDataOverTCP(socketNum, framingHeader, 4, False)) break;  
  if (!sendDataOverTCP(socketNum, framingHeader, 4, True)) break;
  if (!sendDataOverTCP(socketNum, packet, packetSize, True)) break;
  return True;
} while (0);

    return False;
}

《3》、循环播放文件-仅仅针对H264文件)

live555 配置循环播放文件

《4》、禁止使用TCP方式进行rtp包发送

  RTSPServer::disableStreamingRTPOverTCP();

6、参考

《1》、https://blog.csdn.net/nkmnkm/column/info/streaming-media
《2》、https://live-devel.live555.narkive.com/Vh9KbALI/rtp-over-rtsp-tcp-sending-dropped-frames
《3》、https://blog.csdn.net/occupy8/article/details/36449709
《4》、https://blog.csdn.net/lbc2100/article/details/80745162

你可能感兴趣的:(流媒体)