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

为了帮助大家更好地理解后续的代码,这里简要介绍一下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
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数嵌入式工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/6343a6522c897a5b556d575b9de1367c.png)

![img](https://img-blog.csdnimg.cn/img_convert/2a44f8bf23567b31c6ac268260ab5c77.jpeg)

![img](https://img-blog.csdnimg.cn/img_convert/b7ce77521506881ce43ff5b799cf5b2f.png)

 **既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**

![img](https://img-blog.csdnimg.cn/img_convert/f14301334d2c5af3281c6c749f29a09f.png)

![img](https://img-blog.csdnimg.cn/img_convert/5370924d20dcea16ba0598873396cb02.png)

 

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)**

<img src="https://img-community.csdnimg.cn/images/73bb5de17851459088c6af944156ee24.jpg" alt="img" style="zoom: 67%;" />



# 最后

**资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~**

**你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!**

&物联网开发知识点,真正体系化!**

[外链图片转存中...(img-1MGODxN0-1712382633958)]

[外链图片转存中...(img-15t04BQR-1712382633959)]

 

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)**

<img src="https://img-community.csdnimg.cn/images/73bb5de17851459088c6af944156ee24.jpg" alt="img" style="zoom: 67%;" />



# 最后

**资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~**

**你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!**

**[更多资料点击此处获qu!!](https://bbs.csdn.net/topics/618376385)**
  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
live555是一种用于实时取流的开源多媒体库。它提供了一组C++类,用于实现处理多媒体流的功能。通过使用live555,我们可以轻松地实现从网络摄像头、音频设备或网络流媒体服务器获取实时流的功能。 具体来说,使用live555实时取流的过程主要包括以下几个步骤: 首先,我们需要创建一个RTSP(实时流协议)客户端或服务器的实例。RTSP是一个用于控制实时流传输的协议,通过RTSP,我们可以发送命令来控制流的播放、暂停、停止等操作。 接下来,我们需要使用live555提供的类,如MediaSession、MediaSubsession等,来设置流的参数和会话设置。通过设置这些参数,我们可以指定流的编码格式、传输协议、端口等信息。 然后,我们可以使用live555提供的类,如RTSPClient、RTSPServer等,来建立与实时流的连接。通过指定流的URL地址和服务器的IP地址或主机名,我们可以建立与实时流的通信。 最后,我们可以使用live555提供的函数,如startPlaying()、play()等,来开始播放实时流。通过调用这些函数,我们可以启动实时流的接收和解码,将流的数据传输到播放器中进行播放或处理。 总的来说,live555一个功能强大的多媒体库,它提供了一套完整的API,用于实现实时取流的功能。通过使用live555,我们可以轻松地从摄像头、音频设备或网络流媒体服务器获取实时流,并进行播放或其他操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值