- 浏览: 4398355 次
最新评论
RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)
RTMPdump 源代码分析 1: main()函数
RTMPDump(libRTMP)源代码分析 2:解析RTMP地址——RTMP_ParseURL()
RTMPdump(libRTMP) 源代码分析 3: AMF编码
RTMPdump(libRTMP)源代码分析 4: 连接第一步——握手(Hand Shake)
RTMPdump(libRTMP) 源代码分析 5: 建立一个流媒体连接 (NetConnection部分)
RTMPdump(libRTMP) 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)
RTMPdump(libRTMP) 源代码分析 7: 建立一个流媒体连接 (NetStream部分 2)
RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)
RTMPdump(libRTMP) 源代码分析 9: 接收消息(Message)(接收视音频数据)
RTMPdump(libRTMP) 源代码分析 10: 处理各种消息(Message)
===============================
之前写了一系列的文章介绍RTMPDump各种函数。比如怎么建立网络连接(NetConnection),怎么建立网络流(NetStream)之类的,唯独没有介绍这些发送或接收的数据,在底层到底是怎么实现的。本文就是要剖析一下其内部的实现。即这些消息(Message)到底是怎么发送和接收的。
先来看看发送消息吧。
发送connect命令使用函数SendConnectPacket()
发送createstream命令使用RTMP_SendCreateStream()
发送realeaseStream命令使用SendReleaseStream()
发送publish命令使用SendPublish()
发送deleteStream的命令使用SendDeleteStream()
发送pause命令使用RTMP_SendPause()
不再一一例举,发现函数命名有两种规律:RTMP_Send***()或者Send***(),其中*号代表命令的名称。
SendConnectPacket()这个命令是每次程序开始运行的时候发送的第一个命令消息,内容比较多,包含了很多AMF编码的内容,在此不多做分析,贴上代码:
//发送“connect”命令 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); packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_LARGE; packet.m_packetType = 0x14; /* 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; //---------------- r->dlg->AppendMLInfo(20,1,"命令消息","Connect"); //----------------------------- return RTMP_SendPacket(r, &packet, TRUE); }
RTMP_SendCreateStream()命令相对而言比较简单,代码如下:
//发送“createstream”命令 int RTMP_SendCreateStream(RTMP *r) { RTMPPacket packet; char pbuf[256], *pend = pbuf + sizeof(pbuf); char *enc; packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; /* 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_createStream); enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); *enc++ = AMF_NULL; /* NULL */ packet.m_nBodySize = enc - packet.m_body; //---------------- r->dlg->AppendMLInfo(20,1,"命令消息","CreateStream"); //----------------------------- return RTMP_SendPacket(r, &packet, TRUE); }
同样,SendReleaseStream()内容也比较简单,我对其中部分内容作了注释:
//发送RealeaseStream命令 static int SendReleaseStream(RTMP *r) { RTMPPacket packet; char pbuf[1024], *pend = pbuf + sizeof(pbuf); char *enc; packet.m_nChannel = 0x03; /* control channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; packet.m_packetType = 0x14; /* 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; //对“releaseStream”字符串进行AMF编码 enc = AMF_EncodeString(enc, pend, &av_releaseStream); //对传输ID(0)进行AMF编码? enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); //命令对象 *enc++ = AMF_NULL; //对播放路径字符串进行AMF编码 enc = AMF_EncodeString(enc, pend, &r->Link.playpath); if (!enc) return FALSE; packet.m_nBodySize = enc - packet.m_body; //---------------- r->dlg->AppendMLInfo(20,1,"命令消息","ReleaseStream"); //----------------------------- return RTMP_SendPacket(r, &packet, FALSE); }
再来看一个SendPublish()函数,用于发送“publish”命令
//发送Publish命令 static int SendPublish(RTMP *r) { RTMPPacket packet; char pbuf[1024], *pend = pbuf + sizeof(pbuf); char *enc; //块流ID为4 packet.m_nChannel = 0x04; /* source channel (invoke) */ packet.m_headerType = RTMP_PACKET_SIZE_LARGE; //命令消息,类型20 packet.m_packetType = 0x14; /* INVOKE */ packet.m_nTimeStamp = 0; //流ID packet.m_nInfoField2 = r->m_stream_id; packet.m_hasAbsTimestamp = 0; packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; //指向Chunk的负载 enc = packet.m_body; //对“publish”字符串进行AMF编码 enc = AMF_EncodeString(enc, pend, &av_publish); enc = AMF_EncodeNumber(enc, pend, ++r->m_numInvokes); //命令对象为空 *enc++ = AMF_NULL; enc = AMF_EncodeString(enc, pend, &r->Link.playpath); if (!enc) return FALSE; /* FIXME: should we choose live based on Link.lFlags & RTMP_LF_LIVE? */ enc = AMF_EncodeString(enc, pend, &av_live); if (!enc) return FALSE; packet.m_nBodySize = enc - packet.m_body; //---------------- r->dlg->AppendMLInfo(20,1,"命令消息","Pulish"); //----------------------------- return RTMP_SendPacket(r, &packet, TRUE); }
其他的命令不再一一例举,总体的思路是声明一个RTMPPacket类型的结构体,然后设置各种属性值,最后交给RTMP_SendPacket()进行发送。
RTMPPacket类型的结构体定义如下,一个RTMPPacket对应RTMP协议规范里面的一个块(Chunk)。
//Chunk信息 typedef struct RTMPPacket { uint8_t m_headerType;//ChunkMsgHeader的类型(4种) uint8_t m_packetType;//Message type ID(1-7协议控制;8,9音视频;10以后为AMF编码消息) uint8_t m_hasAbsTimestamp; /* Timestamp 是绝对值还是相对值? */ int m_nChannel; //块流ID uint32_t m_nTimeStamp; // Timestamp int32_t m_nInfoField2; /* last 4 bytes in a long header,消息流ID */ uint32_t m_nBodySize; //消息长度 uint32_t m_nBytesRead; RTMPChunk *m_chunk; char *m_body; } RTMPPacket;
下面我们来看看RTMP_SendPacket()吧,各种的RTMPPacket(即各种Chunk)都需要用这个函数进行发送。
//自己编一个数据报发送出去! //非常常用 int RTMP_SendPacket(RTMP *r, RTMPPacket *packet, int queue) { const RTMPPacket *prevPacket = r->m_vecChannelsOut[packet->m_nChannel]; 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; //不是完整ChunkMsgHeader 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) packet->m_headerType = RTMP_PACKET_SIZE_SMALL; if (prevPacket->m_nTimeStamp == packet->m_nTimeStamp && packet->m_headerType == RTMP_PACKET_SIZE_SMALL) packet->m_headerType = RTMP_PACKET_SIZE_MINIMUM; //上一个packet的TimeStamp last = prevPacket->m_nTimeStamp; } 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; } //chunk包头大小;packetSize[] = { 12, 8, 4, 1 } nSize = packetSize[packet->m_headerType]; hSize = nSize; cSize = 0; //相对的TimeStamp t = packet->m_nTimeStamp - last; if (packet->m_body) { //Header的Start //m_body是指向负载数据首地址的指针;“-”号用于指针前移 header = packet->m_body - nSize; //Header的End hend = packet->m_body; } else { header = hbuf + 6; hend = hbuf + sizeof(hbuf); } //当ChunkStreamID大于319时 if (packet->m_nChannel > 319) //ChunkBasicHeader是3个字节 cSize = 2; //当ChunkStreamID大于63时 else if (packet->m_nChannel > 63) //ChunkBasicHeader是2个字节 cSize = 1; if (cSize) { //header指针指向ChunkMsgHeader header -= cSize; //hsize加上ChunkBasicHeader的长度 hSize += cSize; } //相对TimeStamp大于0xffffff,此时需要使用ExtendTimeStamp if (nSize > 1 && t >= 0xffffff) { header -= 4; hSize += 4; } hptr = header; //把ChunkBasicHeader的Fmt类型左移6位 c = packet->m_headerType << 6; switch (cSize) { //把ChunkBasicHeader的低6位设置成ChunkStreamID case 0: c |= packet->m_nChannel; break; //同理,但低6位设置成000000 case 1: break; //同理,但低6位设置成000001 case 2: c |= 1; break; } //可以拆分成两句*hptr=c;hptr++,此时hptr指向第2个字节 *hptr++ = c; //CSize>0,即ChunkBasicHeader大于1字节 if (cSize) { //将要放到第2字节的内容tmp int tmp = packet->m_nChannel - 64; //获取低位存储与第2字节 *hptr++ = tmp & 0xff; //ChunkBasicHeader是最大的3字节时 if (cSize == 2) //获取高位存储于最后1个字节(注意:排序使用大端序列,和主机相反) *hptr++ = tmp >> 8; } //ChunkMsgHeader。注意一共有4种,包含的字段数不同。 //TimeStamp(3B) if (nSize > 1) { //相对TimeStamp和绝对TimeStamp? hptr = AMF_EncodeInt24(hptr, hend, t > 0xffffff ? 0xffffff : t); } //MessageLength+MessageTypeID(4B) if (nSize > 4) { //MessageLength hptr = AMF_EncodeInt24(hptr, hend, packet->m_nBodySize); //MessageTypeID *hptr++ = packet->m_packetType; } //MessageStreamID(4B) if (nSize > 8) hptr += EncodeInt32LE(hptr, packet->m_nInfoField2); //ExtendedTimeStamp if (nSize > 1 && t >= 0xffffff) hptr = AMF_EncodeInt32(hptr, hend, t); //负载长度,指向负载的指针 nSize = packet->m_nBodySize; buffer = packet->m_body; //Chunk大小,默认128字节 nChunkSize = r->m_outChunkSize; 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) { //注意:CSize=1表示ChunkBasicHeader是2字节 //消息分n块后总的开销: //n个ChunkBasicHeader,1个ChunkMsgHeader,1个Message负载 //实际中只有第一个Chunk是完整的,剩下的只有ChunkBasicHeader tlen = chunks * (cSize + 1) + nSize + hSize; //分配内存 tbuf = (char *) 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); if (tbuf) { //void *memcpy(void *dest, const void *src, int n); //由src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内 memcpy(toff, header, nChunkSize + hSize); toff += nChunkSize + hSize; } else { wrote = WriteN(r, header, nChunkSize + hSize); if (!wrote) return FALSE; } //消息负载长度-Chunk负载长度 nSize -= nChunkSize; //Buffer指针前移1个Chunk负载长度 buffer += nChunkSize; hSize = 0; //如果消息没有发完 if (nSize > 0) { //ChunkBasicHeader header = buffer - 1; hSize = 1; if (cSize) { header -= cSize; hSize += cSize; } //ChunkBasicHeader第1个字节 *header = (0xc0 | c); //ChunkBasicHeader大于1字节 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 == 0x14) { 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] = (RTMPPacket *) malloc(sizeof(RTMPPacket)); memcpy(r->m_vecChannelsOut[packet->m_nChannel], packet, sizeof(RTMPPacket)); return TRUE; }
这个函数乍一看好像非常复杂,其实不然,他只是按照RTMP规范将数据编码成符合规范的块(Chunk),规范可以参考相关的文档。
具体怎么编码成块(Chunk)就不多分析了,在这里需要注意一个函数:WriteN()。该函数完成了将数据发送出去的功能。
来看一下WriteN()函数:
//发送数据报的时候调用(连接,buffer,长度) static int WriteN(RTMP *r, const char *buffer, int n) { const char *ptr = buffer; #ifdef CRYPTO char *encrypted = 0; char buf[RTMP_BUFFER_CACHE_SIZE]; if (r->Link.rc4keyOut) { if (n > sizeof(buf)) encrypted = (char *)malloc(n); else encrypted = (char *)buf; ptr = encrypted; RC4_encrypt2((RC4_KEY *)r->Link.rc4keyOut, n, buffer, ptr); } #endif while (n > 0) { int nBytes; //因方式的不同而调用不同函数 //如果使用的是HTTP协议进行连接 if (r->Link.protocol & RTMP_FEATURE_HTTP) nBytes = HTTP_Post(r, RTMPT_SEND, ptr, n); else nBytes = RTMPSockBuf_Send(&r->m_sb, ptr, n); /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d\n", __FUNCTION__, nBytes); */ //成功发送字节数<0 if (nBytes < 0) { int sockerr = GetSockError(); RTMP_Log(RTMP_LOGERROR, "%s, RTMP send error %d (%d bytes)", __FUNCTION__, sockerr, n); if (sockerr == EINTR && !RTMP_ctrlC) continue; RTMP_Close(r); n = 1; break; } if (nBytes == 0) break; n -= nBytes; ptr += nBytes; } #ifdef CRYPTO if (encrypted && encrypted != buf) free(encrypted); #endif return n == 0; }
该函数中,RTMPSockBuf_Send()完成了数据发送的功能,再来看看这个函数(函数调用真是好多啊。。。。)
//Socket发送(指明套接字,buffer缓冲区,数据长度) //返回所发数据量 int RTMPSockBuf_Send(RTMPSockBuf *sb, const char *buf, int len) { int rc; #ifdef _DEBUG fwrite(buf, 1, len, netstackdump); #endif #if defined(CRYPTO) && !defined(NO_SSL) if (sb->sb_ssl) { rc = TLS_write((SSL *)sb->sb_ssl, buf, len); } else #endif { //向一个已连接的套接口发送数据。 //int send( SOCKET s, const char * buf, int len, int flags); //s:一个用于标识已连接套接口的描述字。 //buf:包含待发送数据的缓冲区。 //len:缓冲区中数据的长度。 //flags:调用执行方式。 //rc:所发数据量。 rc = send(sb->sb_socket, buf, len, 0); } return rc; } int RTMPSockBuf_Close(RTMPSockBuf *sb) { #if defined(CRYPTO) && !defined(NO_SSL) if (sb->sb_ssl) { TLS_shutdown((SSL *)sb->sb_ssl); TLS_close((SSL *)sb->sb_ssl); sb->sb_ssl = NULL; } #endif return closesocket(sb->sb_socket); }
到这个函数的时候,发现一层层的调用终于完成了,最后调用了系统Socket的send()函数完成了数据的发送功能。
之前贴过一张图总结这个过程,可能理解起来要方便一些:RTMPDump源代码分析 0: 主要函数调用分析
rtmpdump源代码(Linux):http://download.csdn.net/detail/leixiaohua1020/6376561
rtmpdump源代码(VC 2005 工程):http://download.csdn.net/detail/leixiaohua1020/6563163
相关推荐
1. rtmpdump源代码:这是工具的源代码,程序员可以查看并理解其工作原理,或者根据需求进行定制。 2. librtmp源代码:提供了库的源代码,开发者可以修改、优化或扩展功能。 3. 静态库(.lib文件):适用于那些希望将...
2. **RTMP协议解析**:源代码会解析RTMP协议的固定头部、命令消息和数据消息,理解不同类型的AMF(Action Message Format)编码。 3. **文件写入**:rtmpdump将接收到的数据流写入本地文件,实现流媒体的录制或保存...
在压缩包中的"rtmpdump-2.3"可能是rtmpdump的源代码或者编译好的二进制文件,用户可以进一步查看源码理解其工作原理,或者直接在Android设备上使用编译后的二进制文件进行流媒体操作。对于开发者来说,这为自定义...
- 添加源文件:将rtmpdump和librtmp的源代码添加到项目中。 - 适应平台:修改源代码中的编译器特定宏和包含路径,以适应VS2008环境。 - 链接库设置:确保正确配置了所有依赖库,如zlib等。 - 编译和调试:编译...
在编译rtmpdump时,需要将librtmp_src文件夹中的源代码与其他依赖库合并,并配置合适的编译选项。对于Windows环境,这通常涉及设置项目属性、链接库路径和包含目录。完成编译后,将生成rtmpdump可执行文件,可用于与...
3. **自定义编译**: 虽然这个资源包提供了rtmpdump的源代码,但编译和安装过程需要用户自行完成,这给了用户根据特定环境定制配置的机会。 ### 安装rtmpdump-2.4 在树莓派上安装rtmpdump-2.4时,首先确保系统是...
1. **RTMP连接与握手**:rtmpdump能够处理RTMP协议中的连接建立过程,包括AMF(Action Message Format)编码的握手消息,这是与RTMP服务器交互的基础。 2. **流识别与控制**:它能解析RTMP流元数据,允许用户指定要...
这个压缩包“rtmpdump-2.2e.tar.gz”包含了rtmpdump的源代码,允许用户深入理解其工作原理并可能进行自定义修改。下面将详细讨论RTMP协议、rtmpdump工具、以及它在IT领域的应用。 RTMP(Real Time Messaging ...
2. **编译好的动态库**:预编译的动态库文件(如librtmp.so)使得其他应用程序可以直接链接并使用rtmpdump的功能,而无需重新编译整个源代码。这对于开发基于rtmpdump的应用程序非常方便。 使用rtmpdump-2.3,你...
"bin"目录通常存放可执行文件,而"librtmp_src"则表示RTMP库的源代码,这是rtmpdump的核心部分,用于处理与RTMP服务器的通信。 编译rtmpdump时,你需要确保你有正确的开发环境,例如Visual Studio 2010,因为它是一...
源代码可以让你深入理解RTMP协议的工作原理,以及rtmpdump如何实现数据的捕获和处理。这对于想要自定义或扩展rtmpdump功能的人来说尤其重要。 rtmpdump.exe是rtmpdump的可执行文件,你可以用它来从RTMP服务器上下载...
RTMP协议通过TCP连接工作,并使用一种基于AMF(Action Message Format)的数据格式来编码消息。 **RTMPDump功能** RTMPDump的主要功能包括: 1. **记录RTMP流**:可以将RTMP流记录为本地文件,便于离线观看或...
2. **下载源代码**:从官方网站或者GitHub仓库获取rtmpdump-2.3的源代码包。 3. **解压与配置**:使用`tar`命令解压缩源代码包,然后进入目录并使用`./configure`进行配置,根据提示检查依赖是否齐全。 4. **编译与...
rtmpdump-v1.6.tar.gz是rtmpdump的一个特定版本,通过解压这个tar.gz压缩包,我们可以获取到rtmpdump的源代码和其他相关文件。在这个压缩包中,唯一列出的子文件名是"rtmpdump",这通常指的是rtmpdump的可执行程序...
通过研究rtmpdump的源代码,开发者不仅可以学习RTMP协议的工作原理,还能掌握C++在实际项目中的应用,这对于开发流媒体相关的应用或者进行网络编程都是宝贵的资源。同时,这个项目也可以作为教学示例,帮助初学者...
3. `AMF`模块:Action Message Format,用于封装RTMP协议中的数据,如命令消息和音频、视频数据。 4. `Chunk`模块:RTMP协议使用分块传输来提高效率,chunk模块处理数据的分块和重组。 5. `Utils`模块:包含各种...
同时,开发者也可以利用rtmpdump的源代码,学习如何实现RTMP协议的相关功能,为自己的项目提供支持。 然而,随着HLS (HTTP Live Streaming) 和DASH (Dynamic Adaptive Streaming over HTTP) 等适应性更强的流媒体...
在Linux或macOS上,可能需要通过编译源代码来安装。一旦安装完毕,你可以通过以下命令开始下载: ```bash rtmpdump -r "rtmp://server-address/app-name/stream-key" -o "output-file.flv" ``` 这里的参数解释如下...
3. **实时推流**:RTMPDump能够实现实时地将数据推送到服务器,这意味着你需要持续地从视频源读取数据并将其发送到RTMP服务器。这个过程需要考虑到网络延迟和丢包,确保数据的连续性和完整性。 4. **错误处理和重试...
RTMPdump(包括libRTMP)的VS2005可以编译通过的源代码。