基于Live555框架实现一个实时流媒体服务

本文介绍基于实时流媒体的解决方案,先阐述常用流媒体协议RTSP、RTP和RTCP,接着推荐使用C++开发的Live555框架处理流媒体服务,详细介绍其基类。还指导了Live555源码下载、嵌入式应用交叉编译,以及编写RTSP服务程序,最后说明定制实时ServerMediaSubsession的思路和流程。

在这里插入图片描述

前述:

大家平时在进行camera驱动开发、KVM开发、或AI视觉效果显示时,实时显示图像效果是一个常见需求。本文将介绍一种基于实时流媒体的解决方案,供各位开发者参考。
在这里插入图片描述

1.常用流媒体协议

RTSP(Real Time Streaming Protocol),即实时流传输协议,是TCP/IP协议体系中的一个应用层协议。由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC2326标准。该协议主要用于通过IP网络高效传送多媒体数据,一对多应用程序的理想选择。RTSP在体系结构上位于RTP和RTCP之上,可使用TCP或UDP完成数据传输。

RTP(Real-time Transport Protocol),1996年由IETF多媒体传输工作小组在RFC 1889中定义。RTP详细说明了音频和视频数据在互联网上传递的标准数据包格式,并基于UDP协议构建。

RTCP(Real-time Transport Control Protocol),是RTP的姐妹协议,由RFC 3550定义(替代旧的RFC 1889)。RTCP主要负责在RTP传输过程中提供传输信息反馈。RTP使用偶数UDP端口进行数据传输,而RTCP则使用RTP的下一个端口(奇数端口)进行控制信息传输。

RTSP协议:负责服务器与客户端之间的请求与响应
RTP协议:负责传输媒体数据
RTCP协议:在RTP传输过程中提供传输信息
在这里插入图片描述
从上述架构中可以看出,RTSP、RTP和RTCP是协同工作的。RTSP负责传输控制命令,而RTP和RTCP则负责数据传输和传输控制。这种分离确保了数据传输的高效和稳定。

1.1 RTSP服务建立链接

要使用RTSP协议传输流媒体数据,需要有专门的媒体播放器和媒体服务器,即支持RTSP协议的客户端和服务器。实时视频流的传输流程通常如下:
在这里插入图片描述
流媒体的传输流程是一个按步骤进行的过程,涉及客户端与流服务器之间的多次交互。以下是这个过程的简要描述:

  1. 建立阶段:客户端连接到流服务器并发送一个RTSP描述命令(DESCRIBE)。
  2. 服务器响应:流服务器响应这个命令,提供一个SDP(Session Description Protocol)描述,其中包含了流数量、媒体类型等关键信息。
  3. 客户端分析与设置:客户端分析SDP描述,并针对会话中的每一个流发送一个RTSP建立命令(SETUP)。这一步骤中,客户端将告诉服务器用于发送媒体数据的端口号,也就是建立RTP的端口号。
  4. 开始播放:客户端发送一个播放命令(PLAY),服务器随即开始通过UDP传送媒体流(即RTP包)到客户端。在播放过程中,客户端还可以向服务器发送命令来控制播放,如快进、快退和暂停。
  5. 结束会话:当媒体播放完成或用户决定停止时,客户端发送一个终止命令(TEARDOWN),以结束流媒体会话。

1.2 RTP协议介绍

为了帮助大家更好地理解后续的代码,这里简要介绍一下RTP(Real-time Transport Protocol)协议的关键特点:
在这里插入图片描述
扩展(X):1比特。设为1时,表示在RTP报头后有一个扩展报头。
参与源数(CSRC计数):4比特。表示紧接在固定头后的CSRC标识符数量。
标记(M):1比特。由应用定义其意义,如在视频流中表示帧结束,在音频中表示谈话开始。
有效载荷类型:7比特。指示承载数据的类型(如H264、H265),用于确定解码器。
序列号:16比特。每发送一个RTP数据包,序列号加一,用于检测包损和重建包序列。
时间戳:32比特。反映第一个八进制数的采样时刻,用于同步与抖动计算。
SSRC:32比特。标识同步源,用于防止同一连接中出现相同标识。
CSRC列表:0到15项,每项32比特。表示对载荷起作用的源,由CC段给出。
这些是RTP协议的核心要素,它们共同确保了流媒体数据的有效传输。

