librtmp库API介绍及其结构概述
文章目錄
- librtmp庫API介紹及其結構概述
- 1、librtmp API的使用
- 1.1 librtmp API的使用流程
- 1.2 librtmp的實例
- 1.3 librtmp API詳解
- 1.3.1 librtmp中的chunk和message
- 1.3.2 RTMP基本API
- 1.3.3 設置RTMP的屬性
- 1.3.4 連接服務器
- 1.3.5 創建rtmp流
- 1.3.6 對RTMP流的操作
- 1.3.6.1 控制類API
- 1.3.6.2 查詢類API
- 1.3.6.3 讀寫類API
- 2. librtmp數據流的讀寫流程
- 2.1 傳輸層
- 2.1.1 TCP接口
- 2.1.2 HTTP層接口
- 2.2 Message讀寫層
- 2.3 業務層
- 2.4 FLV讀寫層
- 2.5 其他:處理Message以及發送Message
- 3.rtmpdump示例
- 4. 參考文章
- 5. 注釋代碼
librtmp庫API介紹及其結構概述
? rtmp是常見的一種流媒體協議,它是由Adobe公司提出的一種應用層協議。rtmp傳輸的是flv格式的封裝數據,flv中保存的一般是H.264視頻流和AAC音頻流。
? librtmp庫實現了rtmp協議的客戶端功能,以及少數服務端功能,是學習rtmp的一個良好的工具。
? 本文針對librtmp的主要API做了介紹,并整理了librtmp的大致實現框架,但并沒有詳細深入到具體細節。本文并非入門類的文章,而是一個提綱挈領性的總結,希望對想要使用librtmp以及學歷librtmp的人有所幫助。
? 在想要深入理解librtmp是如何工作之前,請先對FLV封裝格式和rtmp協議做相關了解。
1、librtmp API的使用
1.1 librtmp API的使用流程
? librtmp API的使用流程可以概括為如下幾步:
分配并初始化一個RTMP實例
設置RTMP實例的參數
調用RTMP_Connect函數連接到服務器
調用RTMP_ConnectStream函數創建一個rtmp流
使用RTMP_Read或RTMP_Write進行讀寫,或者對rtmp流進行控制(如暫停、停止等)
讀寫完成后,關閉并釋放RTMP
?
? 在librtmp中,使用結構體RTMP表示一個rtmp連接。
? 在第2步中根據自己的需要對rtmp連接做相應的設置,后續librtmp就會根據我們的設置做對應的操作。注意這里僅僅是設置了參數,這些參數尚未被使用。考察以下兩個接口:
//設置緩沖的時長,單位:毫秒 void RTMP_SetBufferMS(RTMP *r, int size); //發送“設置緩沖時長”的控制包 void RTMP_UpdateBufferMS(RTMP *r);? 前者僅是將size這個參數保存到實例r中,后者則是使用實例r中參數并起到實際的作用。
? 因此,要注意:一般我們僅在第2步進行參數設置操作,不建議在其他地方進行參數設置,這是因為設置參數和這個參數實際起到相關作用是兩個概念。當然,如果你非常了解librtmp的工作方式,也可以在其他地方進行設置。
? 在連接到rtmp服務器,并建立一個rtmp流之后,我們就可以對這個流進行相關的操作。例如,可以進行暫停、停止等操作,也可以讀取或者寫入到rtmp流。
? 等相關操作完成之后,必須關閉并釋放RTMP實例。
1.2 librtmp的實例
? 我們首先查看兩個使用librtmp API的例子,了解一下其API以及使用流程。
? 首先,是讀取rtmp流另存為flv文件的示例:
https://blog.csdn.net/leixiaohua1020/article/details/42104893
? 然后是將本地文件上傳到rtmp服務器的示例:
https://blog.csdn.net/leixiaohua1020/article/details/42104945
? 在詳細了解了上述兩個例子后,就可以詳細系統地學習librtmp的API。
1.3 librtmp API詳解
? 通過上述的示例我們知道RTMP類型是librtmp中非常重要的一個類型,我們現在查看一下它的相關API。
? 先不必查看RTMP的具體組成,我們只需要知道它代表一個rtmp流就可以了。下面就是RTMP相關的最基本的API。
? 這些基本API包括有:分配,初始化,關閉rtmp流,釋放。
1.3.1 librtmp中的chunk和message
? 首先,我們了解一下librtmp中chunk和Message相關的結構體,以及其相關的操作。
? 以下分別對應chunk和Mesage的結構體:
/*表示一個 raw chunk,原始chunkc_header,c_headerSize:保存chunk header的數據和大小c_chunk,c_chunkSize:保存chunk data的數據和大小 */ typedef struct RTMPChunk {int c_headerSize;int c_chunkSize;char *c_chunk;char c_header[RTMP_MAX_HEADER_SIZE]; } RTMPChunk;/*表示一個Messagem_headerType :表示m_chunk的類型,即chunk header中的basic header中的fmtm_packetType :表示Message Type IDm_hasAbsTimestamp :表示時間戳是絕對的還是相對的,即chunk type為0時為絕對時間戳,其他類型時為時間戳增量m_nChannel :表示chunk Stream IDm_nTimeStamp :時間戳m_nInfoField2 :chunk fmt為0時,header的最后四個字節,即Message Stream IDm_nBodySize :Message的body的尺寸m_nBytesRead :已經讀取到的body的字節數m_chunk :如果不為NULL,表示用戶想要獲取chunk,那么在讀取Message時,會填充這個字段m_body :Message的body */ typedef struct RTMPPacket {uint8_t m_headerType;uint8_t m_packetType;uint8_t m_hasAbsTimestamp; /* timestamp absolute or relative? */int m_nChannel;uint32_t m_nTimeStamp; /* timestamp */int32_t m_nInfoField2; /* last 4 bytes in a long header */uint32_t m_nBodySize;uint32_t m_nBytesRead;RTMPChunk *m_chunk;char *m_body; } RTMPPacket;//將全部字段置空(緩沖區仍在) void RTMPPacket_Reset(RTMPPacket *p); //打印packet void RTMPPacket_Dump(RTMPPacket *p); //為p分配緩沖區,其中包括固定大小的header,nSize大小的body int RTMPPacket_Alloc(RTMPPacket *p, int nSize); //釋放緩沖區 void RTMPPacket_Free(RTMPPacket *p);//packet是否完備 #define RTMPPacket_IsReady(a) ((a)->m_nBytesRead == (a)->m_nBodySize)? 注意以下幾點:
- chunk僅有一個保存其原始內容的結構體,而沒有詳細的相關字段的結構體;
- message中的字段名有些似是而非,而且所包含的內容似乎和chunk的內容有所混淆,這是因為librtmp是逆向工程的產物,作者是根據實際rtmp的數據包猜測rtmp協議的內容,逆向解析rtmp協議,當時Adobe尚未公布rtmp協議的內容;
1.3.2 RTMP基本API
//分配一個RTMP //其操作僅僅是分配一塊RTMP大小的內存而已,RTMP也可以在棧上分配 RTMP *RTMP_Alloc(void);//初始化RTMP,將RTMP各個字段設置為默認值 void RTMP_Init(RTMP *r);//關閉這個RTMP,如果當前處于連接狀態,則會關閉連接;然后釋放RTMP相關的內容 void RTMP_Close(RTMP *r);//釋放RTMP的內存 void RTMP_Free(RTMP *r);1.3.3 設置RTMP的屬性
? 分配并初始化一個RTMP實例之后,就需要設置RTMP實例的屬性,以下就是設置屬性相關的API。
//解析RTMP地址 //url的格式為:protocol://host:port/app/playpath,將解析后的字段通過參數返回 int RTMP_ParseURL(const char *url, int *protocol, AVal *host,unsigned int *port, AVal *playpath, AVal *app);//從RTMP URL中解析playpath void RTMP_ParsePlaypath(AVal *in, AVal *out);//設置緩沖的時長,單位:毫秒 void RTMP_SetBufferMS(RTMP *r, int size);//設置RTMP的相關屬性 //如果opt指定的屬性不存在,那么會調用RTMP_OptUsage()打印出RTMP所有的屬性。 int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);//打印rtmp所有的屬性 static void RTMP_OptUsage();//設置url int RTMP_SetupURL(RTMP *r, char *url);//設置流 void RTMP_SetupStream(RTMP *r, int protocol, AVal *hostname, unsigned int port,AVal *sockshost,AVal *playpath,AVal *tcUrl,AVal *swfUrl,AVal *pageUrl,AVal *app,AVal *auth,AVal *swfSHA256Hash,uint32_t swfSize,AVal *flashVer,AVal *subscribepath,int dStart,int dStop, int bLiveStream, long int timeout);//設置RTMP Link添加寫屬性 //即要pulish一個流而不是play一個流 void RTMP_EnableWrite(RTMP *r);? 首先,RTMP_ParseURL() 和 RTMP_ParsePlaypath() 是兩個輔助函數,它們用于將形似protocol://host:port/app/playpath 的字符串,解析為對應的各個字段,然后再設置給RTMP示例。
? 后面5個函數才是設置屬性的函數。其中,RTMP_SetBufferMS() 和 RTMP_EnableWrite() 只能設置單個相關的屬性:前者設置“緩沖時長”,后者告訴librtmp我們要使用polish而非play,即我們要推流到服務器而不是從服務器拉流。
? 而剩下的三個函數在本質上是一樣的,沒有任何區別,只是為設置屬性提供了不同形式的接口而已。RTMP_SetOpt() 會根據opt來設置不同的屬性;RTMP_SetupURL() 則在底層會調用 RTMP_ParseURL() 函數,然后將解析出來的各個字段分別設置為RTMP實例的不同屬性;RTMP_SetupStream() 則提供了一個方便的接口,讓我們可以一次性設置全部的屬性。
? 那么,RTMP具體可以設置哪些屬性呢?我們不在這里贅述,請查看rtmpdump的介紹,其中詳細介紹了RTMP的相關屬性。
1.3.4 連接服務器
/*客戶端發起一個和服務器的TCP連接,并執行Connect內部調用了RTMP_Connect0來完成TCP層面的連接調用RTMP_Connect1完成了Connect */ int RTMP_Connect(RTMP *r, RTMPPacket *cp); struct sockaddr; //將host和port信息寫入到sockaddr_in中 static int add_addr_info(struct sockaddr_in *service, AVal *host, int port); //連接到指定地址(服務器地址或代理地址),僅在TCP層 int RTMP_Connect0(RTMP *r, struct sockaddr *svc); //假定TCP層面的連接已經完成 //進行handshake以及發送Connect Message int RTMP_Connect1(RTMP *r, RTMPPacket *cp);//服務端處理handshake,成功返回值大于0 int RTMP_Serve(RTMP *r);? 從用戶的角度而言,這部分沒什么內容,僅需知道 RTMP_Connect() 函數可以連接到rtmp服務器即可。
1.3.5 創建rtmp流
//連接到url指定的流,返回TRUE和FLASE int RTMP_ConnectStream(RTMP *r, int seekTime);? 這一步也比較簡單,僅需注意在調用它之前,設置足夠的屬性即可。尤其是如下兩點:
- 設置正確的的url信息
- 如果是推流,即polish,那么調用 RTMP_EnableWrite() ,如果是播放一個流,即play,那么不需要額外設置,默認就是play操作。
1.3.6 對RTMP流的操作
? rtmp流創建完成之后,就可以針對這個流做相關的操作。針對RTMP流的操作,可以分為三大類:一類是控制操作,如暫停、停止、恢復、重新連接、中斷等等操作;第二類是查詢類接口,返回rtmp流的當前狀態;最后一類則是讀寫操作,用于讀取或者寫入rtmp流數據。
1.3.6.1 控制類API
//發送“設置緩沖時間”的控制包 void RTMP_UpdateBufferMS(RTMP *r); //暫停或者恢復流 int RTMP_ToggleStream(RTMP *r); //重新連接流,即delete Stream之后再次Connect Stream,且同步等待到流開始 int RTMP_ReconnectStream(RTMP *r, int seekTime); //停止流,或者刪除流 void RTMP_DeleteStream(RTMP *r); //RTMP流程被打斷 //本質為設置全局變量RTMP_ctrlC為true void RTMP_UserInterrupt(void); /* user typed Ctrl-C */? 所謂控制類的API,就是會對當前rtmp流產生改變的API。從用戶角度而言,一般就是對rtmp流進行暫停、恢復、停止等操作,而在rtmp協議層面,還會有其他的操作。在這里我們僅關注前者,無論是那種API,它們都是通過發送一個RTMP Msg來實現的。
1.3.6.2 查詢類API
//RTMP是否處于連接狀態 int RTMP_IsConnected(RTMP *r); //返回RTMP關聯的套接字 int RTMP_Socket(RTMP *r); //RTMP是否中斷 int RTMP_IsTimedout(RTMP *r); //獲得RTMP當前流的長度 double RTMP_GetDuration(RTMP *r);? 查看當前rtmp流的相關狀態或者信息。
1.3.6.3 讀寫類API
//RTMP讀取到一個packet,并不保證一定讀取到完整的packet int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet); //使用RTMP發送packet,queue表示是否按照隊列發送 int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue); //RTMP發送一個chunk int RTMP_SendChunk(RTMP *r, RTMPChunk *chunk);//使用RTMP讀取數據 int RTMP_Read(RTMP *r, char *buf, int size); //使用RTMP寫入數據 int RTMP_Write(RTMP *r, const char *buf, int size);? librtmp中的讀寫的接口非常多,如果不了解的話,根本不知道應該調用哪些函數來完成我們的功能。
? 以上是我們最常用的5個讀寫API,先暫時記住,會使用即可。我們在下面會詳細介紹librtmp的讀寫流程。
2. librtmp數據流的讀寫流程
? 我們只有全面了解了librtmp庫的數據解析流程,才能對這個庫有一個整體的把握,從而更合理的使用這個庫。并通過這個過程印證學習RTMP協議,這才是最重要的。
? 我根據自己的理解,將數據解析的流程分為多個層次,下面我們按照從底到高的順序一一了解其解析流程。
2.1 傳輸層
? 標準的rtmp協議在底層使用TCP協議傳輸數據,而有的一些變種rtmp協議則可能會使用HTTP協議來傳輸數據。無論使用那種協議,這部分都有且僅有數據傳輸的功能,因此我們將其稱為傳輸層。
? 傳輸層對上提供了一組統一的接口,以封裝其傳輸協議的不同,接口如下所示:
//讀取N個字節,返回實際讀取到的字節數 static int ReadN(RTMP *r, char *buffer, int n);//寫入N個字節,返回實際寫入的字節數 static int WriteN(RTMP *r, const char *buffer, int n);? 而傳輸層則依賴更底層的TCP接口和HTTP接口。
2.1.1 TCP接口
? TCP層相關的定義如下,較為簡單:
/*RTMPSockBuf:RTMP的套接字緩存表示一個TCP套接字連接,以及其讀取緩存sb_socket :Socket套接字sb_size :buffer中未處理的字節數量,即緩沖數據的大小sb_start :指向buffer中需要處理的字節,即指向緩沖數據sb_buf :數據讀取緩沖區sb_timedout :套接字是否中斷sb_ssl :SSL相關數據 */ typedef struct RTMPSockBuf {int sb_socket;int sb_size; /* number of unprocessed bytes in buffer */char *sb_start; /* pointer into sb_pBuffer of next byte to process */char sb_buf[RTMP_BUFFER_CACHE_SIZE]; /* data read from socket */int sb_timedout;void *sb_ssl; } RTMPSockBuf;//TCP層面的操作:讀、寫、關閉 int RTMPSockBuf_Fill(RTMPSockBuf *sb); int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len); int RTMPSockBuf_Close(RTMPSockBuf *sb);2.1.2 HTTP層接口
? HTTP層的接口也相對簡單,如下所示:
//HTTP層面的操作:讀,寫 static int HTTP_Post(RTMP *r, RTMPTCmd cmd, const char *buf, int len); static int HTTP_read(RTMP *r, int fill);2.2 Message讀寫層
? 這個層在傳輸層之上,它的作用是讀寫Message和Chunk。
? 這個層是非常單純的讀寫的層,它直接解析從傳輸層讀取到的數據,而不處理解析出來的Message和Chunk,它也不管要寫入的數據的內容是什么,直接調用傳輸層接口寫入相關數據。
? 特點:解析數據,但不處理數據的內容,而是交給更上層處理。
/*作用:讀取一個chunk,然后查看chunk是否能合成一個完整的Message默認條件:函數調用前后,當前流的第一個字節為新的Chunk的開始字節如果packet讀取完畢,通過packet可以獲取到相關的數據如果packet尚未讀取完畢,不能通過packet獲取到相關的數據,但可以獲取相關的情況 */ int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet);/*將msg拆分為chunk后,寫入chunk1 分析msg所使用的chunk的type2 拆分msg為chunk 3 當msg為command msg時,處理相關字段底層調用WriteN()函數,而不是RTMP_SendPacket()函數 */ int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue);/*直接寫入chunk,但rtmp庫中并沒有使用這個函數,建議用戶也不要使用內容:僅僅是寫入chunk底層調用WriteN()函數 */ int RTMP_SendChunk(RTMP *r, RTMPChunk *chunk)2.3 業務層
? 在Message讀寫層之上,業務層對其進行了相關且必要的業務處理,具體包括:
- 對服務器發送的非media Message的處理
- 過濾非media Message
- 處理seek操作
- 處理resume操作
- 過濾尺寸過小的video和audio rtmp Message
? 這樣,經過業務層后,返回的就是用戶想要獲取的媒體數據和元對象數據了。這里所謂的“用戶想要獲取”,包含了用戶指定的seek和resume操作。
/* Read from the stream until we get a media packet.* Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media* packets, 0 if ignorable error, >0 if there is a media packet** 從流中讀取數據,直到獲取到一個媒體packet* 什么是媒體packet,請查看RTMP_GetNextMediaPacket()函數* 返回值:表示當前RTMP流的狀態,即RTMP_READ.status,如下:* 有可忽略的錯誤返回0,讀取到了媒體packet返回正值** 作用:處理設置后的RTMP連接,并返回媒體RTMPPacket** 1 處理seek* 2 處理resume* 3 過濾非media rtmp packet(通過調用 RTMP_GetNextMediaPacket() 完成)* 4 過濾尺寸太小的video audio rtmppacket*/ static int Read_1_Packet(RTMP *r, char *buf, unsigned int buflen);/*獲取一個Media message忽略不是Media Message的Message,直到獲取media message。這個函數會調用RTMP_ReadPacket函數讀取一個Message,然后調用RTMP_ClientPacket處理讀取到的Message。什么是media Message,詳情查看RTMP_ClientPacket */ int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet)/*客戶端處理Message返回值:1表示packet為audio,video,metadata message(object msg中的一種),聚合 message2表示packet為command message,且為Play.Complete或Play.Stop等表示流暫停或結束0表示其他message這個函數通過調用下面的handle message塊,完成對各種類型Message的調用當返回值大于0時,表示這個packet是一個media packet,即這個packet真的包含媒體數據,或者是一些表示媒體已經結束或暫停,暫時不可能獲取到真的媒體數據,因此返回,防止邏輯卡死在這里。 */ int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet)2.4 FLV讀寫層
? rtmp流經過業務層處理后,就剩下媒體數據和元對象數據了,但這些數據仍然是以Message形式存在的,而有時,我們需要以字節流的形式,即flv格式的字節流,來保存媒體數據,因此就有了FLV讀寫層,其作用如下:
- Read時,將Message格式的媒體數據轉化為為FLV TAG格式;Write時,相反
- Read時,補全FLV Header部分;Write時,跳過FLV Header部分
? 這也是為什么上述第一個rtmp示例,將rtmp流保存為flv文件,使用FLV讀寫層接口的原因;而且在第二個示例中,也有分別使用FLV讀寫層和Message讀寫層API的不同示例,其最大的不同就在于一個是FLV TAG形式的字節流形式,一個是RTMPPacket結構的Message形式。
/*讀取數據,FLV層級返回的值為一個FLV數據流,正如RTMP_Write寫入一個FLV數據流1 完成 FLV TAG ==> RTMPPacket,RTMP_Write則相反2 補全FLV數據流,如加入FLV Header返回值:0 表示已經完成讀取RTMP流-1 表示發生了錯誤>0 表示實際讀取的字節數 */ int RTMP_Read(RTMP *r, char *buf, int size)/*依次寫入FLV文件的一部分。注意:一般不能遺漏FLV文件的任何部分。當然,如果想要忽略一部分數據,那么可以忽略【FLVHeader+第一個preTagSize】或者任意的【FLVTAG+preTagSize】實際寫入的部分為:FLV TAG,其他的部分會被忽略,其中TAGHeader會被解析到Msg Header,TAGBody為Msg Body。RTMP_Write將傳入的數據封裝為RTMPPacket,然后調用RTMP_SendPacket()函數發送。返回值:成功返回實際寫入的字節數。失敗返回-1。 */ int RTMP_Write(RTMP *r, const char *buf, int size)2.5 其他:處理Message以及發送Message
? 在上面的業務層中,當讀取到一個Message之后,會調用RTMP_ClientPacket()來處理讀取到的Message,在這個函數內部,根據Message的類型會調用不同的處理函數,也就是下面形為 “Handle***()”的函數。
? 請注意,這里這些處理函數的命名方式,它的命名說明了這些函數都是框架內部的函數,而不是對外的接口。
? 如果對rtmp協議感興趣,可以詳細查看這些函數的內容,來印證學習相關的協議內容。
static int HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize); static int HandleMetadata(RTMP *r, char *body, unsigned int len); static void HandleChangeChunkSize(RTMP *r, const RTMPPacket *packet); static void HandleAudio(RTMP *r, const RTMPPacket *packet); static void HandleVideo(RTMP *r, const RTMPPacket *packet); static void HandleCtrl(RTMP *r, const RTMPPacket *packet); static void HandleServerBW(RTMP *r, const RTMPPacket *packet); static void HandleClientBW(RTMP *r, const RTMPPacket *packet);? 與上面相對應的,當我們想要對rtmp流做一定的修改時,就需要往對方發送Message數據包。具體發送數據包的接口如下,它們都是形如 “Send***()” 或者形如 “RTMP_Send***()” 的函數,當然在librtmp中,還有其他構筑在這些具體發送Message數據包的接口之上的其他接口,當我們遇到時,應該能改輕易分辨出來。
? 同上,如果對rtmp協議的具體內容感興趣,可以取查看這些函數的細節。
static int SendConnectPacket(RTMP *r, RTMPPacket *cp); static int SendCheckBW(RTMP *r); static int SendCheckBWResult(RTMP *r, double txn); static int SendDeleteStream(RTMP *r, double dStreamId); static int SendReleaseStream(RTMP *r); static int SendFCPublish(RTMP *r); static int SendPublish(RTMP *r); static int SendFCUnpublish(RTMP *r); static int SendFCSubscribe(RTMP *r, AVal *subscribepath); static int SendPlay(RTMP *r); static int SendBytesReceived(RTMP *r); int RTMP_SendCreateStream(RTMP *r); int RTMP_SendPause(RTMP *r, int DoPause, int iTime) int RTMP_SendSeek(RTMP *r, int iTime); int RTMP_SendServerBW(RTMP *r); int RTMP_SendClientBW(RTMP *r); int RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject, unsigned int nTime);3.rtmpdump示例
? 這個示例全方位展示了如何使用librtmp作為客戶端使用,如果不知道如何使用librtmp,可以參考這個示例。
4. 參考文章
? 本文僅是一個提綱類的文檔,遠遠沒有將librtmp的各個細節講解清除,讀者可以去參考其他人的相關文章,其中雷神的librtmp相關的系列文章非常詳細,是不可多得的好資料,地址如下:https://blog.csdn.net/leixiaohua1020/article/details/15814587。
5. 注釋代碼
? 以下我注釋的rtmp.h文件:
#ifndef __RTMP_H__ #define __RTMP_H__ /** Copyright (C) 2005-2008 Team XBMC* http://www.xbmc.org* Copyright (C) 2008-2009 Andrej Stepanchuk* Copyright (C) 2009-2010 Howard Chu** This file is part of librtmp.** librtmp is free software; you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License as* published by the Free Software Foundation; either version 2.1,* or (at your option) any later version.** librtmp is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU Lesser General Public License* along with librtmp see the file COPYING. If not, write to* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,* Boston, MA 02110-1301, USA.* http://www.gnu.org/copyleft/lgpl.html*/#if !defined(NO_CRYPTO) && !defined(CRYPTO) #define CRYPTO #endif#include <errno.h> #include <stdint.h> #include <stddef.h>#include "amf.h"#ifdef __cplusplus extern "C" { #endif//這個庫的版本號 #define RTMP_LIB_VERSION 0x020300 /* 2.3 *///rtmp的特征 FEATURE #define RTMP_FEATURE_HTTP 0x01 #define RTMP_FEATURE_ENC 0x02 #define RTMP_FEATURE_SSL 0x04 #define RTMP_FEATURE_MFP 0x08 /* not yet supported */ #define RTMP_FEATURE_WRITE 0x10 /* publish, not play */ //推流而非播放 #define RTMP_FEATURE_HTTP2 0x20 /* server-side rtmpt */ //服務器端的rtmpt//rtmp及其變種的協議類型,本質為上述FEATURE的組合 #define RTMP_PROTOCOL_UNDEFINED -1 #define RTMP_PROTOCOL_RTMP 0 #define RTMP_PROTOCOL_RTMPE RTMP_FEATURE_ENC #define RTMP_PROTOCOL_RTMPT RTMP_FEATURE_HTTP #define RTMP_PROTOCOL_RTMPS RTMP_FEATURE_SSL #define RTMP_PROTOCOL_RTMPTE (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC) #define RTMP_PROTOCOL_RTMPTS (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL) #define RTMP_PROTOCOL_RTMFP RTMP_FEATURE_MFP//默認的max chunk size #define RTMP_DEFAULT_CHUNKSIZE 128//SocketBuffer層面接收緩沖區的尺寸 /* needs to fit largest number of bytes recv() may return */ #define RTMP_BUFFER_CACHE_SIZE (16*1024)//chunk Stream id所能表達的最大值 #define RTMP_CHANNELS 65600//rtmp及其變種的協議類型的字符串名稱,小寫 //還有一個對應的大寫的版本,在rtmp.c中定義,不對用戶開放 extern const char RTMPProtocolStringsLower[][7]; //默認的Flash版本號字符串描述 extern const AVal RTMP_DefaultFlashVer; //表示ctrl-c中斷機制 extern int RTMP_ctrlC;//獲取當前時間 uint32_t RTMP_GetTime(void);//Message Type ID #define RTMP_PACKET_TYPE_AUDIO 0x08 //audio message #define RTMP_PACKET_TYPE_VIDEO 0x09 //video message #define RTMP_PACKET_TYPE_INFO 0x12 //command message//chunk header由三部分組成,各個部分的尺寸均可變化 //其最大尺寸時各個部分的字節為:3+11+4 #define RTMP_MAX_HEADER_SIZE 18//chunk header的第一部分basic header中的fmt取值 #define RTMP_PACKET_SIZE_LARGE 0 #define RTMP_PACKET_SIZE_MEDIUM 1 #define RTMP_PACKET_SIZE_SMALL 2 #define RTMP_PACKET_SIZE_MINIMUM 3/*表示一個 raw chunk,原始chunkc_header,c_headerSize:保存chunk header的數據和大小c_chunk,c_chunkSize:保存chunk data的數據和大小 */ typedef struct RTMPChunk {int c_headerSize;int c_chunkSize;char *c_chunk;char c_header[RTMP_MAX_HEADER_SIZE]; } RTMPChunk;/*表示一個Messagem_headerType :表示m_chunk的類型,即chunk header中的basic header中的fmtm_packetType :表示Message Type IDm_hasAbsTimestamp :表示時間戳是絕對的還是相對的,即chunk type為0時為絕對時間戳,其他類型時為時間戳增量m_nChannel :表示chunk Stream IDm_nTimeStamp :時間戳m_nInfoField2 :chunk fmt為0時,header的最后四個字節,即Message Stream IDm_nBodySize :Message的body的尺寸m_nBytesRead :已經讀取到的body的字節數m_chunk :如果不為NULL,表示用戶想要獲取chunk,那么在讀取Message時,會填充這個字段m_body :Message的body */ typedef struct RTMPPacket {uint8_t m_headerType;uint8_t m_packetType;uint8_t m_hasAbsTimestamp; /* timestamp absolute or relative? */int m_nChannel;uint32_t m_nTimeStamp; /* timestamp */int32_t m_nInfoField2; /* last 4 bytes in a long header */uint32_t m_nBodySize;uint32_t m_nBytesRead;RTMPChunk *m_chunk;char *m_body; } RTMPPacket;/*RTMPSockBuf:RTMP的傳輸層的套接字及其緩存表示一個TCP套接字連接,以及其讀取緩存sb_socket :Socket套接字sb_size :buffer中未處理的字節數量,即緩沖數據的大小sb_start :指向buffer中需要處理的字節,即指向緩沖數據sb_buf :數據讀取緩沖區sb_timedout :套接字是否中斷sb_ssl :SSL相關數據 */ typedef struct RTMPSockBuf {int sb_socket;int sb_size; /* number of unprocessed bytes in buffer */char *sb_start; /* pointer into sb_pBuffer of next byte to process */char sb_buf[RTMP_BUFFER_CACHE_SIZE]; /* data read from socket */int sb_timedout;void *sb_ssl; } RTMPSockBuf;//將全部字段置空(緩沖區仍在) void RTMPPacket_Reset(RTMPPacket *p); //打印packet void RTMPPacket_Dump(RTMPPacket *p); //為p分配緩沖區,其中包括固定大小的header,nSize大小的body int RTMPPacket_Alloc(RTMPPacket *p, int nSize); //釋放緩沖區 void RTMPPacket_Free(RTMPPacket *p);//packet是否完備 #define RTMPPacket_IsReady(a) ((a)->m_nBytesRead == (a)->m_nBodySize)/*RTMP的連接參數,即要建立RTMP連接所需的參數集注意:這是由客戶端的用戶提供的這個結構體里的字段的含義和rtmpdump中的選項聯系緊密。可以查看rtmpdump中選項的含義來幫助我們理解它們。 */ typedef struct RTMP_LNK {AVal hostname; //要連接的服務器的主機名AVal sockshost; //代理主機名稱/* parsed from URL */AVal playpath0; //從URL解析出來的playpath/* passed in explicitly */AVal playpath; //顯式指定的playpathAVal tcUrl; //要連接的目標流的URL。默認值為:rtmp[e]://host[:port]/app/playpath ,由解析出的各個字段值拼接而成。AVal swfUrl; //媒體的SWF播放器的URL,默認不設置任何值AVal pageUrl; //嵌入網頁的媒體的URL,默認不設置任何值AVal app; //要連接的服務器上的應用程序AVal auth; //驗證字符串,它會追加到Connect Message的末尾。使用這個選項,//實際上將會追加一個布爾值TRUE然后才是這個驗證字符串。只有某些特殊的服務器需要,而且已經被廢棄。AVal flashVer; //用于運行SWF播放器的Flash插件的版本。默認為“LUX 10,0,32,18"AVal subscribepath; //要訪問的流的名稱AVal token; //SecureToken Response中要使用的key。當服務器需要一個SecureToken驗證時使用AMFObject extras;int edepth;int seekTime; //訪問媒體時的偏移時間int stopTime; //訪問媒體的停止時間#define RTMP_LF_AUTH 0x0001 /* using auth param */ //是否使用驗證參數 #define RTMP_LF_LIVE 0x0002 /* stream is live */ //請求的流是否為實時流 #define RTMP_LF_SWFV 0x0004 /* do SWF verification */ //是否要執行SWF驗證 #define RTMP_LF_PLST 0x0008 /* send playlist before play */ //是否在發送play之間發送playlist #define RTMP_LF_BUFX 0x0010 /* toggle stream on BufferEmpty msg */ //是否在BufferEmpty msg時toggle stream #define RTMP_LF_FTCU 0x0020 /* free tcUrl on close */ //close時是否釋放tcUrlint lFlags; //復合標識int swfAge; //指定緩存的SWF信息的有效天數,超過這個天數后,將重新檢查int protocol; //服務器的rtmp協議類型/* connection timeout in seconds */int timeout; //連接timeout的時間值unsigned short socksport; //代理主機的端口unsigned short port; //服務器的端口//加密相關的字段 #ifdef CRYPTO #define RTMP_SWF_HASHLEN 32void *dh; /* for encryption */void *rc4keyIn;void *rc4keyOut;uint32_t SWFSize;uint8_t SWFHash[RTMP_SWF_HASHLEN];char SWFVerificationResponse[RTMP_SWF_HASHLEN+10]; #endif } RTMP_LNK;//RTMP業務層,即建立rtmp流之后對rtmp流做必要操作所需的參數 //必要操作如:seek操作,resume操作 /* state for read() wrapper */ typedef struct RTMP_READ {char *buf; //指向讀取緩沖區char *bufpos; //指向未處理數據的指針unsigned int buflen; //未處理數據的大小uint32_t timestamp; //RTMP流的當前時間戳uint8_t dataType; //RTMP流的數據類型,即是否包含音頻數據和視頻數據 0x04為音頻 0x01為視頻,使用的是flv的表示法uint8_t flags; //解析flag,包含以下幾個值 #define RTMP_READ_HEADER 0x01 //表示是否在當前rtmp流的開頭中插入flv header,默認不會設置這個狀態,實際開始讀取時插入對應的flv header,然后設置它,表示已添加flv header #define RTMP_READ_RESUME 0x02 //表示是否要進行resume #define RTMP_READ_NO_IGNORE 0x04 // #define RTMP_READ_GOTKF 0x08 //表示是否完成了resume #define RTMP_READ_GOTFLVK 0x10 // #define RTMP_READ_SEEKING 0x20 //表示是否要執行seek操作int8_t status; //讀取的當前狀態,表示當前的流的分析結果,為以下四個取值,為0表示正常 #define RTMP_READ_COMPLETE -3 //Close或者Stop狀態,表示讀取完成 #define RTMP_READ_ERROR -2 //致命錯誤 #define RTMP_READ_EOF -1 //EOF #define RTMP_READ_IGNORE 0 //可忽略錯誤/* if bResume == TRUE */ //resume時需要指定的字段,用于幫助流定義resume的位置uint8_t initialFrameType; //定位的幀的類型,即是視頻幀還是音頻幀uint32_t nResumeTS; //定位的幀的時間戳char *metaHeader; //要resume的流的metedata數據char *initialFrame; //定位的幀的datauint32_t nMetaHeaderSize; //要resume的流的metadata數據的尺寸uint32_t nInitialFrameSize; //定位的幀的data lengthuint32_t nIgnoredFrameCounter;uint32_t nIgnoredFlvFrameCounter; } RTMP_READ;//method:方法 //表示RTMP的方法或函數 typedef struct RTMP_METHOD {AVal name;int num; } RTMP_METHOD;//表示一個RTMP流,用于保存這個RTMP流的相關參數 typedef struct RTMP {int m_inChunkSize; //接收方向的max chunk sizeint m_outChunkSize;int m_nBWCheckCounter;int m_nBytesIn; //接受到的字節的總數量int m_nBytesInSent; //發送的字節的總數量int m_nBufferMS;int m_stream_id; /* returned in _result from createStream */ //Message Stream IDint m_mediaChannel; //當前media使用的chunk Stream iduint32_t m_mediaStamp; //當前media的時間戳uint32_t m_pauseStamp;int m_pausing;int m_nServerBW; //window sizeint m_nClientBW; //Set Peer Bandwidth Message中的window sizeuint8_t m_nClientBW2; //Set Peer Bandwidth Message中的limit typeuint8_t m_bPlaying; //當前是否playuint8_t m_bSendEncoding;uint8_t m_bSendCounter;int m_numInvokes; //記錄RTMP發起的invoke的數量int m_numCalls; //m_methodCalls中的數量/* remote method calls queue */RTMP_METHOD *m_methodCalls; //request列表RTMPPacket *m_vecChannelsIn[RTMP_CHANNELS]; //輸入的chunk Stream對應的最后一個packetRTMPPacket *m_vecChannelsOut[RTMP_CHANNELS]; //輸出的chunk stream對應的最后一個packet/* abs timestamp of last packet */int m_channelTimestamp[RTMP_CHANNELS]; //最后一個packet對應的絕對時間戳/* audioCodecs for the connect packet */double m_fAudioCodecs; //Connect msg中的音頻編解碼/* videoCodecs for the connect packet */double m_fVideoCodecs; //Connect msg中的視頻編解碼/* AMF0 or AMF3 */double m_fEncoding; //使用AMF0還是AMF3/* duration of stream in seconds */double m_fDuration; //當前流的長度,單位秒/* RTMPT stuff */ //以下是RTMPT相關的成員變量:int m_msgCounter; //int m_polling; //int m_resplen; //HTTP Response的body長度,表示是否讀取到了HTTP Response,但數據保存在SB中int m_unackd; //表示未應答的HTTP request數量(一般,沒一個request必有一個response)AVal m_clientID; //底層為HTTP時,用于表示客戶端IDRTMP_READ m_read; //RTMP層面的讀RTMPPacket m_write; //RTMP層面write時的臨時msg對象RTMPSockBuf m_sb; //套接字層面的讀寫RTMP_LNK Link; //RTMP連接的參數 } RTMP;//解析RTMP地址 int RTMP_ParseURL(const char *url, int *protocol, AVal *host,unsigned int *port, AVal *playpath, AVal *app); //從RTMP URL中解析playpath void RTMP_ParsePlaypath(AVal *in, AVal *out); //設置緩沖的時長,單位:毫秒 void RTMP_SetBufferMS(RTMP *r, int size); //發送“設置緩沖時長”的控制包 void RTMP_UpdateBufferMS(RTMP *r);//設置RTMP的相關屬性 int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);//設置url int RTMP_SetupURL(RTMP *r, char *url);//設置流 void RTMP_SetupStream(RTMP *r, int protocol, //協議AVal *hostname, //hostnameunsigned int port, //portAVal *sockshost,AVal *playpath,AVal *tcUrl,AVal *swfUrl,AVal *pageUrl,AVal *app,AVal *auth,AVal *swfSHA256Hash,uint32_t swfSize,AVal *flashVer,AVal *subscribepath,int dStart,int dStop, int bLiveStream, long int timeout);//客戶端與服務器建立連接,并進行Connect int RTMP_Connect(RTMP *r, RTMPPacket *cp); struct sockaddr; int RTMP_Connect0(RTMP *r, struct sockaddr *svc); int RTMP_Connect1(RTMP *r, RTMPPacket *cp);//服務端處理handshake,成功返回值大于0 int RTMP_Serve(RTMP *r);//RTMP讀取到一個packet,并不保證一定讀取到完整的packet int RTMP_ReadPacket(RTMP *r, RTMPPacket *packet); //使用RTMP發送packet,queue表示是否按照隊列發送 int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue); //RTMP發送一個chunk int RTMP_SendChunk(RTMP *r, RTMPChunk *chunk); //RTMP是否處于連接狀態 int RTMP_IsConnected(RTMP *r); //返回RTMP關聯的套接字 int RTMP_Socket(RTMP *r); //RTMP是否中斷 int RTMP_IsTimedout(RTMP *r); //獲得RTMP當前流的長度 double RTMP_GetDuration(RTMP *r);//暫停或者恢復流 int RTMP_ToggleStream(RTMP *r);//連接到url指定的流,返回TRUE和FLASE int RTMP_ConnectStream(RTMP *r, int seekTime); //重新連接流,即delete Stream之后再次Connect Stream,且同步等待到流開始 int RTMP_ReconnectStream(RTMP *r, int seekTime); //停止流,或者刪除流 void RTMP_DeleteStream(RTMP *r);//獲取一個媒體packet,即音頻幀或者視頻幀(在RTMP_ReadPacket函數之上實現) int RTMP_GetNextMediaPacket(RTMP *r, RTMPPacket *packet);//客戶端處理packet int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet);//初始化RTMP,將RTMP各個字段設置為默認值 void RTMP_Init(RTMP *r); //關閉這個RTMP,如果當前處于連接狀態,則會關閉連接;然后釋放RTMP相關的內容 void RTMP_Close(RTMP *r); //分配一個RTMP //其操作僅僅是分配一塊RTMP大小的內存而已,RTMP也可以在棧上分配 RTMP *RTMP_Alloc(void); //釋放這塊RTMP大小的內存 void RTMP_Free(RTMP *r);//為RTMP Link添加寫屬性 void RTMP_EnableWrite(RTMP *r); //返回RTMP庫的版本號 int RTMP_LibVersion(void); //RTMP流程被打斷 //本質為設置全局變量RTMP_ctrlC為true void RTMP_UserInterrupt(void); /* user typed Ctrl-C */int RTMP_SendCtrl(RTMP *r, short nType, unsigned int nObject,unsigned int nTime);/* caller probably doesn't know current timestamp, should* just use RTMP_Pause instead*/ int RTMP_SendPause(RTMP *r, int DoPause, int dTime); int RTMP_Pause(RTMP *r, int DoPause);int RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name,AMFObjectProperty * p);//TCP層面的操作:讀、寫、關閉 //在TCP層面讀取,返回讀取到的字節數 int RTMPSockBuf_Fill(RTMPSockBuf *sb); //在TCP層面寫入,返回寫入的返回值 int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len); //關閉套接字,返回關閉的返回值 int RTMPSockBuf_Close(RTMPSockBuf *sb);// int RTMP_SendCreateStream(RTMP *r); int RTMP_SendSeek(RTMP *r, int dTime); int RTMP_SendServerBW(RTMP *r); int RTMP_SendClientBW(RTMP *r); //丟棄第i個request //freeit表示是否釋放這個request void RTMP_DropRequest(RTMP *r, int i, int freeit);//FLV層面 int RTMP_Read(RTMP *r, char *buf, int size); int RTMP_Write(RTMP *r, const char *buf, int size);/* hashswf.c */ int RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,int age);#ifdef __cplusplus }; #endif#endif總結
以上是生活随笔為你收集整理的librtmp库API介绍及其结构概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用腾讯云短信SDK发送验证码
- 下一篇: 如何将Python程序打包成linux可