若該文為原創文章,轉載請注明原文出處
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/147879917
長沙紅胖子Qt(長沙創微智科)博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…
FFmpeg、SDL和流媒體開發專欄
上一篇:《live555開發筆記(二):live555創建RTSP服務器源碼剖析,創建rtsp服務器的基本流程總結》
下一篇:敬請期待…
前言
??對于live555的rtsp服務器有了而基本的了解之后,進一步對示例源碼進行剖析,熟悉整個h264文件流媒體的開發步驟。
Demo
??
??
??播放本地文件,多路播放的時候,總是以第一個文件進度為準,所以當前這個Demo是同步播放的。
這對于攝像頭采集視頻實時播放來說,這個是滿足這個功能的。
基本概念
Source、Sink、Filter
- Source:作為數據流的起點,負責生成或獲取原始數據。例如:ByteStreamFileSource:從文件讀取原始字節流6,H264VideoStreamFramer:解析H.264視頻流并生成幀數據
- Filter:在數據流從Souce流到Sink的過程中能夠設置Filter,用于過濾或做進一步加工。例如:H264or5Fragmenter:將視頻幀分片以適應RTP包大小;解碼器Filter:將編碼數據解碼為原始幀。
- Sink:作為數據流的終點,負責消費或轉發數據。
??整個LiveMedia中,數據都是從Souce,經過一個或多個Filter。終于流向Sink。在server中數據流是從文件或設備流向網絡,而在client數據流是從網絡流向文件或屏幕。
??MediaSouce是全部Souce的基類,MediaSink是全部Sink的基類。
??從類數量和代碼規模能夠看到。LiveMedia類是整個LIVE555的核心,其內部包括數十個操作詳細編碼和封裝格式的類。LiveMedia定義的各種Souce均是從文件讀取,假設想實現從設備獲得實時流的傳輸,能夠定義自己的Souce。
ClientSession、ClientConnection
- ClientSession:對于每一個連接到server的client。server會為其創建一個ClientSession對象,保存該client的socket、ip地址等。同一時候在該client中定義了各種響應函數用以處理和回應client的各種請求。
- ClientConnection:用于處理一些與正常播放無關的命令。如命令未找到、命令不支持或媒體文件未找到等。在ClientConnection處理DESCRIBE命令時會創建ClientSession對象。其它命令在ClientSession中處理。
MediaSession、MediaSubsession、Track
??LIVE555使用MediaSession管理一個包括音視頻的媒體文件。每一個MediaSession使用文件名稱唯一標識。使用SubSession管理MediaSession中的一個音頻流或視頻流,音頻或視頻均為一個媒體文件里的媒體流,因此一個MediaSession能夠有多個MediaSubsession,可單獨管理音頻流、視頻流,并為每一個媒體流分配一個TrackID,如視頻流分配為Track1,音頻流分配為Track2,此后client必須在URL指定要為那個Track發送SETUP命令,因此我們能夠覺得MediaSubsession代表Server端媒體文件的一個Track,也即相應一個媒體流。MediaSession代表Server端一個媒體文件。
??對于既包括音頻又包括視頻的媒體文件,MediaSession內包括兩個MediaSubsession。但MediaSession和MediaSubsession僅代表靜態信息。若多個client請求同一個文件,server僅會創建一個MediaSession。各個client公用。為了區分各個MediaSession的狀態又定義了StreamState類,用來管理每一個媒體流的狀態。在MediaSubsession中完畢了Souce和Sink連接。Souce對指針象會被設置進sink。在Sink須要數據時,能夠通過調用Souce的GetNextFrame來獲得。
??LIVE555中大量使用簡單工廠模式,每一個子類均有一個CreateNew靜態成員。該子類的構造函數被設置為Protected,因此在外部不能直接通過new來構造。同一時候。每一個類的構造函數的參數中均有一個指向UsageEnvironment的指針,從而能夠輸出錯誤信息和將自己增加調度。
HashTable
??IVE555內部實現了一個簡單哈希表類BasicHashTable。在LIVE555中。有非常多地方須要用到該哈希表類。如:媒體文件名稱與MediaSession的映射,SessionID與ClientSession的映射,UserName和Password的映射等。
SDP
??SDP是Session Description Protocol的縮寫。是一個用來描寫敘述多媒體會話的應用層協議。它是基于文本的,用于會話建立過程中的媒體類型和編碼方案的協商等。
rtp與rtcp與rtsp服務器
RTP(實時傳輸協議)
??負責傳輸實時音視頻數據流,基于UDP/IP協議實現低延遲傳輸。支持時間戳、序列號等機制,確保數據包的時序性和同步性。不保證傳輸可靠性,需結合RTCP進行質量監控。
RTCP(實時傳輸控制協議)
??作為RTP的輔助協議,用于監控傳輸質量、反饋統計信息(如丟包率、延遲、抖動)。功能實現:通過發送SR(發送者報告)和RR(接收者報告)反饋網絡狀態。支持帶寬動態調整(如TMMBR/TMMBN)和流同步。
RTSP(實時流協議)服務器
??作為流媒體會話的控制中心,負責客戶端與服務器的交互(如播放、暫停、停止等指令)。
- 會話管理:通過SETUP、PLAY、TEARDOWN等命令控制媒體流傳輸的生命周期。
- 協議協調:與RTP/RTCP協同工作,RTSP定義控制邏輯,RTP傳輸數據,RTCP監控質量。
- 多流支持:可同時管理音視頻等多路流,并通過SDP協議協商編解碼參數。
關鍵類介紹
H264VideoRTPSink:H264視頻數據核心組件
??H264VideoRTPSink是H.264視頻流通過RTP/RTSP協議實現實時傳輸的關鍵模塊,負責協議封裝、數據分片和網絡適配,確保視頻流在實時場景下的高效性和兼容性。
??
??H264VideoRTPSink是Live555 流媒體框架中用于封裝和傳輸H.264視頻數據的核心組件,其作用主要包含以下方面:
- RTP數據封裝:H264VideoRTPSink 將 H.264 視頻數據按 RTP 協議規范封裝成網絡傳輸包。具體包括:添加 RTP 包頭信息(如時間戳、序列號、負載類型等);根據 H.264 的 NALU(網絡抽象層單元)結構對視頻數據進行分片或重組,確保數據適應網絡傳輸的最大傳輸單元(MTU)限制。
- 分片處理(Fragmentation):當單個 H.264 幀超過 MTU 限制時,H264VideoRTPSink 會將其拆分為多個 RTP 包(例如使用 FU-A 分片模式),并在包頭中標記分片的起始、中間和結束位置。
- 與 RTSP 協議協同工作:在RTSP會話中,H264VideoRTPSink作為數據傳輸的終點(Sink),與MediaSource(數據源)配合,完成從數據讀取到RTP封裝的完整流程。在客戶端發送PLAY請求后,服務器通過H264VideoRTPSink將封裝后的RTP流推送至網絡。緩沖區管理處理大數據量H.264視頻時,H264VideoRTPSink需依賴動態調整的緩沖區(如OutPacketBuffer::maxSize),防止因數據包過大導致的傳輸失敗。
RTCPInstance:RTCP通訊類
??在Live555框架中,RTCPInstance與RTPSink、RTPInterface等類協作,共同實現完整的RTP/RTCP流媒體傳輸功能。其設計獨立于其他高層協議模塊,僅依賴基礎網絡組件,具備較好的封裝性。
??
??RTCPInstance是Live555框架中封裝RTCP協議通信的核心類:
- RTCP協議通信的封裝與實現:負責RTCP數據包的收發處理,支持通過RTPInterface實現基于UDP或TCP的傳輸。接收到的RTCP報文(如SR、RR、BYE等)在incomingReportHandler等回調函數中處理,實現網絡狀態反饋和會話控制。
- 網絡狀態監測與統計:統計RTP包的收發情況,收集丟包率、延遲、抖動等指標,為流量控制提供數據支持。依賴RTPSink類獲取發送端統計信息,實現與RTP流的關聯。
- 反饋與動態調整機制:通過發送SR(發送者報告)和RR(接收者報告)實時反饋網絡質量,觸發發送速率調整或丟包重傳。支持帶寬控制機制(如TMMBR/TMMBN),根據網絡狀況動態調整媒體流傳輸參數。
- 會話管理與流同步:處理BYE報文實現參與者退出通知,維護會話成員狀態。通過SDES報文傳遞參與者描述信息,輔助多流同步(如音視頻同步)。
ByteStreamFileSource:基礎數據源組件
??ByteStreamFileSource是Live555中處理文件型H.264流的核心入口組件,承擔數據讀取與基礎分塊功能,并為上層解析、封裝模塊提供標準化輸入接口。
??
??ByteStreamFileSource在Live555 流媒體框架中作為基礎數據源組件,核心作用如下:
- 原始字節流讀取:負責從本地文件(如 H.264 裸流文件)中讀取未封裝的原始字節數據,并以分塊(Framed)形式輸出,供后續解析模塊處理。
- 數據鏈的起點:在典型的 H.264 傳輸鏈路(如 H264VideoFileServerMediaSubsession中)中,其作為初始節點啟動數據流,后續連接解析器(如H264VideoStreamParser)和分幀器(H264VideoStreamFramer),形成完整處理鏈路:ByteStreamFileSource → H264VideoStreamParser → H264VideoStreamFramer → … → RTP 封裝。
- 可擴展性支持:雖然默認實現為文件讀取,但其繼承自FramedSource基類,用戶可通過自定義派生類(如實時采集或編碼的數據源)替代該組件,實現靈活的數據輸入適配。
- 關鍵參數配置:支持通過fileSize屬性獲取文件大小信息,便于預估傳輸帶寬需求。在動態服務器場景中,需注意其與緩沖區大小(如OutPacketBuffer::maxSize)的協同配置,避免大幀溢出問題。
FrameSource:幀源抽象類
??FrameSource是Live555中實現按幀輸入媒體數據的基礎組件,為RTSP/RTP 傳輸提供標準化的數據源接口,支持多格式媒體流的靈活擴展。
??
??FrameSource 在 Live555 流媒體框架中作為數據源的核心抽象類,其作用可歸納如下:
- 基礎數據源抽象:FrameSource 是負責按幀(Framed)提供流媒體數據的基類,定義了統一的幀數據讀取接口。其子類需實現 doGetNextFrame 方法,用于逐幀獲取原始媒體數據(如視頻幀或音頻塊)。
- RTP數據流起點:在RTSP/RTP傳輸鏈路中,FrameSource 作為數據生產者,將媒體數據以幀為單位傳遞給下游處理模塊(如解析器、封裝器)。例如,H264VideoStreamFramer 繼承自 FrameSource,負責將裸 H.264 碼流分幀后輸出。
- 接口標準化:FrameSource 通過純虛函數強制子類實現關鍵操作,包括:幀數據異步獲取機制(通過 afterGetting 回調觸發數據傳輸);幀數據的分塊與緩沖區管理。
- 與下游組件協同:FrameSource與MediaSink類(如H264VideoRTPSink)形成數據鏈路:MediaSink通過fSource成員綁定FrameSource,驅動數據拉取與封裝流程;在RTSP會話中,FrameSource的數據最終被封裝為RTP包并發送至客戶端。
H264VideoStreamFramer:H264視頻流幀器
??H264VideoStreamFramer在RTSP流媒體服務中通常由H264VideoFileServerMediaSubsession創建,是H.264實時流傳輸的關鍵解析層,承擔了從原始字節流到結構化視頻數據的轉換任務。
??
??H264VideoStreamFramer在Live555框架中作為視頻流解析的核心組件,主要承擔H.264基本流(ES)的解析與重構,其核心功能如下:
- H.264原始流解析?:從ByteStreamFileSource等數據源讀取原始字節流,通過內置的H264VideoStreamParser解析器識別NALU(網絡抽象層單元)邊界,將連續字節流分割為獨立NALU單元?25處理H.264 Annex B格式的起始碼(如0x00000001),實現NALU的精準定位?56
- ?參數集處理?:提取并緩存SPS(序列參數集)和PPS(圖像參數集),用于后續解碼器初始化?,在流啟動時優先發送SPS/PPS,確保解碼端正確初始化?。
- 分幀邏輯控制?:區分VCL(視頻編碼層)和非VCL NALU,根據幀類型(如IDR幀、非IDR幀)組織數據輸出?56;處理分片單元(Slice)的關聯性,確保幀完整性?
- 時間戳生成機制:基于視頻幀率或外部輸入時鐘計算RTP時間戳,實現與音視頻同步;處理B幀/P幀的顯示時間戳(PTS)與解碼時間戳(DTS)關系。
- ?與上下游組件協作?:作為FramedSource的子類,向上連接ByteStreamFileSource獲取原始數據,向下對接H264FUAFragmenter完成RTP分片?;通過事件驅動模型觸發數據讀取,形成Source → Parser → Framer → Fragmenter → RTPSink的完整處理鏈路?。
??H264VideoStreamFramer把自己的緩沖(其實是sink的)傳給H264VideoStreamParser,每當H264VideoStreamFramer要獲取一個NALU時,就跟H264VideoStreamParser要,而H264VideoStreamParser就從ByteStreamFileSource讀一坨數據,然后進行分析,如果取得了一個NALU,就傳給H264VideoStreamFramer。
Live555流媒體服務實現基本流程
步驟一:創建任務調度管理器
??
步驟二:創建rtp和rtcp
??在不同平臺使用的socketaddr_storage類型有區別,有些事socketaddr_in,主要是groupsock的頭文件構造函數類型的區別,判斷是live555各種版本有區別。
??
步驟三:創建H264VideoRTPSink
??
步驟四:創建RTCPInstance
??
步驟五:RTSP服務器
??
步驟六:創建ServerMediaSession實例
??
步驟七:創建subsession實例
??
步驟八:開始播放
??
步驟九:服務器運行
??
Demo區別
??
??沒有啟動服務器http端口監聽,而是直接play。
整理后的中文注釋代碼
/*為了使此應用程序正常工作,H.264 Elementary Stream視頻文件*必須*包含SPS和PPS NAL單元,最好在文件開頭或附近。這些SPS和PPS NAL單元用于指定在輸出流的SDP描述中設置的“配置”信息(由此應用程序內置的RTSP服務器設置)。另請注意,與其他一些“*Streamer”演示應用程序不同,生成的流只能使用RTSP客戶端(如“openRTSP”)接收
*/#include <liveMedia.hh>
#include <BasicUsageEnvironment.hh>
#include <GroupsockHelper.hh>UsageEnvironment* env;
//char const* inputFileName = "test.264";
char const* inputFileName = "T:/test/front/20250311_123244_0.h264";
H264VideoStreamFramer* videoSource;
RTPSink* videoSink;void announceURL(RTSPServer* rtspServer, ServerMediaSession* sms)
{if(rtspServer == NULL || sms == NULL){return;}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";
}void play(); // forwardint main(int argc, char** argv)
{// 步驟一:創建任務調度器和運行信息環境TaskScheduler* scheduler = BasicTaskScheduler::createNew();env = BasicUsageEnvironment::createNew(*scheduler);// 步驟二:創建groupsocks用于RTP和RTCPstruct sockaddr_storage destinationAddress;destinationAddress.ss_family = AF_INET;((struct sockaddr_in&)destinationAddress).sin_addr.s_addr = chooseRandomIPv4SSMAddress(*env);// 這是一個多播地址。如果希望使用單播進行流式傳輸,那么應該使用“testOnDemand RTSPServer”測試程序(而不是此測試程序)作為模型。const unsigned short rtpPortNum = 18888;const unsigned short rtcpPortNum = rtpPortNum+1;const unsigned char ttl = 255;const Port rtpPort(rtpPortNum);const Port rtcpPort(rtcpPortNum);Groupsock rtpGroupsock(*env, destinationAddress, rtpPort, ttl);rtpGroupsock.multicastSendOnly();Groupsock rtcpGroupsock(*env, destinationAddress, rtcpPort, ttl);rtcpGroupsock.multicastSendOnly(); //// 步驟三:從RTP“groupsock”創建“H264視頻RTP”接收器OutPacketBuffer::maxSize = 100000;videoSink = H264VideoRTPSink::createNew(*env, &rtpGroupsock, 96);// 步驟四:為此RTP接收器創建(并啟動)一個“RTCP實例” kbps為單位;RTCP b/w份額const unsigned estimatedSessionBandwidth = 500;const unsigned maxCNAMElen = 100;unsigned char CNAME[maxCNAMElen+1];gethostname((char*)CNAME, maxCNAMElen);CNAME[maxCNAMElen] = '\0'; // just in caseRTCPInstance* rtcp = RTCPInstance::createNew(*env,&rtcpGroupsock,estimatedSessionBandwidth,CNAME,videoSink,NULL, // 代表服務器True); // 代表SSM源// 步驟五:這將自動啟動RTCP運行RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554);if (rtspServer == NULL){*env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";exit(1);}// 步驟六:創建ServerMediaSessionServerMediaSession* sms = ServerMediaSession::createNew(*env,"testStream",inputFileName,"Session streamed by \"testH264VideoStreamer\"",True);// 步驟七:創建subsessionsms->addSubsession(PassiveServerMediaSubsession::createNew(*videoSink, rtcp));rtspServer->addServerMediaSession(sms);announceURL(rtspServer, sms);// 開始流播放:*env << "Beginning streaming...\n";// 這個開始播放函數調用不調用區別:// 1.不調用時,有客戶端輸入,無法播放;必須調用play,進入則會開始調用播放,從頭開始播放// 2.后進入的客戶端播放進度會主動同步首個連接的客戶端播放進度play();// 服務器阻塞進入服務循環env->taskScheduler().doEventLoop(); // does not returnreturn 0;
}void afterPlaying(void* clientData)
{*env << "...done reading from file\n";// 停止播放videoSink->stopPlaying();// 請注意,這也會關閉此源讀取的輸入文件。這是靜態方法,可以直接關閉Medium::close(videoSource);// 再次開始播放play();
}void play()
{// 將輸入文件作為“字節流文件源”打開ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(*env, inputFileName);if(fileSource == NULL){*env << "Unable to open file \"" << inputFileName<< "\" as a byte-stream file source\n";exit(1);}FramedSource* videoES = fileSource;// 為視頻基本流創建幀器videoSource = H264VideoStreamFramer::createNew(*env, videoES);// 最后,開始播放*env << "Beginning to read from file...\n";videoSink->startPlaying(*videoSource, afterPlaying, videoSink);
}
工程模板v1.2.0
??
上一篇:《live555開發筆記(二):live555創建RTSP服務器源碼剖析,創建rtsp服務器的基本流程總結》
下一篇:敬請期待…
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/147879917