RTCP(Real-Time Control Protocol)在流媒体传输中起着辅助作用,但它不是本文的重点,有兴趣的读者可以自行查阅更多资料。

2.Live555框架介绍

在处理流媒体服务的工作中,自行编写代码不仅耗时而且容易产生错误。因此,选择一个合适的开源媒体软件框架至关重要。常见的框架有ffmpeg、GStreamer和Live555等。
开发一个自定义实时流媒体,我特别推荐使用Live555框架,出于以下几个原因:
基于C++开发:这意味着代码不仅高效,而且具有良好的结构和可读性。
代码简洁、高效:在开发流媒体服务时,这两个特点极为重要,可以减少bug的产生。
通俗易懂:使得即使是初学者也能快速上手。
支持深度定制:为需要特定功能的开发者提供了极大的灵活性。

2.2 Live555框架介绍

Live555框架的强大功能和灵活性来源于其精心设计的几个基类。这些基类为流媒体的不同方面提供了支持。下面是这些基类的简要介绍:

首次接触live555的读者现在初步了解基本概念就行,下面实战环节帮助大家加深理解

在这里插入图片描述
UsageEnvironment:这是一个抽象基类,用于处理事件循环和错误报告。它定义了流媒体基本操作的环境,如日志记录、事件处理等。它代表了整个系统运行的环境,提供错误记录、报告和日志输出功能。任何需要输出错误的类都需要保存对UsageEnvironment的引用。
BasicUsageEnvironment:这个类继承自UsageEnvironment,并实现了其中的抽象方法。它还包括对TaskScheduler和HashTable等的实现。
liveMedia:这是一个关键库,包含了实现RTSP Server的类和针对不同流媒体类型(如TS流、PS流等)的编码类。其基类是Medium,具有清晰的层次结构。在这个库中,一些重要的类包括RTSPServer、ServerMediaSession、RTPSink、RTPInterface、FramedSource等。
在这里插入图片描述
groupsock:这个库专注于网络层,管理组播和单播套接字的创建和操作。它为数据包的发送和接收提供底层支持。groupsock库包括了GroupEId、Groupsock、GroupsockHelper、NetAddress、NetInterface等类。

3.live555定制实战

本节将指导您如何下载、安装以及定制Live555流媒体框架,特别是在嵌入式应用中的交叉编译。

3.1.live555源码下载

首先,您需要下载并安装 Live555。Live555 是一个开源的流媒体框架,可以用来设置 RTSP 服务器。您可以从(http://www.live555.com/liveMedia/) 下载源代码。

3.2 嵌入式应用交叉编译

本例中,我将使用一个aarch64的SOC来演示交叉编译的过程。进行交叉编译时,需要注意以下几个关键步骤:
修改配置文件:首先,您需要修改config.armlinux这个配置文件,这是为了确保编译过程符合您的嵌入式系统的要求。
适配C++库版本:如果您的嵌入式系统的C++库版本低于C++20,您需要在config.armlinux文件的COMPILE_OPTS项中追加-DNO_STD_LIB。这一步是必要的,以避免因版本不兼容导致的语法报错。
处理openssl库依赖:如果您的嵌入式系统中没有安装openssl库,您需要在config.armlinux文件的COMPILE_OPTS项中追加-DNO_OPENSSL=1。这样做可以防止因缺少openssl库而导致的编译错误。
通过遵循这些步骤,您可以顺利地在嵌入式系统上交叉编译Live555。
在这里插入图片描述

#!/bin/bash

LIVE555_DIR=`pwd`

cd $LIVE555_DIR

INSTALL_DIR=$LIVE555_DIR/output
mkdir -p $INSTALL_DIR

#编译成静态库
export LDFLAGS="-static"

#声明交叉编译器的路径
#export PATH=/opt/arm-gcc/bin/:$PATH

./genMakefiles armlinux
make -j$(nproc) CROSS_COMPILE=aarch64-linux-gnu-

make install PREFIX=$INSTALL_DIR CROSS_COMPILE=aarch64-linux-gnu-

执行上边脚本,编译完成之后会出现如下live555的交叉编译产出文件:
在这里插入图片描述

3.3.编写一个RTSP服务程序

大家可以参考testProgs/testOnDemandRTSPServer.cpp示例代码:

#include "liveMedia.hh"
#include "BasicUsageEnvironment.hh"
#include "InputFile.hh"
#include <GroupsockHelper.hh> // for "weHaveAnIPv*Address()"

static void announceURL(RTSPServer* rtspServer, ServerMediaSession* sms) {
  if (rtspServer == NULL || sms == NULL) return; // sanity check
  UsageEnvironment& env = rtspServer->envir();
  env << "Play this stream using the URL ";
  if (weHaveAnIPv4Address(env)) {
    char* url = rtspServer->ipv4rtspURL(sms);
    env << "\"" << url << "\"";
    delete[] url;
    if (weHaveAnIPv6Address(env)) env << " or ";
  }
  if (weHaveAnIPv6Address(env)) {
    char* url = rtspServer->ipv6rtspURL(sms);
    env << "\"" << url << "\"";
    delete[] url;
  }
  env << "\n";
}

static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
			   char const* streamName, char const* inputFileName) {
  UsageEnvironment& env = rtspServer->envir();

  env << "\n\"" << streamName << "\" stream, from the file \""
      << inputFileName << "\"\n";
  announceURL(rtspServer, sms);
}

int main(int argc, char** argv) {
  // Begin by setting up our usage environment:
  //照着写就行,这个时所有live555的基石,必须创建
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
  //设置RTP数据的最大传输大小,根据你需要传输的H265实际的码率设置,如果设小了,程序会报错并丢包
  OutPacketBuffer::maxSize = 1000000;
  //创建一个rtsp的服务,这个服务实现RSTP相关协议
  RTSPServer* rtspServer = RTSPServer::createNew(*env);
  if (rtspServer == NULL) {
    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
    exit(1);
  }
  
  char const* descriptionString
    = "Session streamed by \"LiveRTSPServer\"";

  // A H.265 video elementary stream:
  {
    char const* streamName = "video1";
    char const* inputFileName = "surfing.265";
    Boolean reuseFirstSource = true;
    //创建一个会话,它会添加给rtsp服务,大概的意思就是大概的意思就是告诉rtsp服务有一个流可以它管理了
    ServerMediaSession* sms
      = ServerMediaSession::createNew(*env, streamName, streamName,
				      descriptionString);
	//当然管理之前,你需要先注册一个实例,实现里边所有的管理function,将来给rtsp服务调度。这里先实现一个live555写好的一个H265VideoFileServerMediaSubsession实例,这个实例的流程是从一个h265文件中读取数据作为RTP流的来源。等下我们主要就是要定制这个类,来实现从我们想要的位置获取数据。
    sms->addSubsession(H265VideoFileServerMediaSubsession
		       ::createNew(*env, inputFileName, reuseFirstSource));
    rtspServer->addServerMediaSession(sms);
	//将ServerMediaSession添加到rstp服务
    announceStream(rtspServer, sms, streamName, inputFileName);
  }
	
  // A H.265 video elementary stream:
  //我们也可以添加多个类似的会话给rtsp服务管理
  {
    char const* streamName = "video2";
    char const* inputFileName = "surfing.265";
    Boolean reuseFirstSource = true;
    ServerMediaSession* sms
      = ServerMediaSession::createNew(*env, streamName, streamName,
				      descriptionString);
    sms->addSubsession(H265VideoFileServerMediaSubsession
		       ::createNew(*env, inputFileName, reuseFirstSource));
    rtspServer->addServerMediaSession(sms);

    announceStream(rtspServer, sms, streamName, inputFileName);
  }
  //开始运行服务
  env->taskScheduler().doEventLoop(); // does not return

  return 0; // only to prevent compiler warning
}

4.定制自己的实时ServerMediaSubsession

在实现实时流媒体服务时,关键的一步是确定如何获取实时码流。以下是几种常见的方法:

  1. 通过Linux设备文件:直接从硬件驱动获取码流。
  2. 通过共享内存或Linux的IPC:从编码进程获取码流。
  3. 使用SOA编程架构:类似于ROS2的编程模式,在同一系统中订阅码流。
    为了演示,我将采用第三种方法。但为避免引入ROS2的复杂性,我将使用本地lo网络的TCP socket来代替ROS2通信。
    在这里插入图片描述

4.1大致实现思路

  1. VLC流媒体播放器请求:VLC流媒体播放器向流媒体服务申请网络直播视频流。
  2. 建立TCP连接:流媒体服务收到请求后,通过本地lo网络的TCP socket与编码进程建立连接,并通过socket读取实时码流。
  3. 编码进程发送码流:编码进程成功创建TCP连接后,开始向客户端源源不断地发送实时码流,直到连接断开。

