【原】librtmp源码详解
1. librtmp概述
实时流协议(Real-TimeMessaging Protocol,RTMP)是用于互联网上传输视音频数据的网络协议。本API提供了支持RTMP, RTMPT,RTMPE,RTMPS及以上几种协议的变种(RTMPTE, RTMPTS)协议所需的大部分客户端功能以及少量的服务器功能。尽管Adobe公司已经公布了RTMP协议规范(RTMP specification),但是本工程并不是通过Adobe的协议规范而是通过逆向工程的方式完成的。因此,它的运行方式可能和公布的协议规范有所偏离,但是一般情况下它和Adobe的客户端的运行方式是一模一样的。
本博客将对libRTMP的函数进行简要说明。 这些函数可以在 -lrtmp 库中找到。其他还有很多函数,但是还没有为这些函数写文档。
基本的操作如下文所述:
RTMP_Alloc() :用于创建一个RTMP的结构体。
RTMP_Init():初始化RTMP结构体。
RTMP_SetupURL():设置推拉流的URL。
RTMP_EnableWrite(): 是否推流。
RTMP_Connect():建立RTMP链接中的网络连接(NetConnection)。
RTMP_ConnectStream():建立RTMP链接中的网络流(NetStream)。
RTMP_Read():读取RTMP流的内容。
RTMP_Pause():流播放的时候可以用于暂停和继续。
RTMP_Seek():改变流播放的位置。
当RTMP_Read()返回0 字节的时候,代表流已经读取完毕,而后可以调用RTMP_Close()。
RTMP_Free():用于释放RTMP结构体。
所有的数据都使用 FLV 格式进行传输。一个基本的会话需要一个RTMP URL。RTMP URL 格式如下所示:
rtmp[t][e|s]://hostname[:port][/app[/playpath]]
2. librtmp流程图
本流程主要为推流示意图,拉流与此大同小异。
3. 源码剖析
源码剖析的建议:
a. 先了解RTMP协议内容或者结合代码一块理解,详情可查阅: http://www.cnblogs.com/Kingfans/p/7083100.html;
b. 先了解AMF编码机制,详情课查阅:http://www.cnblogs.com/Kingfans/p/7069542.html;
c. 发包过程中请配合wireshark抓包工具一块理解。
3.1 RTMP_Alloc
RTMP_Alloc()用于创建一个RTMP的结构体。
/************************************************************************************************************ * @brief 申请RTMP内存 * ************************************************************************************************************/ RTMP* RTMP_Alloc() { return calloc(1, sizeof(RTMP)); }
3.2 RTMP_Init
RTMP_Init():初始化RTMP结构体。
/************************************************************************************************************ * @brief 初始化RTMP * ************************************************************************************************************/ void RTMP_Init(RTMP *r) { #ifdef CRYPTO if (!RTMP_TLS_ctx) RTMP_TLS_Init(); #endif memset(r, 0, sizeof(RTMP)); r->m_sb.sb_socket = -1; r->m_inChunkSize = RTMP_DEFAULT_CHUNKSIZE; r->m_outChunkSize = RTMP_DEFAULT_CHUNKSIZE; r->m_bSendChunkSizeInfo = 1; r->m_nBufferMS = 30000; r->m_nClientBW = 2500000; r->m_nClientBW2 = 2; r->m_nServerBW = 2500000; r->m_fAudioCodecs = 3191.0; r->m_fVideoCodecs = 252.0; r->Link.timeout = 30; r->Link.swfAge = 30; }
3.3 RTMP_SetupURL
RTMP_SetupURL():设置推拉流的URL。内部调用了RTMP_ParseURL主要用于解析url,详见3.3.1。
/************************************************************************************************************ * @brief 设置推流: 地址url; * ************************************************************************************************************/ int RTMP_SetupURL(RTMP *r, char *url) { AVal opt, arg; char *p1, *p2, *ptr = strchr(url, ' '); int ret, len; unsigned int port = 0; if (ptr) { *ptr = '\0'; } len = (int)strlen(url); // 将url解析后设置到r; ret = RTMP_ParseURL(url, &r->Link.protocol, &r->Link.hostname, &port, &r->Link.playpath0, &r->Link.app); if (!ret) { return ret; } r->Link.port = port; r->Link.playpath = r->Link.playpath0; while (ptr) { *ptr++ = '\0'; p1 = ptr; p2 = strchr(p1, '='); if (!p2) { break; } opt.av_val = p1; opt.av_len = p2 - p1; *p2++ = '\0'; arg.av_val = p2; ptr = strchr(p2, ' '); if (ptr) { *ptr = '\0'; arg.av_len = ptr - p2; /* skip repeated spaces */ while (ptr[1] == ' ') { *ptr++ = '\0'; } } else { arg.av_len = (int)strlen(p2); } /* unescape */ port = arg.av_len; for (p1=p2; port >0;) { if (*p1 == '\\') { unsigned int c; if (port < 3) { return FALSE; } sscanf(p1+1, "%02x", &c); *p2++ = c; port -= 3; p1 += 3; } else { *p2++ = *p1++; port--; } } arg.av_len = p2 - arg.av_val; ret = RTMP_SetOpt(r, &opt, &arg); if (!ret) { return ret; } } if (!r->Link.tcUrl.av_len) { r->Link.tcUrl.av_val = url; if (r->Link.app.av_len) { if (r->Link.app.av_val < url + len) { /* if app is part of original url, just use it */ r->Link.tcUrl.av_len = r->Link.app.av_len + (r->Link.app.av_val - url); } else { len = r->Link.hostname.av_len + r->Link.app.av_len + sizeof("rtmpte://:65535/"); r->Link.tcUrl.av_val = malloc(len); r->Link.tcUrl.av_len = snprintf(r->Link.tcUrl.av_val, len, "%s://%.*s:%d/%.*s", RTMPProtocolStringsLower[r->Link.protocol], r->Link.hostname.av_len, r->Link.hostname.av_val, r->Link.port, r->Link.app.av_len, r->Link.app.av_val); r->Link.lFlags |= RTMP_LF_FTCU; } } else { r->Link.tcUrl.av_len = (int)strlen(url); } } #ifdef CRYPTO if ((r->Link.lFlags & RTMP_LF_SWFV) && r->Link.swfUrl.av_len) RTMP_HashSWF(r->Link.swfUrl.av_val, &r->Link.SWFSize, (unsigned char *)r->Link.SWFHash, r->Link.swfAge); #endif SocksSetup(r, &r->Link.sockshost); if (r->Link.port == 0) { if (r->Link.protocol & RTMP_FEATURE_SSL) { r->Link.port = 443; } else if (r->Link.protocol & RTMP_FEATURE_HTTP) { r->Link.port = 80; } else { r->Link.port = 1935; } } return TRUE; }
3.3.1 RTMP_ParseURL
RTMP_ParseURL用于解析url,就是将url字符串内的协议类型、主机名称、端口号、appname、playpath解析出来。
int RTMP_ParseURL(const char *url, int *protocol, AVal* host, unsigned int *port, AVal* playpath, AVal* app) { char *p, *end, *col, *ques, *slash; RTMP_Log(RTMP_LOGDEBUG, "RTMP_ParseURL"); *protocol = RTMP_PROTOCOL_RTMP; *port = 0; playpath->av_len = 0; playpath->av_val = NULL; app->av_len = 0; app->av_val = NULL; /* Old School Parsing */ /* look for usual :// pattern */ p = strstr(url, "://"); if(!p) { RTMP_Log(RTMP_LOGERROR, "RTMP URL: No :// in url!"); return FALSE; } { int len = (int)(p-url); if (len == 4 && strncasecmp(url, "rtmp", 4) == 0) { *protocol = RTMP_PROTOCOL_RTMP; } else if (len == 5 && strncasecmp(url, "rtmpt", 5) == 0) { *protocol = RTMP_PROTOCOL_RTMPT; } else if (len == 5 && strncasecmp(url, "rtmps", 5) == 0) { *protocol = RTMP_PROTOCOL_RTMPS; } else if (len == 5 && strncasecmp(url, "rtmpe", 5) == 0) { *protocol = RTMP_PROTOCOL_RTMPE; } else if (len == 5 && strncasecmp(url, "rtmfp", 5) == 0) { *protocol = RTMP_PROTOCOL_RTMFP; } else if (len == 6 && strncasecmp(url, "rtmpte", 6) == 0) { *protocol = RTMP_PROTOCOL_RTMPTE; } else if (len == 6 && strncasecmp(url, "rtmpts", 6) == 0) { *protocol = RTMP_PROTOCOL_RTMPTS; } else { RTMP_Log(RTMP_LOGWARNING, "Unknown protocol!\n"); goto parsehost; } } RTMP_Log(RTMP_LOGDEBUG, "Parsed protocol: %d", *protocol); parsehost: /* let's get the hostname */ p+=3; /* check for sudden death */ if(*p==0) { RTMP_Log(RTMP_LOGWARNING, "No hostname in URL!"); return FALSE; } end = p + strlen(p); col = strchr(p, ':'); ques = strchr(p, '?'); slash = strchr(p, '/'); { int hostlen; if (slash) { hostlen = slash - p; } else { hostlen = end - p; } if (col && col - p < hostlen) { hostlen = col - p; } if(hostlen < 256) { host->av_val = p; host->av_len = hostlen; RTMP_Log(RTMP_LOGDEBUG, "Parsed host : %.*s", hostlen, host->av_val); } else { RTMP_Log(RTMP_LOGWARNING, "Hostname exceeds 255 characters!"); } p+=hostlen; } /* get the port number if available */ if(*p == ':') { unsigned int p2; p++; p2 = atoi(p); if(p2 > 65535) { RTMP_Log(RTMP_LOGWARNING, "Invalid port number!"); } else { *port = p2; } } if(!slash) { RTMP_Log(RTMP_LOGWARNING, "No application or playpath in URL!"); return TRUE; } p = slash+1; { /* parse application * * rtmp://host[:port]/app[/appinstance][/...] * application = app[/appinstance] */ char *slash2, *slash3 = NULL, *slash4 = NULL; int applen, appnamelen; slash2 = strchr(p, '/'); if (slash2) { slash3 = strchr(slash2+1, '/'); } if (slash3) { slash4 = strchr(slash3+1, '/'); } applen = end-p; /* ondemand, pass all parameters as app */ appnamelen = applen; /* ondemand length */ if(ques && strstr(p, "slist=")) /* whatever it is, the '?' and slist= means we need to use everything as app and parse plapath from slist= */ { appnamelen = ques-p; } else if(strncmp(p, "ondemand/", 9)==0) { /* app = ondemand/foobar, only pass app=ondemand */ applen = 8; appnamelen = 8; } else /* app!=ondemand, so app is app[/appinstance] */ { if (slash4) { appnamelen = slash4-p; } else if (slash3) { appnamelen = slash3-p; } else if (slash2) { appnamelen = slash2-p; } applen = appnamelen; } app->av_val = p; app->av_len = applen; RTMP_Log(RTMP_LOGDEBUG, "Parsed app : %.*s", applen, p); p += appnamelen; } if (*p == '/') { p++; } if (end-p) { AVal av = {p, end-p}; RTMP_ParsePlaypath(&av, playpath); } return TRUE; }
3.4 RTMP_EnableWrite
RTMP_EnableWrite()设置为推流状态。
/************************************************************************************************************ * @brief 是否进行推流; * ************************************************************************************************************/ void RTMP_EnableWrite(RTMP *r) { r->Link.protocol |= RTMP_FEATURE_WRITE; }
3.5 RTMP_Connect
RTMP_Connect():建立RTMP链接中的网络连接(NetConnection)。主要分为RTMP_Connect0(3.5.1)+ RTMP_Connect1(3.5.2)。
/************************************************************************************************************ * @brief 建立RTMP中的NetConnection * * @return 成功返回TRUE, 否则返回FALSE. ************************************************************************************************************/ int RTMP_Connect(RTMP *r, RTMPPacket *cp) { // Socket结构体; struct sockaddr_storage service; if (!r->Link.hostname.av_len) { return FALSE; } // COMODO security software sandbox blocks all DNS by returning "host not found" HOSTENT *h = gethostbyname("localhost"); if (!h && GetLastError() == WSAHOST_NOT_FOUND) { RTMP_Log(RTMP_LOGERROR, "RTMP_Connect: Connection test failed. This error is likely caused by Comodo Internet Security running OBS in sandbox mode. Please add OBS to the Comodo automatic sandbox exclusion list, restart OBS and try again (11001)."); return FALSE; } memset(&service, 0, sizeof(service)); if (r->Link.socksport) { // 加入地址信息, 使用SOCKS连接; if (!add_addr_info(&service, &r->Link.sockshost, r->Link.socksport)) { return FALSE; } } else { // 直接连接; if (!add_addr_info(&service, &r->Link.hostname, r->Link.port)) { return FALSE; } } RTMP_Log(RTMP_LOGDEBUG, "%s, 建立连接:第0次连接。开始建立Socket连接", __FUNCTION__); // RTMP_Connect0()主要用于建立Socket连接,并未开始真正的建立RTMP连接; if (!RTMP_Connect0(r, (struct sockaddr *)&service)) { RTMP_Log(RTMP_LOGDEBUG, "%s, 建立连接:第0次连接。建立Socket连接失败", __FUNCTION__); return FALSE; } RTMP_Log(RTMP_LOGDEBUG, "%s, 建立连接:第0次连接。建立Socket连接成功", __FUNCTION__); r->m_bSendCounter = TRUE; // 真正建立RTMP连接的函数; return RTMP_Connect1(r, cp); }
3.5.1 RTMP_Connect0
RTMP_Connect0():第0次连接,建立socket连接。
/************************************************************************************************************ * @brief 第0次连接,建立Socket连接 * * @return 成功返回TRUE, 否则返回FALSE. ************************************************************************************************************/ int RTMP_Connect0(RTMP *r, struct sockaddr * service) { int on = 1; r->m_sb.sb_timedout = FALSE; r->m_pausing = 0; r->m_fDuration = 0.0; // 创建一个Socket,并把Socket序号赋值给相应变量; //r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); r->m_sb.sb_socket = WSASocket(service->sa_family, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (r->m_sb.sb_socket != -1) { if(r->m_bindIP.addrLen) { if (bind(r->m_sb.sb_socket, (const struct sockaddr *)&r->m_bindIP.addr, r->m_bindIP.addrLen) < 0) { int err = GetSockError(); RTMP_Log(RTMP_LOGERROR, "%s, failed to bind socket: %s (%d)", __FUNCTION__, socketerror(err), err); RTMP_Close(r); return FALSE; } } // 定义函数int connect (int sockfd, struct sockaddr* serv_addr, int addrlen); // 函数说明connect()用来将参数sockfd 的Socket连至参数serv_addr指定的网络地址。参数addrlen为sockaddr的结构长度。 // 连接; if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr_storage)) < 0) { int err = GetSockError(); switch (err) { case 10061: { RTMP_Log(RTMP_LOGERROR, "%s is offline. Try a different server (10061).", r->Link.hostname.av_val); } break; case 10013: { RTMP_Log(RTMP_LOGERROR, "The connection is being blocked by a firewall or other security software (10013)."); } break; case 10060: { RTMP_Log(RTMP_LOGERROR, "The connection timed out. Try a different server, or check that the connection is not being blocked by a firewall or other security software (10060)."); } break; default: { RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket: %s (%d)", __FUNCTION__, socketerror(err), err); } break; } RTMP_Close(r); return FALSE; } // 指定了端口号 (注:这不是必需的); if (r->Link.socksport) { RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__); // 谈判? 发送数据报以进行谈判? ; if (!SocksNegotiate(r)) { RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__); RTMP_Close(r); return FALSE; } } } else { RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__, GetSockError()); return FALSE; } /* set timeout */ // 超时; { SET_RCVTIMEO(tv, r->Link.timeout); if (setsockopt (r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv))) { RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!", __FUNCTION__, r->Link.timeout); } } if (!r->m_bUseNagle) { setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&on, sizeof(on)); } return TRUE; }
3.5.2 RTMP_Connect1
RTMP_Connect1():第1次连接,建立真正的rtmp连接。主要分为HandShake握手(3.5.2.1)、SendConnectPacket发送命令请求(3.5.2.2)。
/************************************************************************************************************ * @brief 第1次连接,建立RTMP连接 * ************************************************************************************************************/ int RTMP_Connect1(RTMP *r, RTMPPacket *cp) { if (r->Link.protocol & RTMP_FEATURE_SSL) { #if defined(CRYPTO) && !defined(NO_SSL) TLS_client(RTMP_TLS_ctx, r->m_sb.sb_ssl); TLS_setfd(r->m_sb.sb_ssl, r->m_sb.sb_socket); if (TLS_connect(r->m_sb.sb_ssl) < 0) { RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__); RTMP_Close(r); return FALSE; } #else RTMP_Log(RTMP_LOGERROR, "%s, no SSL/TLS support", __FUNCTION__); RTMP_Close(r); return FALSE; #endif } // 使用HTTP; if (r->Link.protocol & RTMP_FEATURE_HTTP) { r->m_msgCounter = 1; r->m_clientID.av_val = NULL; r->m_clientID.av_len = 0; HTTP_Post(r, RTMPT_OPEN, "", 1); if (HTTP_read(r, 1) != 0) { r->m_msgCounter = 0; RTMP_Log(RTMP_LOGDEBUG, "%s, Could not connect for handshake", __FUNCTION__); RTMP_Close(r); return 0; } r->m_msgCounter = 0; } RTMP_Log(RTMP_LOGDEBUG, "%s, ... connected, handshaking", __FUNCTION__); // 握手(C0+C1, S0+S1+S2, C1); if (!HandShake(r, TRUE)) { RTMP_Log(RTMP_LOGERROR, "%s, handshake failed.", __FUNCTION__); RTMP_Close(r); return FALSE; } RTMP_Log(RTMP_LOGDEBUG, "%s, handshaked", __FUNCTION__); if (!SendConnectPacket(r, cp)) { RTMP_Log(RTMP_LOGERROR, "%s, RTMP connect failed.", __FUNCTION__); RTMP_Close(r); return FALSE; } return TRUE; }
3.5.2.1 HandShake
HandShake():握手过程实现(C0+C1, S0+S1+S2, C2), 具体查看RTMP协议规范。
/************************************************************************************************************ * @brief 客户端握手过程; * * C0和C1放在一个buffer中发送出去,当客户端收齐S0和S1数据后,开始发送C2; * 当客户端收到S2后并验证,则客户端的握手完成; * 此处将就收到的S1原封不动的当成C2发送给服务器端; * * C0/S0 (1个字节) : 表示版本号; * C1/S1 (1536字节) : 格式为 | time(4字节) | 0 0 0 0 (4字节) | 1528个随机数 | * C2/S2 (1536字节) : 格式为 | time(4字节)(对等端发送的时间) | time2 (4字节) | 1528个对等端随机回复 | * * @return 握手成功返回TRUE, 否则返回FALSE. ************************************************************************************************************/ static int HandShake(RTMP *r, int FP9HandShake) { int i = 0; uint32_t uptime = 0, suptime = 0; // 客户端和服务器端时间; int bMatch = 0; char type; char strC[RTMP_SIG_SIZE + 1]; // C0+C1; char* strC1 = strC + 1; // C1; char strC2[RTMP_SIG_SIZE]; // C2; char strS2[RTMP_SIG_SIZE]; // S2 // RTMP协议版本号为0x03, 即C0数据; strC[0] = 0x03; // not encrypted: 没有加密; // 获取系统时间(毫秒为单位),将其写入到C1中,占4个字节; uptime = htonl(RTMP_GetTime()); memcpy(strC1, &uptime, 4); // 上次对方返回请求的时间(毫秒为单位),将其写入到C1中; memset(&strC1[4], 0, 4); #ifdef _DEBUG // debug版,后面的1528个随机数简单的都设为0xff; for (i = 8; i < RTMP_SIG_SIZE; i++) { strC1[i] = 0xff; } #else // release版,使用rand()循环生成1528个伪随机数; for (i = 8; i < RTMP_SIG_SIZE; i++) { strC1[i] = (char)(rand() % 256); } #endif // 发送握手数据C0和C1 if (!WriteN(r, strC, RTMP_SIG_SIZE + 1)) { return FALSE; } // 读取数据报,长度为1,存入type中; // 此处读取的是服务器端发送来的S0,表示服务器使用的Rtmp版本; if (ReadN(r, &type, 1) != 1) { /* 0x03 or 0x06 (03是明文,06是加密,其他值非法)*/ return FALSE; } RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type); // 客户端要求的版本与服务器端提供的版本不一样; if (type != strC[0]) { RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, strC[0], type); } // 读取服务器端发送过来的S1数据赋值给C2,并判断随机序列长度是否相同; if (ReadN(r, strC2, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) { return FALSE; } /* decode server response */ // 把serversig的前4个字节赋值给suptime; // S1中的time与C2中的time应该相同; memcpy(&suptime, strC2, 4); // 将网络字节序转化为主机字节序,即大端转小端; suptime = ntohl(suptime); RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime); RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__, strC2[4], strC2[5], strC2[6], strC2[7]); /* 2nd part of handshake */ // 发送握手数据C2(1536个字节)给服务器; if (!WriteN(r, strC2, RTMP_SIG_SIZE)) { return FALSE; } // 读取从服务器发送过来的握手数据S2(1536个字节); if (ReadN(r, strS2, RTMP_SIG_SIZE) != RTMP_SIG_SIZE) { return FALSE; } // 比较客户端C1和服务器端S2的1536个数是否匹配; bMatch = (memcmp(strS2, strC1, RTMP_SIG_SIZE) == 0); if (!bMatch) { RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__); } return TRUE; }
wireshark抓包结果:
3.5.2.2 SendConnectPacket
SendConnectPacket()发送命令请求: set chunk size 和 connect(stream id)。
/************************************************************************************************************ * @brief 发送connect命令; * 这是每次程序运行的时候发送的第一个命令消息; * 命令消息由命令名,传输ID,和命令对象组成; * 命令对象由一系列的相关参数组成; * 可参考rtmp协议:rtmp命令消息--4.1.1节; * * set chunk size + connect(stream id) ************************************************************************************************************/ static int SendConnectPacket(RTMP *r, RTMPPacket *cp) { RTMPPacket packet; char pbuf[4096], *pend = pbuf + sizeof(pbuf); char *enc; if (cp) { return RTMP_SendPacket(r, cp, TRUE); } if((r->Link.protocol & RTMP_FEATURE_WRITE) && r->m_bSendChunkSizeInfo) { packet.m_nChannel = 0x02; packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = RTMP_PACKET_TYPE_CHUNK_SIZE; packet.m_nTimeStamp = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; packet.m_nBodySize = 4; enc = packet.m_body; AMF_EncodeInt32(enc, pend, r->m_outChunkSize); // 发送 set chunk size; if (!RTMP_SendPacket(r, &packet, FALSE)) { return 0; } } packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = RTMP_PACKET_TYPE_INVOKE; packet.m_nTimeStamp = 0; packet.m_nInfoField2 = 0; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; enc = packet.m_body; enc = AMF_EncodeString(enc, pend, &av_connect); enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); *enc++ = AMF_OBJECT; enc = AMF_EncodeNamedString(enc, pend, &av_app, &r->Link.app); if (!enc) { return FALSE; } if (r->Link.protocol & RTMP_FEATURE_WRITE) { enc = AMF_EncodeNamedString(enc, pend, &av_type, &av_nonprivate); if (!enc) { return FALSE; } } if (r->Link.flashVer.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_flashVer, &r->Link.flashVer); if (!enc) { return FALSE; } } if (r->Link.swfUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_swfUrl, &r->Link.swfUrl); if (!enc) { return FALSE; } } if (r->Link.tcUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_tcUrl, &r->Link.tcUrl); if (!enc) { return FALSE; } } if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) { enc = AMF_EncodeNamedBoolean(enc, pend, &av_fpad, FALSE); if (!enc) { return FALSE; } enc = AMF_EncodeNamedNumber(enc, pend, &av_capabilities, 15.0); if (!enc) { return FALSE; } enc = AMF_EncodeNamedNumber(enc, pend, &av_audioCodecs, r->m_fAudioCodecs); if (!enc) { return FALSE; } enc = AMF_EncodeNamedNumber(enc, pend, &av_videoCodecs, r->m_fVideoCodecs); if (!enc) { return FALSE; } enc = AMF_EncodeNamedNumber(enc, pend, &av_videoFunction, 1.0); if (!enc) { return FALSE; } if (r->Link.pageUrl.av_len) { enc = AMF_EncodeNamedString(enc, pend, &av_pageUrl, &r->Link.pageUrl); if (!enc) { return FALSE; } } } if (r->m_fEncoding != 0.0 || r->m_bSendEncoding) { /* AMF0, AMF3 not fully supported yet */ enc = AMF_EncodeNamedNumber(enc, pend, &av_objectEncoding, r->m_fEncoding); if (!enc) { return FALSE; } } if (enc + 3 >= pend) { return FALSE; } *enc++ = 0; *enc++ = 0; /* end of object - 0x00 0x00 0x09 */ *enc++ = AMF_OBJECT_END; /* add auth string */ if (r->Link.auth.av_len) { enc = AMF_EncodeBoolean(enc, pend, r->Link.lFlags & RTMP_LF_AUTH); if (!enc) { return FALSE; } enc = AMF_EncodeString(enc, pend, &r->Link.auth); if (!enc) { return FALSE; } } if (r->Link.extras.o_num) { int i; for (i = 0; i < r->Link.extras.o_num; i++) { enc = AMFProp_Encode(&r->Link.extras.o_props[i], enc, pend); if (!enc) { return FALSE; } } } packet.m_nBodySize = enc - packet.m_body; // 发送 connect(streamid); return RTMP_SendPacket(r, &packet, TRUE); }
wireshark抓包结果:
3.6 RTMP_ConnectStream
RTMP_ConnectStream(): 建立RTMP中的NetStream。此处while循环接收消息并调用RTMP_ClientPacket进行处理。
/************************************************************************************************************ * @brief 建立RTMP中的NetStream * * @return 成功返回TRUE, 否则返回FALSE. ************************************************************************************************************/ int RTMP_ConnectStream(RTMP *r, int seekTime) { RTMPPacket packet = { 0 }; /* seekTime was already set by SetupStream / SetupURL. * This is only needed by ReconnectStream. */ if (seekTime > 0) { r->Link.seekTime = seekTime; } r->m_mediaChannel = 0; while (!r->m_bPlaying && RTMP_IsConnected(r) && RTMP_ReadPacket(r, &packet)) { if (RTMPPacket_IsReady(&packet)) { if (!packet.m_nBodySize) { continue; } if ((packet.m_packetType == RTMP_PACKET_TYPE_AUDIO) || (packet.m_packetType == RTMP_PACKET_TYPE_VIDEO) || (packet.m_packetType == RTMP_PACKET_TYPE_INFO)) { RTMP_Log(RTMP_LOGWARNING, "Received FLV packet before play()! Ignoring."); RTMPPacket_Free(&packet); continue; } RTMP_ClientPacket(r, &packet); RTMPPacket_Free(&packet); } } return r->m_bPlaying; }
3.6.1 RTMP_ClientPacket
RTMP_ClientPacket(): 处理收到的消息。
/************************************************************************************************************ * @brief 处理接收到的Chunk; * * @return 有数据返回TRUE, 无返回FALSE. ************************************************************************************************************/ int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet) { int bHasMediaPacket = 0; switch (packet->m_packetType) { // RTMP消息类型ID=1 设置块大小; case RTMP_PACKET_TYPE_CHUNK_SIZE: { /* chunk size */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: chunk_size (m_packetType=1)\n"); HandleChangeChunkSize(r, packet); } break; // RTMP消息类型ID=3 致谢; case RTMP_PACKET_TYPE_BYTES_READ_REPORT: { /* bytes read report */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: bytes_read_report (m_packetType=3)\n "); } break; // RTMP消息类型ID=4 用户控制; case RTMP_PACKET_TYPE_CONTROL: { /* ctrl */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: control (m_packetType=4)\n "); HandleCtrl(r, packet); } break; // RTMP消息类型ID=5 ; case RTMP_PACKET_TYPE_SERVER_BW: { /* server bw */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: server_bw (m_packetType=5)\n "); HandleServerBW(r, packet); } break; // RTMP消息类型ID=6; case RTMP_PACKET_TYPE_CLIENT_BW: { /* client bw */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: client_bw (m_packetType=6)\n "); HandleClientBW(r, packet); } break; // RTMP消息类型ID=8 音频数据; case RTMP_PACKET_TYPE_AUDIO: { /* audio data */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: audio (m_packetType=8)\n "); HandleAudio(r, packet); bHasMediaPacket = 1; if (!r->m_mediaChannel) { r->m_mediaChannel = packet->m_nChannel; } if (!r->m_pausing) { r->m_mediaStamp = packet->m_nTimeStamp; } } break; // RTMP消息类型ID=9 视频数据; case RTMP_PACKET_TYPE_VIDEO: { /* video data */ RTMP_Log(RTMP_LOGDEBUG, "处理消息: video (m_packetType=9)\n "); HandleVideo(r, packet); bHasMediaPacket = 1; if (!r->m_mediaChannel) { r->m_mediaChannel = packet->m_nChannel; } if (!r->m_pausing) { r->m_mediaStamp = packet->m_nTimeStamp; } } break; // RTMP消息类型ID=15 AMF3编码 忽略; case RTMP_PACKET_TYPE_FLEX_STREAM_SEND: { /* flex stream send */ RTMP_Log(RTMP_LOGDEBUG, "%s, flex stream send, size %u bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); } break; // RTMP消息类型ID=16 AMF3编码 忽略; case RTMP_PACKET_TYPE_FLEX_SHARED_OBJECT: { /* flex shared object */ RTMP_Log(RTMP_LOGDEBUG, "%s, flex shared object, size %u bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); } break; // RTMP消息类型ID=17 AMF3编码 忽略 case RTMP_PACKET_TYPE_FLEX_MESSAGE: { /* flex message */ RTMP_Log(RTMP_LOGDEBUG, "%s, flex message, size %u bytes, not fully supported", __FUNCTION__, packet->m_nBodySize); /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ /* some DEBUG code */ #if 0 RTMP_LIB_AMFObject obj; int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1); if(nRes < 0) { RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__); /*return; */ } obj.Dump(); #endif if (HandleInvoke(r, packet->m_body + 1, packet->m_nBodySize - 1) == 1) { bHasMediaPacket = 2; } } break; // RTMP消息类型ID=18 AMF0编码 数据消息; case RTMP_PACKET_TYPE_INFO: { /* metadata (notify) */ RTMP_Log(RTMP_LOGDEBUG, "%s, received: notify %u bytes", __FUNCTION__, packet->m_nBodySize); // 处理元数据; if (HandleMetadata(r, packet->m_body, packet->m_nBodySize)) { bHasMediaPacket = 1; } } break; // RTMP消息类型ID=19 AMF0编码,忽略; case RTMP_PACKET_TYPE_SHARED_OBJECT: { RTMP_Log(RTMP_LOGDEBUG, "%s, shared object, not supported, ignoring", __FUNCTION__); } break; // RTMP消息类型ID=20 AMF0编码,命令消息 case RTMP_PACKET_TYPE_INVOKE: { /* invoke */ RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %u bytes", __FUNCTION__, packet->m_nBodySize); /*RTMP_LogHex(packet.m_body, packet.m_nBodySize); */ if (HandleInvoke(r, packet->m_body, packet->m_nBodySize) == 1) { bHasMediaPacket = 2; } } break; // RTMP消息类型ID=22 case RTMP_PACKET_TYPE_FLASH_VIDEO: { /* go through FLV packets and handle metadata packets */ unsigned int pos = 0; uint32_t nTimeStamp = packet->m_nTimeStamp; while (pos + 11 < packet->m_nBodySize) { uint32_t dataSize = AMF_DecodeInt24(packet->m_body + pos + 1); /* size without header (11) and prevTagSize (4) */ if (pos + 11 + dataSize + 4 > packet->m_nBodySize) { RTMP_Log(RTMP_LOGWARNING, "Stream corrupt?!"); break; } if (packet->m_body[pos] == 0x12) { HandleMetadata(r, packet->m_body + pos + 11, dataSize); } else if (packet->m_body[pos] == 8 || packet->m_body[pos] == 9) { nTimeStamp = AMF_DecodeInt24(packet->m_body + pos + 4); nTimeStamp |= (packet->m_body[pos + 7] << 24); } pos += (11 + dataSize + 4); } if (!r->m_pausing) r->m_mediaStamp = nTimeStamp; /* FLV tag(s) */ /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: FLV tag(s) %lu bytes", __FUNCTION__, packet.m_nBodySize); */ bHasMediaPacket = 1; } break; default: { RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__, packet->m_packetType); #ifdef _DEBUG RTMP_LogHex(RTMP_LOGDEBUG, packet->m_body, packet->m_nBodySize); #endif } break; } return bHasMediaPacket; }
wireshark抓包结果:
3.7 RTMP_SendPacket
RTMP_SendPacket(): 消息流发送,内含分块处理,具体可结合RTMP协议规范。
/************************************************************************************************************ * @brief 消息流发送: 包括分块发送实现; * ************************************************************************************************************/ int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) { const RTMPPacket *prevPacket; uint32_t last = 0; int nSize; int hSize, cSize; char *header, *hptr, *hend, hbuf[RTMP_MAX_HEADER_SIZE], c; uint32_t t; char *buffer, *tbuf = NULL, *toff = NULL; int nChunkSize; int tlen; if (packet->m_nChannel >= r->m_channelsAllocatedOut) { int n = packet->m_nChannel + 10; RTMPPacket **packets = realloc(r->m_vecChannelsOut, sizeof(RTMPPacket*) * n); if (!packets) { free(r->m_vecChannelsOut); r->m_vecChannelsOut = NULL; r->m_channelsAllocatedOut = 0; return FALSE; } r->m_vecChannelsOut = packets; memset(r->m_vecChannelsOut + r->m_channelsAllocatedOut, 0, sizeof(RTMPPacket*) * (n - r->m_channelsAllocatedOut)); r->m_channelsAllocatedOut = n; } // 前一个packet存在且不是完整的ChunkMsgHeader,因此有可能需要调整块消息头的类型; // fmt字节; // case 0: chunk msg header 长度为11; // case 1: chunk msg header 长度为7; // case 2: chunk msg header 长度为3; // case 3: chunk msg header 长度为0; prevPacket = r->m_vecChannelsOut[packet->m_nChannel]; if (prevPacket && packet->m_headerType != RTMP_PACKET_SIZE_LARGE) { /* compress a bit by using the prev packet's attributes */ // 获取ChunkMsgHeader类型,前一个Chunk与当前Chunk比较; if (prevPacket->m_nBodySize == packet->m_nBodySize && prevPacket->m_packetType == packet->m_packetType && packet->m_headerType == RTMP_PACKET_SIZE_MEDIUM) { // 如果前后两个块的大小、包类型都相同,则将块头类型fmt设为2; // 即可省略消息长度、消息类型id、消息流id; // 可以参考官方协议:流的分块 --- 6.1.2.3节; packet->m_headerType = RTMP_PACKET_SIZE_SMALL; } if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp && packet->m_headerType == RTMP_PACKET_SIZE_SMALL) { // 前后两个块的时间戳相同,且块头类型fmt为2,则相应的时间戳也可省略,因此将块头类型置为3; // 可以参考官方协议:流的分块 --- 6.1.2.4节; packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM; } last = prevPacket->m_nTimeStamp; } // 块头类型fmt取值0、1、2、3; 超过3就表示出错(fmt占二个字节); if (packet->m_headerType > 3) /* sanity */ { RTMP_Log(RTMP_LOGERROR, "sanity failed!! trying to send header of type: 0x%02x.", (unsigned char)packet->m_headerType); return FALSE; } // 块头初始大小 = 基本头(1字节) + 块消息头大小(11/7/3/0) = [12, 8, 4, 1]; // 块基本头是1-3字节,因此用变量cSize来表示剩下的0-2字节; // nSize 表示块头初始大小, hSize表示块头大小; nSize = packetSize[packet->m_headerType]; hSize = nSize; cSize = 0; t = packet->m_nTimeStamp - last; // 时间戳增量; if (packet->m_body) { // m_body是指向负载数据首地址的指针; "-"号用于指针前移; // header块头的首指针; hend块头的尾指针; header = packet->m_body - nSize; hend = packet->m_body; } else { header = hbuf + 6; hend = hbuf + sizeof(hbuf); } if (packet->m_nChannel > 319) { // 块流id(cs id)大于319,则块基本头占3个字节; cSize = 2; } else if (packet->m_nChannel > 63) { // 块流id(cs id)在64与319之间,则块基本头占2个字节; cSize = 1; } // ChunkBasicHeader的长度比初始长度还要长; if (cSize) { // header指向块头; header -= cSize; // hSize加上ChunkBasicHeader的长度(比初始长度多出来的长度); hSize += cSize; } // nSize>1表示块消息头至少有3个字节,即存在timestamp字段; // 相对TimeStamp大于0xffffff,此时需要使用ExtendTimeStamp; if (nSize > 1 && t >= 0xffffff) { header -= 4; hSize += 4; } hptr = header; // 把ChunkBasicHeader的Fmt类型左移6位; c = packet->m_headerType << 6; // 设置basic header的第一个字节值,前两位为fmt; // 可以参考官方协议:流的分块 --- 6.1.1节; switch (cSize) { case 0: { // 把ChunkBasicHeader的低6位设置成ChunkStreamID( cs id ) c |= packet->m_nChannel; } break; case 1: { // 同理 但低6位设置成000000; } break; case 2: { // 同理 但低6位设置成000001; c |= 1; } break; } // 可以拆分成两句*hptr=c; hptr++; // 此时hptr指向第2个字节; *hptr++ = c; // 设置basic header的第二(三)个字节值; if (cSize) { // 将要放到第2字节的内容tmp; int tmp = packet->m_nChannel - 64; // 获取低位存储与第2字节; *hptr++ = tmp & 0xff; if (cSize == 2) { // ChunkBasicHeader是最大的3字节时,获取高位存储于最后1个字节(注意:排序使用大端序列,和主机相反); *hptr++ = tmp >> 8; } } // ChunkMsgHeader长度为11、7、3 都含有timestamp(3字节); if (nSize > 1) { // 将时间戳(相对或绝对)转化为3个字节存入hptr 如果时间戳超过0xffffff 则后面还要填入Extend Timestamp; hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t); } // ChunkMsgHeader长度为11、7,都含有 msg length + msg type id; if (nSize > 4) { // 将消息长度(msg length)转化为3个字节存入hptr; hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize); *hptr++ = packet->m_packetType; } // ChunkMsgHeader长度为11 含有msg stream id(小端); if (nSize > 8) { hptr += EncodeInt32LE(hptr, packet->m_nInfoField2); } if (nSize > 1 && t >= 0xffffff) { hptr = AMF_EncodeInt32(hptr, hend, t); } // 到此为止 已经将块头填写好了; // 此时nSize表示负载数据的长度 buffer是指向负载数据区的指针; nSize = packet->m_nBodySize; buffer = packet->m_body; nChunkSize = r->m_outChunkSize; //Chunk大小 默认是128字节; RTMP_Log(RTMP_LOGDEBUG2, "%s: fd=%d, size=%d", __FUNCTION__, r->m_sb.sb_socket, nSize); /* send all chunks in one HTTP request 使用HTTP协议 */ if (r->Link.protocol & RTMP_FEATURE_HTTP) { // nSize: Message负载长度; nChunkSize:Chunk长度; // 例nSize: 307 nChunkSize: 128 ; // 可分为(307 + 128 - 1)/128 = 3个; // 为什么加 nChunkSize - 1? 因为除法会只取整数部分!; int chunks = (nSize+nChunkSize-1) / nChunkSize; // Chunk个数超过一个; if (chunks > 1) { // 注意: ChunkBasicHeader的长度 = cSize + 1; // 消息分n块后总的开销; // n个ChunkBasicHeader 1个ChunkMsgHeader 1个Message负载; // 实际上只有第一个Chunk是完整的,剩下的只有ChunkBasicHeader; tlen = chunks * (cSize + 1) + nSize + hSize; tbuf = malloc(tlen); if (!tbuf) { return FALSE; } toff = tbuf; } } // 消息的负载 + 头; while (nSize + hSize) { int wrote; // 消息负载大小 < Chunk大小(不用分块); if (nSize < nChunkSize) { // Chunk可能小于设定值; nChunkSize = nSize; } RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)header, hSize); RTMP_LogHexString(RTMP_LOGDEBUG2, (uint8_t *)buffer, nChunkSize); // 如果r->Link.protocol采用Http协议 则将RTMP包数据封装成多个Chunk 然后一次性发送; // 否则每封装成一个块,就立即发送出去; if (tbuf) { // 将从Chunk头开始的nChunkSize + hSize个字节拷贝至toff中; // 这些拷贝的数据包括块头数据(hSize字节)和nChunkSize个负载数据; memcpy(toff, header, nChunkSize + hSize); toff += nChunkSize + hSize; } else // 负载数据长度不超过设定的块大小 不需要分块; 因此tbuf为NULL或者r->Link.protocol不采用Http; { // 直接将负载数据和块头数据发送出去; wrote = WriteN(r, header, nChunkSize + hSize); if (!wrote) { return FALSE; } } nSize -= nChunkSize; // 消息负载长度 - Chunk负载长度; buffer += nChunkSize; // buffer指针前移1个Chunk负载长度; hSize = 0; // 如果消息负载数据还没有发完 准备填充下一个块的块头数据; if (nSize > 0) { header = buffer - 1; hSize = 1; if (cSize) { header -= cSize; hSize += cSize; } *header = (0xc0 | c); if (cSize) { int tmp = packet->m_nChannel - 64; header[1] = tmp & 0xff; if (cSize == 2) { header[2] = tmp >> 8; } } } } if (tbuf) { int wrote = WriteN(r, tbuf, toff-tbuf); free(tbuf); tbuf = NULL; if (!wrote) { return FALSE; } } /* we invoked a remote method */ if (packet->m_packetType == RTMP_PACKET_TYPE_INVOKE) { AVal method; char *ptr; ptr = packet->m_body + 1; AMF_DecodeString(ptr, &method); RTMP_Log(RTMP_LOGDEBUG, "Invoking %s", method.av_val); /* keep it in call queue till result arrives */ if (queue) { int txn; ptr += 3 + method.av_len; txn = (int)AMF_DecodeNumber(ptr); AV_queue(&r->m_methodCalls, &r->m_numCalls, &method, txn); } } if (!r->m_vecChannelsOut[packet->m_nChannel]) { r->m_vecChannelsOut[packet->m_nChannel] = malloc(sizeof(RTMPPacket)); } memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket)); return TRUE; }
wireshark抓包结果: