大家好,欢迎来到IT知识分享网。
前述:
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的下一个端口(奇数端口)进行控制信息传输。
1.1 RTSP服务建立链接
- 建立阶段:客户端连接到流服务器并发送一个RTSP描述命令(DESCRIBE)。
- 服务器响应:流服务器响应这个命令,提供一个SDP(Session Description Protocol)描述,其中包含了流数量、媒体类型等关键信息。
- 客户端分析与设置:客户端分析SDP描述,并针对会话中的每一个流发送一个RTSP建立命令(SETUP)。这一步骤中,客户端将告诉服务器用于发送媒体数据的端口号,也就是建立RTP的端口号。
- 开始播放:客户端发送一个播放命令(PLAY),服务器随即开始通过UDP传送媒体流(即RTP包)到客户端。在播放过程中,客户端还可以向服务器发送命令来控制播放,如快进、快退和暂停。
- 结束会话:当媒体播放完成或用户决定停止时,客户端发送一个终止命令(TEARDOWN),以结束流媒体会话。
1.2 RTP协议介绍
RTCP(Real-Time Control Protocol)在流媒体传输中起着辅助作用,但它不是本文的重点,有兴趣的读者可以自行查阅更多资料。
2.Live555框架介绍
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 嵌入式应用交叉编译
#!/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-
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 = ; //创建一个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继承自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 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 = ; 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.成果展示
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121308.html




