RTMPdump 原始碼分析 1: main()函式
阿新 • • 發佈:2019-02-01
=====================================================
RTMPdump(libRTMP) 原始碼分析系列文章:
=====================================================
rtmpdump 是一個用來處理 RTMP 流媒體的工具包,支援 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps:// 等。之前在學習RTMP協議的時候,發現沒有講它原始碼的,只好自己分析,現在打算把自己學習的成果寫出來,可能結果不一定都對,先暫且記錄一下。
函式呼叫結構圖
RTMPDump (libRTMP)的整體的函式呼叫結構圖如下圖所示。
詳細分析
使用RTMPdump下載一個流媒體的大致流程是這樣的:
RTMP_Init();//初始化結構體
InitSockets();//初始化Socket
RTMP_ParseURL();//解析輸入URL
RTMP_SetupStream();//一些設定
fopen();//開啟檔案,準備寫入
RTMP_Connect();//建立NetConnection
RTMP_ConnectStream()//建立NetStream
Download();//下載函式
RTMP_Close();//關閉連線
fclose();//關閉檔案
CleanupSockets();//清理Socket
其中Download()主要是使用RTMP_Read()進行下載的。
下面貼上自己註釋的RTMPDump原始碼。注意以下幾點:
1.此RTMPDump已經被移植進VC 2010 的 MFC的工程,所以main()函式已經被改名為rtmpdump(),而且引數也改了,傳進來一個MFC視窗的控制代碼。不過功能沒怎麼改(控制檯程式移植到MFC以後,main()就不是程式的入口了,所以main()名字改成什麼是無所謂的)
2.裡面有很多提取資訊的程式碼形如:rtmp.dlg->AppendCInfo("開始初始化Socket...");這些程式碼是我為了獲取RTMP資訊而自己加的,並不影響程式的執行。
int rtmpdump(LPVOID lpParam,int argc,char **argv) { extern char *optarg; //一定要設定,否則只能執行一次 extern int optind; optind=0; int nStatus = RD_SUCCESS; double percent = 0; double duration = 0.0; int nSkipKeyFrames = DEF_SKIPFRM; // skip this number of keyframes when resuming int bOverrideBufferTime = FALSE; // if the user specifies a buffer time override this is true int bStdoutMode = TRUE; // if true print the stream directly to stdout, messages go to stderr int bResume = FALSE; // true in resume mode uint32_t dSeek = 0; // seek position in resume mode, 0 otherwise uint32_t bufferTime = DEF_BUFTIME; // meta header and initial frame for the resume mode (they are read from the file and compared with // the stream we are trying to continue char *metaHeader = 0; uint32_t nMetaHeaderSize = 0; // video keyframe for matching char *initialFrame = 0; uint32_t nInitialFrameSize = 0; int initialFrameType = 0; // tye: audio or video AVal hostname = { 0, 0 }; AVal playpath = { 0, 0 }; AVal subscribepath = { 0, 0 }; int port = -1; int protocol = RTMP_PROTOCOL_UNDEFINED; int retries = 0; int bLiveStream = FALSE; // 是直播流嗎? then we can't seek/resume int bHashes = FALSE; // display byte counters not hashes by default long int timeout = DEF_TIMEOUT; // timeout connection after 120 seconds uint32_t dStartOffset = 0; // 非直播流搜尋點seek position in non-live mode uint32_t dStopOffset = 0; RTMP rtmp = { 0 }; AVal swfUrl = { 0, 0 }; AVal tcUrl = { 0, 0 }; AVal pageUrl = { 0, 0 }; AVal app = { 0, 0 }; AVal auth = { 0, 0 }; AVal swfHash = { 0, 0 }; uint32_t swfSize = 0; AVal flashVer = { 0, 0 }; AVal sockshost = { 0, 0 }; #ifdef CRYPTO int swfAge = 30; /* 30 days for SWF cache by default */ int swfVfy = 0; unsigned char hash[RTMP_SWF_HASHLEN]; #endif char *flvFile = 0; signal(SIGINT, sigIntHandler); signal(SIGTERM, sigIntHandler); #ifndef WIN32 signal(SIGHUP, sigIntHandler); signal(SIGPIPE, sigIntHandler); signal(SIGQUIT, sigIntHandler); #endif RTMP_debuglevel = RTMP_LOGINFO; //首先搜尋“ --quiet”選項 int index = 0; while (index < argc) { if (strcmp(argv[index], "--quiet") == 0 || strcmp(argv[index], "-q") == 0) RTMP_debuglevel = RTMP_LOGCRIT; index++; } #define RTMPDUMP_VERSION "1.0" RTMP_LogPrintf("RTMP流媒體下載 %s\n", RTMPDUMP_VERSION); RTMP_LogPrintf ("2012 雷霄驊 中國傳媒大學/資訊工程學院/通訊與資訊系統/數字電視技術\n"); //RTMP_LogPrintf("輸入 -h 獲取命令選項\n"); RTMP_Init(&rtmp); //控制代碼----------------------------- rtmp.dlg=(CSpecialPRTMPDlg *)lpParam; //--------------------------------- //---------------------- rtmp.dlg->AppendCInfo("開始初始化Socket..."); //----------------------------- if (!InitSockets()) { //---------------------- rtmp.dlg->AppendCInfo("初始化Socket失敗!"); //----------------------------- RTMP_Log(RTMP_LOGERROR, "Couldn't load sockets support on your platform, exiting!"); return RD_FAILED; } //---------------------- rtmp.dlg->AppendCInfo("成功初始化Socket"); //----------------------------- /* sleep(30); */ int opt; /* struct option longopts[] = { {"help", 0, NULL, 'h'}, {"host", 1, NULL, 'n'}, {"port", 1, NULL, 'c'}, {"socks", 1, NULL, 'S'}, {"protocol", 1, NULL, 'l'}, {"playpath", 1, NULL, 'y'}, {"playlist", 0, NULL, 'Y'}, {"rtmp", 1, NULL, 'r'}, {"swfUrl", 1, NULL, 's'}, {"tcUrl", 1, NULL, 't'}, {"pageUrl", 1, NULL, 'p'}, {"app", 1, NULL, 'a'}, {"auth", 1, NULL, 'u'}, {"conn", 1, NULL, 'C'}, #ifdef CRYPTO {"swfhash", 1, NULL, 'w'}, {"swfsize", 1, NULL, 'x'}, {"swfVfy", 1, NULL, 'W'}, {"swfAge", 1, NULL, 'X'}, #endif {"flashVer", 1, NULL, 'f'}, {"live", 0, NULL, 'v'}, {"flv", 1, NULL, 'o'}, {"resume", 0, NULL, 'e'}, {"timeout", 1, NULL, 'm'}, {"buffer", 1, NULL, 'b'}, {"skip", 1, NULL, 'k'}, {"subscribe", 1, NULL, 'd'}, {"start", 1, NULL, 'A'}, {"stop", 1, NULL, 'B'}, {"token", 1, NULL, 'T'}, {"hashes", 0, NULL, '#'}, {"debug", 0, NULL, 'z'}, {"quiet", 0, NULL, 'q'}, {"verbose", 0, NULL, 'V'}, {0, 0, 0, 0} };*/ //分析命令列引數,注意用法。 //選項都是一個字母,後面有冒號的代表該選項還有相關引數 //一直迴圈直到獲取所有的opt 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:#"/*, longopts, NULL*/)) != -1) { //不同的選項做不同的處理 switch (opt) { case 'h': usage(argv[0]); return RD_SUCCESS; #ifdef CRYPTO case 'w': { int res = hex2bin(optarg, &swfHash.av_val); if (res != RTMP_SWF_HASHLEN) { swfHash.av_val = NULL; RTMP_Log(RTMP_LOGWARNING, "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN); } swfHash.av_len = RTMP_SWF_HASHLEN; break; } case 'x': { int size = atoi(optarg); if (size <= 0) { RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n"); } else { swfSize = size; } break; } case 'W': STR2AVAL(swfUrl, optarg); swfVfy = 1; break; case 'X': { int num = atoi(optarg); if (num < 0) { RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n"); } else { swfAge = num; } } break; #endif case 'k': nSkipKeyFrames = atoi(optarg); if (nSkipKeyFrames < 0) { RTMP_Log(RTMP_LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!"); nSkipKeyFrames = 0; } else { RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d", nSkipKeyFrames); } break; case 'b': { int32_t bt = atol(optarg); if (bt < 0) { RTMP_Log(RTMP_LOGERROR, "Buffer time must be greater than zero, ignoring the specified value %d!", bt); } else { bufferTime = bt; bOverrideBufferTime = TRUE; } break; } //直播流 case 'v': //---------------- rtmp.dlg->AppendCInfo("該RTMP的URL是一個直播流"); //---------------- bLiveStream = TRUE; // no seeking or resuming possible! break; case 'd': STR2AVAL(subscribepath, optarg); break; case 'n': STR2AVAL(hostname, optarg); break; case 'c': port = atoi(optarg); break; case 'l': protocol = atoi(optarg); if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS) { RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol); return RD_FAILED; } break; case 'y': STR2AVAL(playpath, optarg); break; case 'Y': RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true); break; //路徑引數-r case 'r': { AVal parsedHost, parsedApp, parsedPlaypath; unsigned int parsedPort = 0; int parsedProtocol = RTMP_PROTOCOL_UNDEFINED; //解析URL。注optarg指向引數(URL) RTMP_LogPrintf("RTMP URL : %s\n",optarg); //---------------- rtmp.dlg->AppendCInfo("解析RTMP的URL..."); //---------------- if (!RTMP_ParseURL (optarg, &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp)) { //---------------- rtmp.dlg->AppendCInfo("解析RTMP的URL失敗!"); //---------------- RTMP_Log(RTMP_LOGWARNING, "無法解析 url (%s)!", optarg); } else { //---------------- rtmp.dlg->AppendCInfo("解析RTMP的URL成功"); //---------------- //把解析出來的資料賦值 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; } case 's': STR2AVAL(swfUrl, optarg); break; case 't': STR2AVAL(tcUrl, optarg); break; case 'p': STR2AVAL(pageUrl, optarg); break; case 'a': STR2AVAL(app, optarg); break; case 'f': STR2AVAL(flashVer, optarg); break; //指定輸出檔案 case 'o': flvFile = optarg; if (strcmp(flvFile, "-")) bStdoutMode = FALSE; break; case 'e': bResume = TRUE; break; case 'u': STR2AVAL(auth, optarg); break; case 'C': { AVal av; STR2AVAL(av, optarg); if (!RTMP_SetOpt(&rtmp, &av_conn, &av)) { RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg); return RD_FAILED; } } break; case 'm': timeout = atoi(optarg); break; case 'A': dStartOffset = (int) (atof(optarg) * 1000.0); break; case 'B': dStopOffset = (int) (atof(optarg) * 1000.0); break; case 'T': { AVal token; STR2AVAL(token, optarg); RTMP_SetOpt(&rtmp, &av_token, &token); } break; case '#': bHashes = TRUE; break; case 'q': RTMP_debuglevel = RTMP_LOGCRIT; break; case 'V': RTMP_debuglevel = RTMP_LOGDEBUG; break; case 'z': RTMP_debuglevel = RTMP_LOGALL; break; case 'S': STR2AVAL(sockshost, optarg); break; default: RTMP_LogPrintf("unknown option: %c\n", opt); usage(argv[0]); return RD_FAILED; break; } } if (!hostname.av_len) { RTMP_Log(RTMP_LOGERROR, "您必須指定 主機名(hostname) (--host) 或 url (-r \"rtmp://host[:port]/playpath\") 包含 a hostname"); return RD_FAILED; } if (playpath.av_len == 0) { RTMP_Log(RTMP_LOGERROR, "您必須指定 播放路徑(playpath) (--playpath) 或 url (-r \"rtmp://host[:port]/playpath\") 包含 a playpath"); return RD_FAILED; } if (protocol == RTMP_PROTOCOL_UNDEFINED) { RTMP_Log(RTMP_LOGWARNING, "您沒有指定 協議(protocol) (--protocol) 或 rtmp url (-r), 預設協議 RTMP"); protocol = RTMP_PROTOCOL_RTMP; } if (port == -1) { RTMP_Log(RTMP_LOGWARNING, "您沒有指定 埠(port) (--port) 或 rtmp url (-r), 預設埠 1935"); port = 0; } if (port == 0) { if (protocol & RTMP_FEATURE_SSL) port = 443; else if (protocol & RTMP_FEATURE_HTTP) port = 80; else port = 1935; } if (flvFile == 0) { RTMP_Log(RTMP_LOGWARNING, "請指定一個輸出檔案 (-o filename), using stdout"); bStdoutMode = TRUE; } if (bStdoutMode && bResume) { RTMP_Log(RTMP_LOGWARNING, "Can't resume in stdout mode, ignoring --resume option"); bResume = FALSE; } if (bLiveStream && bResume) { RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option"); bResume = FALSE; } #ifdef CRYPTO if (swfVfy) { if (RTMP_HashSWF(swfUrl.av_val, (unsigned int *)&swfSize, hash, swfAge) == 0) { swfHash.av_val = (char *)hash; swfHash.av_len = RTMP_SWF_HASHLEN; } } if (swfHash.av_len == 0 && swfSize > 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash"); swfSize = 0; } if (swfHash.av_len != 0 && swfSize == 0) { RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF hash, supply also the swf size with --swfsize"); swfHash.av_len = 0; swfHash.av_val = NULL; } #endif if (tcUrl.av_len == 0) { char str[512] = { 0 }; tcUrl.av_len = snprintf(str, 511, "%s://%.*s:%d/%.*s", RTMPProtocolStringsLower[protocol], hostname.av_len, hostname.av_val, port, app.av_len, app.av_val); tcUrl.av_val = (char *) malloc(tcUrl.av_len + 1); strcpy(tcUrl.av_val, str); } int first = 1; // User defined seek offset if (dStartOffset > 0) { //直播流 if (bLiveStream) { RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option"); dStartOffset = 0; } } //---------------- rtmp.dlg->AppendCInfo("開始初始化RTMP連線的引數..."); //---------------- //設定 RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath, &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize, &flashVer, &subscribepath, dSeek, dStopOffset, bLiveStream, timeout); //此處設定引數----------------- rtmp.dlg->AppendCInfo("成功初始化RTMP連線的引數"); //----------------------------- char *temp=(char *)malloc(MAX_URL_LENGTH); memcpy(temp,rtmp.Link.hostname.av_val,rtmp.Link.hostname.av_len); temp[rtmp.Link.hostname.av_len]='\0'; rtmp.dlg->AppendB_R_L_Info("主機名",temp); itoa(rtmp.Link.port,temp,10); rtmp.dlg->AppendB_R_L_Info("埠號",temp); memcpy(temp,rtmp.Link.app.av_val,rtmp.Link.app.av_len); temp[rtmp.Link.app.av_len]='\0'; rtmp.dlg->AppendB_R_L_Info("應用程式",temp); memcpy(temp,rtmp.Link.playpath.av_val,rtmp.Link.playpath.av_len); temp[rtmp.Link.playpath.av_len]='\0'; rtmp.dlg->AppendB_R_L_Info("路徑",temp); //----------------------------- /* Try to keep the stream moving if it pauses on us */ if (!bLiveStream && !(protocol & RTMP_FEATURE_HTTP)) rtmp.Link.lFlags |= RTMP_LF_BUFX; off_t size = 0; // ok,我們必須獲得timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) if (bResume) { //開啟檔案,輸出的檔案(Resume) nStatus = OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize, &duration); if (nStatus == RD_FAILED) goto clean; if (!file) { // file does not exist, so go back into normal mode bResume = FALSE; // we are back in fresh file mode (otherwise finalizing file won't be done) } else { //獲取最後一個關鍵幀 nStatus = GetLastKeyframe(file, nSkipKeyFrames, &dSeek, &initialFrame, &initialFrameType, &nInitialFrameSize); if (nStatus == RD_FAILED) { RTMP_Log(RTMP_LOGDEBUG, "Failed to get last keyframe."); goto clean; } if (dSeek == 0) { RTMP_Log(RTMP_LOGDEBUG, "Last keyframe is first frame in stream, switching from resume to normal mode!"); bResume = FALSE; } } } //如果輸出檔案不存在 if (!file) { if (bStdoutMode) { //直接輸出到stdout file = stdout; SET_BINMODE(file); } else { //開啟一個檔案 //w+b 讀寫開啟或建立一個二進位制檔案,允許讀和寫。 //----------------- rtmp.dlg->AppendCInfo("建立輸出檔案..."); //----------------------------- file = fopen(flvFile, "w+b"); if (file == 0) { //----------------- rtmp.dlg->AppendCInfo("建立輸出檔案失敗!"); //----------------------------- RTMP_LogPrintf("Failed to open file! %s\n", flvFile); return RD_FAILED; } rtmp.dlg->AppendCInfo("成功建立輸出檔案"); } } #ifdef _DEBUG netstackdump = fopen("netstackdump", "wb"); netstackdump_read = fopen("netstackdump_read", "wb"); #endif while (!RTMP_ctrlC) { RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime); //設定Buffer時間 //----------------- rtmp.dlg->AppendCInfo("設定緩衝(Buffer)的時間"); //----------------------------- RTMP_SetBufferMS(&rtmp, bufferTime); //第一次執行 if (first) { first = 0; RTMP_LogPrintf("開始建立連線!\n"); //----------------- rtmp.dlg->AppendCInfo("開始建立連線(NetConnection)..."); //----------------------------- //建立連線(Connect) if (!RTMP_Connect(&rtmp, NULL)) { //----------------- rtmp.dlg->AppendCInfo("建立連線(NetConnection)失敗!"); //----------------------------- nStatus = RD_FAILED; break; } //----------------- rtmp.dlg->AppendCInfo("成功建立連線(NetConnection)"); //----------------------------- //RTMP_Log(RTMP_LOGINFO, "已連結..."); // 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; } } //建立流(Stream)(傳送connect命令訊息後處理傳來的資料) itoa(rtmp.m_inChunkSize,temp,10); rtmp.dlg->AppendB_R_Info("輸入Chunk大小",temp); itoa(rtmp.m_outChunkSize,temp,10); rtmp.dlg->AppendB_R_Info("輸出Chunk大小",temp); itoa(rtmp.m_stream_id,temp,10); rtmp.dlg->AppendB_R_Info("Stream ID",temp); itoa(rtmp.m_nBufferMS,temp,10); rtmp.dlg->AppendB_R_Info("Buffer時長(ms)",temp); itoa(rtmp.m_nServerBW,temp,10); rtmp.dlg->AppendB_R_Info("ServerBW",temp); itoa(rtmp.m_nClientBW,temp,10); rtmp.dlg->AppendB_R_Info("ClientBW",temp); itoa((int)rtmp.m_fEncoding,temp,10); rtmp.dlg->AppendB_R_Info("命令訊息編碼方法",temp); itoa((int)rtmp.m_fDuration,temp,10); rtmp.dlg->AppendB_R_Info("時長(s)",temp); rtmp.dlg->ShowBInfo(); free(temp); //----------------- rtmp.dlg->AppendCInfo("開始建立網路流(NetStream)"); //----------------------------- if (!RTMP_ConnectStream(&rtmp, dSeek)) { //----------------- rtmp.dlg->AppendCInfo("建立網路流(NetStream)失敗!"); //----------------- nStatus = RD_FAILED; break; } //----------------- rtmp.dlg->AppendCInfo("成功建立網路流(NetStream)!"); //----------------- } 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; } //----------------- //----------------- rtmp.dlg->AppendCInfo("開始將媒體資料寫入檔案"); //----------------- //下載,寫入檔案 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.dlg->AppendCInfo("寫入檔案完成"); //----------------- RTMP_LogPrintf("Download complete\n"); } //沒下載完的時候 else if (nStatus == RD_INCOMPLETE) { //----------------- rtmp.dlg->AppendCInfo("寫入檔案可能不完整"); //----------------- RTMP_LogPrintf ("Download may be incomplete (downloaded about %.2f%%), try resuming\n", percent); } //後續清理工作 clean: //----------------- rtmp.dlg->AppendCInfo("關閉連線"); //----------------- RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n"); RTMP_Close(&rtmp); rtmp.dlg->AppendCInfo("關閉檔案"); if (file != 0) fclose(file); rtmp.dlg->AppendCInfo("關閉Socket"); CleanupSockets(); #ifdef _DEBUG if (netstackdump != 0) fclose(netstackdump); if (netstackdump_read != 0) fclose(netstackdump_read); #endif return nStatus; }
其中InitSocket()程式碼很簡單,初始化了Socket,如下:
// 初始化 sockets
int
InitSockets()
{
#ifdef WIN32
WORD version;
WSADATA wsaData;
version = MAKEWORD(1, 1);
return (WSAStartup(version, &wsaData) == 0);
#else
return TRUE;
#endif
}
CleanupSockets()則更簡單:
inline void
CleanupSockets()
{
#ifdef WIN32
WSACleanup();
#endif
}
Download()函式則比較複雜:
int
Download(RTMP * rtmp, // connected RTMP object
FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, int bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent) // percentage downloaded [out]
{
int32_t now, lastUpdate;
int bufferSize = 64 * 1024;
char *buffer = (char *) malloc(bufferSize);
int nRead = 0;
//long ftell(FILE *stream);
//返回當前檔案指標
RTMP_LogPrintf("開始下載!\n");
off_t size = ftello(file);
unsigned long lastPercent = 0;
//時間戳
rtmp->m_read.timestamp = dSeek;
*percent = 0.0;
if (rtmp->m_read.timestamp)
{
RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
}
//是直播
if (bLiveStream)
{
RTMP_LogPrintf("直播流\n");
}
else
{
// print initial status
// Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
if (duration > 0)
{
if ((double) rtmp->m_read.timestamp >= (double) duration * 999.0)
{
RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
(double) rtmp->m_read.timestamp / 1000.0,
(double) duration / 1000.0);
return RD_SUCCESS;
}
else
{
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
bResume ? "Resuming" : "Starting",
(double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,
*percent);
}
}
else
{
RTMP_LogPrintf("%s download at: %.3f kB\n",
bResume ? "Resuming" : "Starting",
(double) size / 1024.0);
}
}
if (dStopOffset > 0)
RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);
//各種設定引數到rtmp連線
if (bResume && nInitialFrameSize > 0)
rtmp->m_read.flags |= RTMP_READ_RESUME;
rtmp->m_read.initialFrameType = initialFrameType;
rtmp->m_read.nResumeTS = dSeek;
rtmp->m_read.metaHeader = metaHeader;
rtmp->m_read.initialFrame = initialFrame;
rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize;
rtmp->m_read.nInitialFrameSize = nInitialFrameSize;
now = RTMP_GetTime();
lastUpdate = now - 1000;
do
{
//從rtmp中把bufferSize(64k)個數據讀入buffer
nRead = RTMP_Read(rtmp, buffer, bufferSize);
//RTMP_LogPrintf("nRead: %d\n", nRead);
if (nRead > 0)
{
//函式:size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream);
//向檔案讀入寫入一個數據塊。返回值:返回實際寫入的資料塊數目
//(1)buffer:是一個指標,對fwrite來說,是要輸出資料的地址。
//(2)size:要寫入內容的單位元組數;
//(3)count:要進行寫入size位元組的資料項的個數;
//(4)stream:目標檔案指標。
//(5)返回實際寫入的資料項個數count。
//關鍵。把buffer裡面的資料寫成檔案
if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
(size_t) nRead)
{
RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
free(buffer);
return RD_FAILED;
}
//記錄已經寫入的位元組數
size += nRead;
//RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
if (duration <= 0) // if duration unknown try to get it from the stream (onMetaData)
duration = RTMP_GetDuration(rtmp);
if (duration > 0)
{
// make sure we claim to have enough buffer time!
if (!bOverrideBufferTime && bufferTime < (duration * 1000.0))
{
bufferTime = (uint32_t) (duration * 1000.0) + 5000; // 再加5s以確保buffertime足夠長
RTMP_Log(RTMP_LOGDEBUG,
"Detected that buffer time is less than duration, resetting to: %dms",
bufferTime);
//重設Buffer長度
RTMP_SetBufferMS(rtmp, bufferTime);
//給伺服器傳送UserControl訊息通知Buffer改變
RTMP_UpdateBufferMS(rtmp);
}
//計算百分比
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
if (bHashes)
{
if (lastPercent + 1 <= *percent)
{
RTMP_LogStatus("#");
lastPercent = (unsigned long) *percent;
}
}
else
{
//設定顯示資料的更新間隔200ms
now = RTMP_GetTime();
if (abs(now - lastUpdate) > 200)
{
RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
(double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0, *percent);
lastUpdate = now;
}
}
}
else
{
//現在距離開機的毫秒數
now = RTMP_GetTime();
//每間隔200ms重新整理一次資料
if (abs(now - lastUpdate) > 200)
{
if (bHashes)
RTMP_LogStatus("#");
else
//size為已寫入檔案的位元組數
RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0);
lastUpdate = now;
}
}
}
#ifdef _DEBUG
else
{
RTMP_Log(RTMP_LOGDEBUG, "zero read!");
}
#endif
}
while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
free(buffer);
if (nRead < 0)
//nRead是讀取情況
nRead = rtmp->m_read.status;
/* Final status update */
if (!bHashes)
{
if (duration > 0)
{
*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
*percent = ((double) (int) (*percent * 10.0)) / 10.0;
//輸出
RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
(double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0, *percent);
}
else
{
RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
(double) (rtmp->m_read.timestamp) / 1000.0);
}
}
RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);
//讀取錯誤
if (bResume && nRead == -2)
{
RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
nSkipKeyFrames + 1);
return RD_FAILED;
}
//讀取正確
if (nRead == -3)
return RD_SUCCESS;
//沒讀完...
if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
|| RTMP_IsTimedout(rtmp))
{
return RD_INCOMPLETE;
}
return RD_SUCCESS;
}
以上內容是我能理解到的rtmpdump.c裡面的內容。