我们首先分析一下Live555中H265VideoFileServerMediaSubsession的实现。这将帮助我们理解如何在Live555框架下定制自己的实时流媒体服务。

class FileServerMediaSubsession: public OnDemandServerMediaSubsession {
protected: // we're a virtual base class
  FileServerMediaSubsession(UsageEnvironment& env, char const* fileName,
			    Boolean reuseFirstSource);
  virtual ~FileServerMediaSubsession();

protected:
  char const* fFileName;
  u_int64_t fFileSize; // if known
};


class H265VideoFileServerMediaSubsession: public FileServerMediaSubsession {
public:
  static H265VideoFileServerMediaSubsession*
  createNew(UsageEnvironment& env, char const* fileName, Boolean reuseFirstSource);

  // Used to implement "getAuxSDPLine()":
  void checkForAuxSDPLine1();
  void afterPlayingDummy1();

protected:
  H265VideoFileServerMediaSubsession(UsageEnvironment& env,
				      char const* fileName, Boolean reuseFirstSource);
      // called only by createNew();
  virtual ~H265VideoFileServerMediaSubsession();

  void setDoneFlag() { fDoneFlag = ~0; }

protected: // redefined virtual functions
/*当RTSPServer收到对某个客户端的DESCRIBE请求时,它会找到对应的ServerMediaSession,调用
ServerMediaSession::generateSDPDescription()。generateSDPDescription()中会遍历调用
ServerMediaSession中所有的调用ServerMediaSubsession,通过subsession->sdpLines()取得每个
Subsession的sdp,合并成一个完整的SDP返回。这个
H265VideoFileServerMediaSubsession::getAuxSDPLine就是获取当前流的SDPLine;*/
  virtual char const* getAuxSDPLine(RTPSink* rtpSink,
				    FramedSource* inputSource);
 	/*创建一个码流来源,用class FramedSource来描述,用来获取码流*/
  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
					      unsigned& estBitrate);
	/*创建一个RTPSink,用来管理RTP打包推流*/
  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
                                    unsigned char rtpPayloadTypeIfDynamic,
				    FramedSource* inputSource);

private:
  char* fAuxSDPLine;
  char fDoneFlag; // used when setting up "fAuxSDPLine"
  RTPSink* fDummyRTPSink; // ditto
};

H265VideoFileServerMediaSubsession是Live555框架中处理H265视频流的关键类。它的实现涉及继承和重写特定的函数,以下是其主要特点和继承关系的解析:
继承关系:

  • H265VideoFileServerMediaSubsession继承自FileServerMediaSubsession类。
  • FileServerMediaSubsession进一步继承自OnDemandServerMediaSubsession
  • OnDemandServerMediaSubsession直接继承自ServerMediaSubsession,后者是我们需要创建的目标类。

关键函数实现
H265VideoFileServerMediaSubsession实现了三个重要的虚函数:

  • getAuxSDPLine:获取SDP描述的辅助行。
  • createNewStreamSource:创建新的流源。
  • createNewRTPSink:创建新的RTP接收器。
    这些函数的实现对于设置和管理视频流至关重要。
class OnDemandServerMediaSubsession: public ServerMediaSubsession {
protected: // we're a virtual base class
  OnDemandServerMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource,
				portNumBits initialPortNum = 6970,
				Boolean multiplexRTCPWithRTP = False);
  virtual ~OnDemandServerMediaSubsession();

