rtmpdump总体流程
为了更加深入理解rtmp协议,下面分析一个开源的rtmp工具:rtmpdump。下载地址:https://github.com/aajanki/rtmpdump
rtmpdump实际就是一个下载工具,它会把URL中指定的资源下载下来
1、初始化
2、解析URL
3、打开文件(把URL资源保存到本地的文件)
4、建立连接
5、建立流
6、开始下载
7、下载完成,关闭连接,清理资源
下面是简化过的main函数,只保留核心的几个函数
int
main(int argc, char **argv)
{
// *** 只保留核心代码
// rtmp初始化
RTMP_Init(&rtmp);
// 参数解析
while ((opt =
getopt_long(argc, argv,
"hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:J:",
longopts, NULL)) != -1)
{
switch (opt)
{
case 'h':
usage(argv[0]);
return RD_SUCCESS;
case 'r':
{
AVal parsedHost, parsedApp, parsedPlaypath;
unsigned int parsedPort = 0;
int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
// URL解析
if (!RTMP_ParseURL
(optarg, &parsedProtocol, &parsedHost, &parsedPort,
&parsedPlaypath, &parsedApp))
{
RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
optarg);
}
else
{
if (!hostname.av_len)
hostname = parsedHost;
if (port == -1)
port = parsedPort;
if (playpath.av_len == 0 && parsedPlaypath.av_len)
{
playpath = parsedPlaypath;
}
if (protocol == RTMP_PROTOCOL_UNDEFINED)
protocol = parsedProtocol;
if (app.av_len == 0 && parsedApp.av_len)
{
app = parsedApp;
}
}
break;
}
default:
RTMP_LogPrintf("unknown option: %c\n", opt);
usage(argv[0]);
return RD_FAILED;
break;
}
}
// 启动流
RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
&tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
&flashVer, &subscribepath, &usherToken, &WeebToken, dSeek, dStopOffset, bLiveStream, timeout);
if (!file)
{
if (bStdoutMode)
{
file = stdout;
SET_BINMODE(file);
}
else
{
file = fopen(flvFile, "w+b");
if (file == 0)
{
RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
return RD_FAILED;
}
}
}
while (!RTMP_ctrlC)
{
RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
RTMP_SetBufferMS(&rtmp, bufferTime);
if (first)
{
first = 0;
RTMP_LogPrintf("Connecting ...\n");
// 建立连接
if (!RTMP_Connect(&rtmp, NULL))
{
nStatus = RD_NO_CONNECT;
break;
}
RTMP_Log(RTMP_LOGINFO, "Connected...");
// User defined seek offset
if (dStartOffset > 0)
{
// Don't need the start offset if resuming an existing file
if (bResume)
{
RTMP_Log(RTMP_LOGWARNING,
"Can't seek a resumed stream, ignoring --start option");
dStartOffset = 0;
}
else
{
dSeek = dStartOffset;
}
}
// Calculate the length of the stream to still play
if (dStopOffset > 0)
{
// Quit if start seek is past required stop offset
if (dStopOffset <= dSeek)
{
RTMP_LogPrintf("Already Completed\n");
nStatus = RD_SUCCESS;
break;
}
}
// 建立流连接
if (!RTMP_ConnectStream(&rtmp, dSeek))
{
nStatus = RD_FAILED;
break;
}
}
else
{
nInitialFrameSize = 0;
if (retries)
{
RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
if (!RTMP_IsTimedout(&rtmp))
nStatus = RD_FAILED;
else
nStatus = RD_INCOMPLETE;
break;
}
RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n");
/* Did we already try pausing, and it still didn't work? */
if (rtmp.m_pausing == 3)
{
/* Only one try at reconnecting... */
retries = 1;
dSeek = rtmp.m_pauseStamp;
if (dStopOffset > 0)
{
if (dStopOffset <= dSeek)
{
RTMP_LogPrintf("Already Completed\n");
nStatus = RD_SUCCESS;
break;
}
}
if (!RTMP_ReconnectStream(&rtmp, dSeek))
{
RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
if (!RTMP_IsTimedout(&rtmp))
nStatus = RD_FAILED;
else
nStatus = RD_INCOMPLETE;
break;
}
}
else if (!RTMP_ToggleStream(&rtmp))
{
RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
if (!RTMP_IsTimedout(&rtmp))
nStatus = RD_FAILED;
else
nStatus = RD_INCOMPLETE;
break;
}
bResume = TRUE;
}
// 下载
nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
metaHeader, nMetaHeaderSize, initialFrame,
initialFrameType, nInitialFrameSize,
nSkipKeyFrames, bStdoutMode, bLiveStream, bHashes,
bOverrideBufferTime, bufferTime, &percent);
free(initialFrame);
initialFrame = NULL;
/* If we succeeded, we're done.
*/
if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
break;
}
if (nStatus == RD_SUCCESS)
{
RTMP_LogPrintf("Download complete\n");
}
else if (nStatus == RD_INCOMPLETE)
{
RTMP_LogPrintf
("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
percent);
}
clean:
// 关闭,清理
RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
RTMP_Close(&rtmp);
if (file != 0)
fclose(file);
CleanupSockets();
#ifdef _DEBUG
if (netstackdump != 0)
fclose(netstackdump);
if (netstackdump_read != 0)
fclose(netstackdump_read);
#endif
return nStatus;
}