注:此前写了一些列的分析RTMPdump(libRTMP)源代码的文章,在此列一个列表:
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 源代码分析 6: 建立一个流媒体连接 (NetStream部分 1)
上回说到,有两个函数尤为重要:
RTMP_ReadPacket()
RTMP_ClientPacket()
而且分析了第一个函数。现在我们再来看看第二个函数吧。第二个函数的主要作用是:处理消息(Message),并做出响应。
先把带注释的代码贴上:
//处理接收到的Chunk int RTMP_ClientPacket(RTMP *r, RTMPPacket *packet) { int bHasMediaPacket = 0; switch (packet->m_packetType) { //RTMP消息类型ID=1,设置块大小 case 0x01: /* chunk size */ //---------------- r->dlg->AppendCInfo("处理收到的数据。消息 Set Chunk Size (typeID=1)。"); //----------------------------- RTMP_LogPrintf("处理消息 Set Chunk Size (typeID=1)\n"); HandleChangeChunkSize(r, packet); break; //RTMP消息类型ID=3,致谢 case 0x03: /* bytes read report */ RTMP_Log(RTMP_LOGDEBUG, "%s, received: bytes read report", __FUNCTION__); break; //RTMP消息类型ID=4,用户控制 case 0x04: /* ctrl */ //---------------- r->dlg->AppendCInfo("处理收到的数据。消息 User Control (typeID=4)。"); //----------------------------- RTMP_LogPrintf("处理消息 User Control (typeID=4)\n"); HandleCtrl(r, packet); break; //RTMP消息类型ID=5 case 0x05: /* server bw */ //---------------- r->dlg->AppendCInfo("处理收到的数据。消息 Window Acknowledgement Size (typeID=5)。"); //----------------------------- RTMP_LogPrintf("处理消息 Window Acknowledgement Size (typeID=5)\n"); HandleServerBW(r, packet); break; //RTMP消息类型ID=6 case 0x06: /* client bw */ //---------------- r->dlg->AppendCInfo("处理收到的数据。消息 Set Peer Bandwidth (typeID=6)。"); //----------------------------- RTMP_LogPrintf("处理消息 Set Peer Bandwidth (typeID=6)\n"); HandleClientBW(r, packet); break; //RTMP消息类型ID=8,音频数据 case 0x08: /* audio data */ /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize); */ 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 0x09: /* video data */ /*RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize); */ 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 0x0F: /* flex stream send */ RTMP_Log(RTMP_LOGDEBUG, "%s, flex stream send, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); break; //RTMP消息类型ID=16,AMF3编码,忽略 case 0x10: /* flex shared object */ RTMP_Log(RTMP_LOGDEBUG, "%s, flex shared object, size %lu bytes, not supported, ignoring", __FUNCTION__, packet->m_nBodySize); break; //RTMP消息类型ID=17,AMF3编码,忽略 case 0x11: /* flex message */ { RTMP_Log(RTMP_LOGDEBUG, "%s, flex message, size %lu 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 0x12: /* metadata (notify) */ RTMP_Log(RTMP_LOGDEBUG, "%s, received: notify %lu bytes", __FUNCTION__, packet->m_nBodySize); //处理元数据,暂时注释 /* if (HandleMetadata(r, packet->m_body, packet->m_nBodySize)) bHasMediaPacket = 1; break; */ //RTMP消息类型ID=19,AMF0编码,忽略 case 0x13: RTMP_Log(RTMP_LOGDEBUG, "%s, shared object, not supported, ignoring", __FUNCTION__); break; //RTMP消息类型ID=20,AMF0编码,命令消息 //处理命令消息! case 0x14: //---------------- r->dlg->AppendCInfo("处理收到的数据。消息 命令 (AMF0编码) (typeID=20)。"); //----------------------------- /* invoke */ RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__, packet->m_nBodySize); RTMP_LogPrintf("处理命令消息 (typeID=20,AMF0编码)\n"); /*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 0x16: { /* 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, (const uint8_t *)packet->m_body, packet->m_nBodySize); #endif } return bHasMediaPacket; }
里面注释的比较多,可以看出,大体的思路是,根据接收到的消息(Message)类型的不同,做出不同的响应。例如收到的消息类型为0x01,那么就是设置块(Chunk)大小的协议,那么就调用相应的函数进行处理。
因此,本函数可以说是程序的灵魂,收到的各种命令消息都要经过本函数的判断决定调用哪个函数进行相应的处理。
在这里注意一下消息类型为0x14的消息,即消息类型ID为20的消息,是AMF0编码的命令消息。这在RTMP连接中是非常常见的,比如说各种控制命令:播放,暂停,停止等等。我们来仔细看看它的调用。
可以发现它调用了HandleInvoke()函数来处理服务器发来的AMF0编码的命令,来看看细节:
/* Returns 0 for OK/Failed/error, 1 for 'Stop or Complete' */ static int HandleInvoke(RTMP *r, const char *body, unsigned int nBodySize) { AMFObject obj; AVal method; int txn; int ret = 0, nRes; if (body[0] != 0x02) /* make sure it is a string method name we start with */ { RTMP_Log(RTMP_LOGWARNING, "%s, Sanity failed. no string method in invoke packet", __FUNCTION__); return 0; } nRes = AMF_Decode(&obj, body, nBodySize, FALSE); if (nRes < 0) { RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__); return 0; } AMF_Dump(&obj); AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method); txn = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1)); RTMP_Log(RTMP_LOGDEBUG, "%s, server invoking <%s>", __FUNCTION__, method.av_val); if (AVMATCH(&method, &av__result)) { AVal methodInvoked = {0}; int i; for (i=0; i<r->m_numCalls; i++) { if (r->m_methodCalls[i].num == txn) { methodInvoked = r->m_methodCalls[i].name; AV_erase(r->m_methodCalls, &r->m_numCalls, i, FALSE); break; } } if (!methodInvoked.av_val) { RTMP_Log(RTMP_LOGDEBUG, "%s, received result id %d without matching request", __FUNCTION__, txn); goto leave; } //---------------- char temp_str[100]; sprintf(temp_str,"接收数据。消息 %s 的 Result",methodInvoked.av_val); r->dlg->AppendCInfo(temp_str); //----------------------------- RTMP_Log(RTMP_LOGDEBUG, "%s, received result for method call <%s>", __FUNCTION__, methodInvoked.av_val); if (AVMATCH(&methodInvoked, &av_connect)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","Result (Connect)"); //----------------------------- if (r->Link.token.av_len) { AMFObjectProperty p; if (RTMP_FindFirstMatchingProperty(&obj, &av_secureToken, &p)) { DecodeTEA(&r->Link.token, &p.p_vu.p_aval); SendSecureTokenResponse(r, &p.p_vu.p_aval); } } if (r->Link.protocol & RTMP_FEATURE_WRITE) { SendReleaseStream(r); SendFCPublish(r); } else { //---------------- r->dlg->AppendCInfo("发送数据。消息 Window Acknowledgement Size (typeID=5)。"); //----------------------------- RTMP_LogPrintf("发送消息Window Acknowledgement Size(typeID=5)\n"); RTMP_SendServerBW(r); RTMP_SendCtrl(r, 3, 0, 300); } //---------------- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (CreateStream)。"); //----------------------------- RTMP_LogPrintf("发送命令消息“CreateStream” (typeID=20)\n"); RTMP_SendCreateStream(r); if (!(r->Link.protocol & RTMP_FEATURE_WRITE)) { /* Send the FCSubscribe if live stream or if subscribepath is set */ if (r->Link.subscribepath.av_len) SendFCSubscribe(r, &r->Link.subscribepath); else if (r->Link.lFlags & RTMP_LF_LIVE) SendFCSubscribe(r, &r->Link.playpath); } } else if (AVMATCH(&methodInvoked, &av_createStream)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","Result (CreateStream)"); //----------------------------- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); if (r->Link.protocol & RTMP_FEATURE_WRITE) { SendPublish(r); } else { if (r->Link.lFlags & RTMP_LF_PLST) SendPlaylist(r); //---------------- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (Play)。"); //----------------------------- RTMP_LogPrintf("发送命令消息“play” (typeID=20)\n"); SendPlay(r); RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); } } else if (AVMATCH(&methodInvoked, &av_play) || AVMATCH(&methodInvoked, &av_publish)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","Result (Play or Publish)"); //----------------------------- r->m_bPlaying = TRUE; } free(methodInvoked.av_val); } else if (AVMATCH(&method, &av_onBWDone)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","onBWDone"); //----------------------------- if (!r->m_nBWCheckCounter) SendCheckBW(r); } else if (AVMATCH(&method, &av_onFCSubscribe)) { /* SendOnFCSubscribe(); */ } else if (AVMATCH(&method, &av_onFCUnsubscribe)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","onFCUnsubscribe"); //----------------------------- RTMP_Close(r); ret = 1; } else if (AVMATCH(&method, &av_ping)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","Ping"); //----------------------------- SendPong(r, txn); } else if (AVMATCH(&method, &av__onbwcheck)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","onBWcheck"); //----------------------------- SendCheckBWResult(r, txn); } else if (AVMATCH(&method, &av__onbwdone)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","onBWdone"); //----------------------------- int i; for (i = 0; i < r->m_numCalls; i++) if (AVMATCH(&r->m_methodCalls[i].name, &av__checkbw)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); break; } } else if (AVMATCH(&method, &av__error)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","error"); //----------------------------- RTMP_Log(RTMP_LOGERROR, "rtmp server sent error"); } else if (AVMATCH(&method, &av_close)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","close"); //----------------------------- RTMP_Log(RTMP_LOGERROR, "rtmp server requested close"); RTMP_Close(r); } else if (AVMATCH(&method, &av_onStatus)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","onStatus"); //----------------------------- AMFObject obj2; AVal code, level; AMFProp_GetObject(AMF_GetProp(&obj, NULL, 3), &obj2); AMFProp_GetString(AMF_GetProp(&obj2, &av_code, -1), &code); AMFProp_GetString(AMF_GetProp(&obj2, &av_level, -1), &level); RTMP_Log(RTMP_LOGDEBUG, "%s, onStatus: %s", __FUNCTION__, code.av_val); if (AVMATCH(&code, &av_NetStream_Failed) || AVMATCH(&code, &av_NetStream_Play_Failed) || AVMATCH(&code, &av_NetStream_Play_StreamNotFound) || AVMATCH(&code, &av_NetConnection_Connect_InvalidApp)) { r->m_stream_id = -1; RTMP_Close(r); RTMP_Log(RTMP_LOGERROR, "Closing connection: %s", code.av_val); } else if (AVMATCH(&code, &av_NetStream_Play_Start)) { int i; r->m_bPlaying = TRUE; for (i = 0; i < r->m_numCalls; i++) { if (AVMATCH(&r->m_methodCalls[i].name, &av_play)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); break; } } } else if (AVMATCH(&code, &av_NetStream_Publish_Start)) { int i; r->m_bPlaying = TRUE; for (i = 0; i < r->m_numCalls; i++) { if (AVMATCH(&r->m_methodCalls[i].name, &av_publish)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); break; } } } /* Return 1 if this is a Play.Complete or Play.Stop */ else if (AVMATCH(&code, &av_NetStream_Play_Complete) || AVMATCH(&code, &av_NetStream_Play_Stop) || AVMATCH(&code, &av_NetStream_Play_UnpublishNotify)) { RTMP_Close(r); ret = 1; } else if (AVMATCH(&code, &av_NetStream_Seek_Notify)) { r->m_read.flags &= ~RTMP_READ_SEEKING; } else if (AVMATCH(&code, &av_NetStream_Pause_Notify)) { if (r->m_pausing == 1 || r->m_pausing == 2) { RTMP_SendPause(r, FALSE, r->m_pauseStamp); r->m_pausing = 3; } } } else if (AVMATCH(&method, &av_playlist_ready)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","playlist_ready"); //----------------------------- int i; for (i = 0; i < r->m_numCalls; i++) { if (AVMATCH(&r->m_methodCalls[i].name, &av_set_playlist)) { AV_erase(r->m_methodCalls, &r->m_numCalls, i, TRUE); break; } } } else { } leave: AMF_Reset(&obj); return ret; } int RTMP_FindFirstMatchingProperty(AMFObject *obj, const AVal *name, AMFObjectProperty * p) { int n; /* this is a small object search to locate the "duration" property */ for (n = 0; n < obj->o_num; n++) { AMFObjectProperty *prop = AMF_GetProp(obj, NULL, n); if (AVMATCH(&prop->p_name, name)) { *p = *prop; return TRUE; } if (prop->p_type == AMF_OBJECT) { if (RTMP_FindFirstMatchingProperty(&prop->p_vu.p_object, name, p)) return TRUE; } } return FALSE; }
该函数主要做了以下几步:
1.调用AMF_Decode()解码AMF命令数据
2.调用AMFProp_GetString()获取具体命令的字符串
3.调用AVMATCH()比较字符串,不同的命令做不同的处理,例如以下几个:
AVMATCH(&methodInvoked, &av_connect) AVMATCH(&methodInvoked, &av_createStream) AVMATCH(&methodInvoked, &av_play) AVMATCH(&methodInvoked, &av_publish) AVMATCH(&method, &av_onBWDone)
等等,不一一例举了
具体的处理过程如下所示。在这里说一个“建立网络流”(createStream)的例子,通常发生在建立网络连接(NetConnection)之后,播放(Play)之前。
else if (AVMATCH(&methodInvoked, &av_createStream)) { //---------------- r->dlg->AppendMLInfo(20,0,"命令消息","Result (CreateStream)"); //----------------------------- r->m_stream_id = (int)AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 3)); if (r->Link.protocol & RTMP_FEATURE_WRITE) { SendPublish(r); } else { if (r->Link.lFlags & RTMP_LF_PLST) SendPlaylist(r); //---------------- r->dlg->AppendCInfo("发送数据。消息 命令 (typeID=20) (Play)。"); //----------------------------- RTMP_LogPrintf("发送命令消息“play” (typeID=20)\n"); SendPlay(r); RTMP_SendCtrl(r, 3, r->m_stream_id, r->m_nBufferMS); } }
由代码可见,程序先获取了stream_id,然后发送了两个消息(Message),分别是SendPlaylist()和SendPlay(),用于获取播放列表,以及开始播放流媒体数据。
rtmpdump源代码(Linux):http://download.csdn.net/detail/leixiaohua1020/6376561
rtmpdump源代码(VC 2005 工程):http://download.csdn.net/detail/leixiaohua1020/6563163
相关推荐
首先,rtmpdump是一个开源命令行工具,用于从RTMP服务器上下载或播放流媒体内容。它支持RTMP、RTMPT、RTMPE、RTMPTE和RTMPS等多种协议变种。rtmpdump2.3是该工具的一个版本,可能包含了一些新特性或修复了某些已知...
对于想要深入了解网络流媒体技术或RTMP协议的开发者,rtmpdump源代码提供了一个很好的学习平台。你可以通过阅读和理解源代码,学习如何处理网络请求、解析复杂协议、实现流媒体的抓取和存储。同时,源代码也适用于...
在压缩包中的"rtmpdump-2.3"可能是rtmpdump的源代码或者编译好的二进制文件,用户可以进一步查看源码理解其工作原理,或者直接在Android设备上使用编译后的二进制文件进行流媒体操作。对于开发者来说,这为自定义...
rtmpdump是基于librtmp库构建的,主要功能包括连接RTMP服务器、发送控制命令、接收和保存流媒体数据。其命令行语法简洁,支持多种参数定制,如指定服务器地址、播放路径、输出文件等。 3. librtmp库: librtmp是...
librtmp 2.2是rtmpdump的一个关键组成部分,提供了对RTMP协议的底层支持。 **RTMP协议** RTMP是一种广泛应用于在线视频流传输的协议,由Adobe Systems开发。它允许内容提供商向用户实时传输音频、视频和其他数据。...
rtmpdump-2.4是一个强大的工具,尤其对需要抓取或回放RTMP流的开发者和爱好者来说。尽管需要手动编译,但其灵活性和实用性使其在Linux环境中具有很高的价值。记得在使用前了解RTMP协议和基本的命令行操作,这将有助...
rtmpdump是一个开源工具,主要用于从RTMP(Real-Time Messaging Protocol)流中捕获和下载媒体内容,如视频和音频。这个压缩包“rtmpdump.zip”包含了在Visual Studio环境中编译rtmpdump项目所需的所有关键文件,...
2. **编译好的动态库**:预编译的动态库文件(如librtmp.so)使得其他应用程序可以直接链接并使用rtmpdump的功能,而无需重新编译整个源代码。这对于开发基于rtmpdump的应用程序非常方便。 使用rtmpdump-2.3,你...
总的来说,rtmpdump是一个强大的工具,它简化了与RTMP服务器交互的过程,特别是在需要抓取和分析流媒体内容时。通过使用你提供的压缩包,用户可以避免编译问题,直接在VC2010环境中使用rtmpdump,这对于开发者和网络...
rtmpdump是一个开源工具,由Alexander Zoller创建,用于从RTMP服务器中抓取、录制或者回放流媒体内容。它可以用于下载在线视频,帮助网络管理员进行故障排查,或者对RTMP服务器进行测试。rtmpdump支持多个RTMP变种,...
rtmpdump是一款强大的开源工具,主要用于从RTMP(Real-Time Messaging Protocol)服务器上抓取流媒体内容。RTMP是一种广泛用于在线视频传输的协议,由Adobe Systems开发。rtmpdump的2.4版本是我们讨论的重点。 这个...
2. **获取流信息**:连接成功后,rtmpdump会获取流的元数据,包括视频和音频编码信息、流长度等。 3. **数据抓取**:rtmpdump持续接收来自服务器的数据包,并将它们写入本地文件。这些数据包包含了视频帧和音频帧,...
rtmpdump-v1.6.tar.gz是rtmpdump的一个特定版本,通过解压这个tar.gz压缩包,我们可以获取到rtmpdump的源代码和其他相关文件。在这个压缩包中,唯一列出的子文件名是"rtmpdump",这通常指的是rtmpdump的可执行程序...
在提供的压缩包文件`rtmp_send`中,可能包含了一个示例程序,演示了如何使用RTMPDump或其他相关工具进行视频推流。这个程序通常会包含设置RTMP连接、读取FLV数据、处理元数据和实时推流的主要逻辑。你可以通过阅读和...
rtmpdump 2.3 是一个开源工具,专为从RTMP(Real Time Messaging Protocol)流媒体服务器上抓取和下载点播视频文件而设计。它由Alexander Zerbe开发,广泛应用于网络视频内容的下载和分析。本文将详细探讨rtmpdump的...
例如,你可以创建一个RTMPClient类,封装RTMP连接、发布流、播放流等功能,调用librtmp的API来实现: 1. 初始化RTMP连接:使用`RTMP_Alloc()`和`RTMP_Init()`初始化RTMP对象,然后通过`RTMP_SetupURL()`设置服务器...
例如,要记录一个RTMP流,命令可能如下: ``` rtmpdump -r rtmp://server/live/stream -o output.flv ``` 这将把`rtmp://server/live/stream`的流保存到`output.flv`文件中。 **总结** 通过VC6.0编译的RTMPDump....
总之,rtmpdump.7z提供的源代码资源是学习和理解RTMP协议及流媒体处理的一个宝贵素材。通过阅读和分析源代码,开发者不仅可以提升网络编程技能,还能为自己的项目创建定制化的解决方案。同时,也要注意保持软件的...
rtmpdump是一个开源工具,主要用于从RTMP(Real Time Messaging Protocol)流中捕获和下载媒体数据,如视频和音频。这个压缩包“rtmpdump.zip”包含了使用C++编程语言编译通过的rtmpdump工程,适用于Visual Studio ...
2. **librtmp库**:rtmpdump基于librtmp库,这是一个C语言编写的库,提供了与RTMP服务器交互的API,包括连接、发送数据、接收数据和断开连接等功能。librtmp库不仅限于rtmpdump,也可以被其他项目集成,以实现RTMP...