protected: // redefined virtual functions
  virtual char const* sdpLines(int addressFamily);
  virtual void getStreamParameters(unsigned clientSessionId,
				   struct sockaddr_storage const& clientAddress,
                                   Port const& clientRTPPort,
                                   Port const& clientRTCPPort,
				   int tcpSocketNum,
                                   unsigned char rtpChannelId,
                                   unsigned char rtcpChannelId,
				   TLSState* tlsState,
                                   struct sockaddr_storage& destinationAddress,
				   u_int8_t& destinationTTL,
                                   Boolean& isMulticast,
                                   Port& serverRTPPort,
                                   Port& serverRTCPPort,
                                   void*& streamToken);
  virtual void startStream(unsigned clientSessionId, void* streamToken,
			   TaskFunc* rtcpRRHandler,
			   void* rtcpRRHandlerClientData,
			   unsigned short& rtpSeqNum,
                           unsigned& rtpTimestamp,
			   ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
                           void* serverRequestAlternativeByteHandlerClientData);
  virtual void pauseStream(unsigned clientSessionId, void* streamToken);
  virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT, double streamDuration, u_int64_t& numBytes);
  virtual void seekStream(unsigned clientSessionId, void* streamToken, char*& absStart, char*& absEnd);
  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken,
			      double streamEndTime, u_int64_t& numBytes);
  virtual void setStreamScale(unsigned clientSessionId, void* streamToken, float scale);
  virtual float getCurrentNPT(void* streamToken);
  virtual FramedSource* getStreamSource(void* streamToken);
  virtual void getRTPSinkandRTCP(void* streamToken,
				 RTPSink*& rtpSink, RTCPInstance*& rtcp);
  virtual void deleteStream(unsigned clientSessionId, void*& streamToken);
  }

OnDemandServerMediaSubsession的作用:

  • OnDemandServerMediaSubsession实现了ServerMediaSubsession的多个方法,如pauseStreamdeleteStream等。
  • 除了H265VideoFileServerMediaSubsession,还有H264VideoFileServerMediaSubsessionMPEG1or2DemuxedServerMediaSubsession等多个子类也都继承自OnDemandServerMediaSubsession
    通过理解这些类和函数的作用,我们可以更好地掌握如何在Live555框架内定制自己的实时视频流服务。

4.2 Live555流媒体服务的实现流程

最后定制Live555的RTSP服务涉及几个关键步骤,以下是这一过程的简要梳理:

1.创建RTSP服务**:
  • 首先需要创建一个RTSP服务实例。这个服务需要添加一个ServerMediaSession实例。
2.添加ServerMediaSession实例
  • 创建ServerMediaSession实例时,您需要为其提供一个ServerMediaSubsession实例。
3.实现ServerMediaSubsession实例
  • 在创建ServerMediaSubsession实例的过程中,需要实现许多操作函数。
4.利用OnDemandServerMediaSubsession简化实现
  • Live555提供了OnDemandServerMediaSubsession作为ServerMediaSubsession的一个实现,它已经实现了操作函数的公共部分,以减少您的工作量。
5.继承并定制OnDemandServerMediaSubsession:
  • 您需要继承OnDemandServerMediaSubsession并实现自己的getAuxSDPLine、createNewStreamSource和createNewRTPSink函数。
  • 分析class OnDemandServerMediaSubsession表明,getAuxSDPLine已提供了一个通用实现,因此您主要需要关注createNewStreamSource和createNewRTPSink两个函数的实现。

上代码~!
自定义服务如下

static int connectServer(void) 
{
    int sock = 0;
    struct sockaddr_in serv_addr;
    const int PORT = 1000;

    // Creating socket file descriptor
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        std::cout << "\n Socket creation error \n";
        return -1;
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // Convert IPv4 addresses from text to binary form
    if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cout << "\nInvalid address/ Address not supported \n";
        close(sock);
        return -1;
    }

    // Connect to the server
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cout << "\nConnection Failed \n";
        close(sock);
        return -1;
    }
    return sock;
}
//首先继承class FramedSource,定义一个自己的 CustomFramedSource,从socket中获取实时码流
class CustomFramedSource : public FramedSource {
public:
	static CustomFramedSource* createNew(UsageEnvironment& env,
      unsigned preferredFrameSize = 0,
      unsigned playTimePerFrame = 0) {
      //在创建CustomFramedSource的时候创建一个connectServer链接
      int socket = connectServer();
      if (socket < 0) return NULL;
      CustomFramedSource* newSource = new CustomFramedSource(env, socket, preferredFrameSize, playTimePerFrame);
      newSource->fFileSize = 0xFFFFFFFF;
      return newSource;
  }
  //CustomFramedSource 被销毁时记得关闭socket,不然会出现socket泄露
  //CustomFramedSource将会在客户端终止播放的时候被销毁
  ~CustomFramedSource() {
    printf("CustomFramedSource close socket=%d\n", m_socket);
    if(m_socket > 0) {
      ::close(m_socket);
    }
    m_socket = -1;
  }
    
