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

RTMPdump 原始碼分析 1: main()函式

=====================================================

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裡面的內容。