源码阅读 rtmpdump - 主函数解析
main
函数位于源码文件 rtmpdump.c
中,是 rtmpdump
工具的入口函数。
main 函数源码
由于 main
函数较长,本文对其中部分内容进行了裁剪,比如以下不会走到的 if
、case
分支等。
假设我们执行的命令为:rtmpdump -r rtmp://62.234.111.13:1935/mylive/1 -o Hello.flv
,该条命令涉及到的函数流程均进行保留,其他流程分支请参考源代码。
int main(int argc, char **argv) {
if (!InitSockets()) {
RTMP_Log(RTMP_LOGERROR,
"Couldn't load sockets support on your platform, exiting!");
return RD_FAILED;
}
RTMP_Init(&rtmp);
while ((opt = getopt_long(argc, argv,
"hVveqzRr:s:t:i:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#j:",
longopts, NULL)) != -1) {
switch (opt) {
case 'i':
STR2AVAL(fullUrl, optarg);
break;
case 'o':
flvFile = optarg;
if (strcmp(flvFile, "-")) bStdoutMode = FALSE;
break;
default:
RTMP_LogPrintf("unknown option: %c\n", opt);
usage(argv[0]);
return RD_FAILED;
break;
}
}
if (port == 0 && !fullUrl.av_len) {
if (protocol & RTMP_FEATURE_SSL) port = 443;
else if (protocol & RTMP_FEATURE_HTTP) port = 80;
else port = 1935;
}
if (tcUrl.av_len == 0)
{
tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
hostname.av_len + app.av_len + sizeof("://:65535/");
tcUrl.av_val = (char *) malloc(tcUrl.av_len);
if (!tcUrl.av_val) return RD_FAILED;
tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
RTMPProtocolStringsLower[protocol], hostname.av_len,
hostname.av_val, port, app.av_len, app.av_val);
}
if (!fullUrl.av_len) {
RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
&tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
&flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
}
else {
if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE) {
RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val);
return RD_FAILED;
}
}
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;
}
}
}
int first = 1;
while (!RTMP_ctrlC) {
RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
RTMP_SetBufferMS(&rtmp, bufferTime);
if (first) {
first = 0;
if (!RTMP_Connect(&rtmp, NULL)) {
nStatus = RD_NO_CONNECT;
break;
}
// 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, bRealtimeStream, 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();
return nStatus;
}
main 函数流程
从上述源码中可以看出,main
函数的大致流程为:
- InitSockets();
- RTMP_Init();
- RTMP_SetupStream()/RTMP_SetupURL();
- fopen();
- RTMP_Connect();
- RTMP_ConnectStream()
- Download();
- RTMP_Close();
- fclose();
- CleanupSockets();
其中 InitSockets
函数和 CleanupSockets
较为简单,在 Windows
平台下仅仅做了 socket
的初始化和反初始化工作,在 Linux
平台下甚至什么都没有做就 return
了:
int InitSockets() {
#ifdef WIN32
WORD version;
WSADATA wsaData;
version = MAKEWORD(1, 1);
return (WSAStartup(version, &wsaData) == 0);
#else
return TRUE;
#endif
}
inline void CleanupSockets() {
#ifdef WIN32
WSACleanup();
#endif
}
main 函数关键函数
由 main 函数流程可知,以下函数是理解 RTMPDump 源码的关键:
- RTMP_Init();
- RTMP_SetupStream()/RTMP_SetupURL();
- RTMP_Connect();
- RTMP_ConnectStream()
- Download();
- RTMP_Close();
由于每个函数进行分析所占用篇幅较大,以上函数的解析会在后续文章中推出,欢迎持续关注本博客。