  u_int64_t fileSize() const { return fFileSize; }
  
protected:
    CustomFramedSource(UsageEnvironment& env, int socket,
        unsigned preferredFrameSize,
				unsigned playTimePerFrame) : FramedSource(env),
        m_preferredFrameSize(preferredFrameSize),
        m_playTimePerFrame(playTimePerFrame),
        m_socket(socket){
        envir() << "Create New CustomFramedSource\n";
    }
	//我们只需要实现doGetNextFrame函数,将实时码流读到fTo中,将大小填到fFrameSize中
    virtual void doGetNextFrame() {
      fFrameSize = read(m_socket, fTo, fMaxSize);
      if (fFrameSize <= 0 ) {
        handleClosure();
        ::close(m_socket);
        return;
      }
      gettimeofday(&fPresentationTime, NULL);
      // Because the file read was done from the event loop, we can call the
      // 'after getting' function directly, without risk of infinite recursion:
      FramedSource::afterGetting(this);
    }
    
protected:
    unsigned m_preferredFrameSize;
    unsigned m_playTimePerFrame;
    unsigned fLastPlayTime;
    unsigned fFileSize;
    int m_socket = -1;
};
//创建一个自己的CustomMediaSubsession,继承OnDemandServerMediaSubsession;并实现createNewStreamSource和createNewRTPSink
class CustomMediaSubsession : public OnDemandServerMediaSubsession {
public:
  static CustomMediaSubsession* createNew(UsageEnvironment& env, Boolean reuseFirstSource = true) {
      return new CustomMediaSubsession(env, reuseFirstSource);
  }
  
protected:
  CustomMediaSubsession(UsageEnvironment& env, Boolean reuseFirstSource)
  : OnDemandServerMediaSubsession(env, reuseFirstSource)
  {
  }
  //创建调用CustomFramedSource创建自定义的FramedSource
  virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate) {
    estBitrate = 5000; // kbps, estimate
    // Create the video source:
    CustomFramedSource* fileSource = CustomFramedSource::createNew(envir());
    if (fileSource == NULL) return NULL;
    fFileSize = fileSource->fileSize();

    // Create a framer for the Video Elementary Stream:
    return H265VideoStreamFramer::createNew(envir(), fileSource);
  }
//直接调用H265VideoRTPSink创建RTPSink
  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
                                    unsigned char rtpPayloadTypeIfDynamic,
                                    FramedSource* inputSource) {
      return H265VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
  }
protected:
  char const* fFileName;
  u_int64_t fFileSize; 
};

main函数如下:

static void announceURL(RTSPServer* rtspServer, ServerMediaSession* sms) {
  if (rtspServer == NULL || sms == NULL) return; // sanity check

  UsageEnvironment& env = rtspServer->envir();

  env << "Play this stream using the URL ";
  if (weHaveAnIPv4Address(env)) {
    char* url = rtspServer->ipv4rtspURL(sms);
    env << "\"" << url << "\"";
    delete[] url;
    if (weHaveAnIPv6Address(env)) env << " or ";
  }
  if (weHaveAnIPv6Address(env)) {
    char* url = rtspServer->ipv6rtspURL(sms);
    env << "\"" << url << "\"";
    delete[] url;
  }
  env << "\n";
}

static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
			   char const* streamName, char const* inputFileName) {
  UsageEnvironment& env = rtspServer->envir();

  env << "\n\"" << streamName << "\" stream, from the file \""
      << inputFileName << "\"\n";
  announceURL(rtspServer, sms);
}

int main(int argc, char** argv) {
  // Begin by setting up our usage environment:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);
  
  OutPacketBuffer::maxSize = 1000000;
  RTSPServer* rtspServer = RTSPServer::createNew(*env);
  if (rtspServer == NULL) {
    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
    exit(1);
  }
  
  char const* descriptionString
    = "Session streamed by \"LiveRTSPServer\"";

  {
    char const* streamName = "video0";
    Boolean reuseFirstSource = true;
    ServerMediaSession* sms
      = ServerMediaSession::createNew(*env, streamName, streamName,
				      descriptionString);
    sms->addSubsession(CustomMediaSubsession::createNew(*env, reuseFirstSource));
    rtspServer->addServerMediaSession(sms);
    announceStream(rtspServer, sms, streamName, "TCP Socket Port 1000");
  }
  env->taskScheduler().doEventLoop(); // does not return

  return 0; // only to prevent compiler warning
}

5.成果展示

在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值