为了帮助大家更好地理解后续的代码,这里简要介绍一下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
在实现实时流媒体服务时,关键的一步是确定如何获取实时码流。以下是几种常见的方法:
- 通过Linux设备文件:直接从硬件驱动获取码流。
- 通过共享内存或Linux的IPC:从编码进程获取码流。
- 使用SOA编程架构:类似于ROS2的编程模式,在同一系统中订阅码流。
为了演示,我将采用第三种方法。但为避免引入ROS2的复杂性,我将使用本地lo网络的TCP socket来代替ROS2通信。
4.1大致实现思路
- VLC流媒体播放器请求:VLC流媒体播放器向流媒体服务申请网络直播视频流。
- 建立TCP连接:流媒体服务收到请求后,通过本地lo网络的TCP socket与编码进程建立连接,并通过socket读取实时码流。
- 编码进程发送码流:编码进程成功创建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的多个方法,如pauseStream、deleteStream等。
- 除了H265VideoFileServerMediaSubsession,还有H264VideoFileServerMediaSubsession、MPEG1or2DemuxedServerMediaSubsession等多个子类也都继承自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)**