1. 程式人生 > >RTMPDump原始碼分析-main函式(1)

RTMPDump原始碼分析-main函式(1)


RTMPDump.c原始碼中的main函式主要是:

InitSockets();//初始化Socket  
RTMP_Init();//初始化結構體  
RTMP_ParseURL();//解析輸入URL  
RTMP_SetupStream();//一些設定  
fopen();//開啟檔案,準備寫入  
RTMP_Connect();//建立NetConnection  
RTMP_ConnectStream()//建立NetStream  
Download();//下載函式  
RTMP_Close();//關閉連線  
fclose();//關閉檔案  
CleanupSockets();//清理Socket

1. InitiSocket和 CleanupSocket對於Linux來說什麼也沒有做
// starts sockets
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
}

2. 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 bRealtimeStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent)	// percentage downloaded [out]
{
  int32_t now, lastUpdate;
  int bufferSize = 64 * 1024;
  char *buffer;
  int nRead = 0;
  // 返回當前檔案指標
  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("Starting Live Stream\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 (bRealtimeStream)
	RTMP_LogPrintf("  in approximately realtime (disabled BUFX speedup hack)\n");
    }

  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;

  buffer = (char *) malloc(bufferSize);

  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))
		{
		  // 再加5s以確保buffertime足夠長 
		  bufferTime = (uint32_t) (duration * 1000.0) + 5000;	// extra 5sec to make sure we've got enough

		  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)
		{
		  // size為已寫入檔案的位元組數  
		  if (bHashes)
		    RTMP_LogStatus("#");
		  else
		    RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
			      (double) (rtmp->m_read.timestamp) / 1000.0);
		  lastUpdate = now;
		}
	  }
	}
      else
	{
#ifdef _DEBUG
	  RTMP_Log(RTMP_LOGDEBUG, "zero read!");
#endif
	  if (rtmp->m_read.status == RTMP_READ_EOF)
	    break;
	}

    }
  while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
  free(buffer);
  if (nRead < 0)
    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;
}


3. main函式

int
main(int argc, char **argv)
{
  ...
  // 最先執行開始檢查引數裡面是否有退出
  // Check for --quiet option before printing any output
  int index = 0;
  while (index < argc)
    {
      if (strcmp(argv[index], "--quiet") == 0
	  || strcmp(argv[index], "-q") == 0)
	RTMP_debuglevel = RTMP_LOGCRIT;
      index++;
    }

  RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
  RTMP_LogPrintf
    ("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");
    if (!InitSockets())
    {
      RTMP_Log(RTMP_LOGERROR,
	  "Couldn't load sockets support on your platform, exiting!");
      return RD_FAILED;
    }

  /* sleep(30); */

  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)
  {
	...
  }
  ...
  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;
	    }
    }
  ...
  // 找到最後的關鍵幀
  // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
  if (bResume)
  {
      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)
	{
	  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);
	  ...
	  // 在此處下載進行
      nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
			 metaHeader, nMetaHeaderSize, initialFrame,
			 initialFrameType, nInitialFrameSize, nSkipKeyFrames,
			 bStdoutMode, bLiveStream, bRealtimeStream, bHashes,
			 bOverrideBufferTime, bufferTime, &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;
}