Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7775758
  • 博文数量: 701
  • 博客积分: 2150
  • 博客等级: 上尉
  • 技术积分: 13233
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-29 16:28
个人简介

天行健,君子以自强不息!

文章分类

全部博文(701)

文章存档

2019年(2)

2018年(12)

2017年(76)

2016年(120)

2015年(178)

2014年(129)

2013年(123)

2012年(61)

分类: 网络与安全

2014-02-05 22:38:09

RTMPdump 源代码分析 1: main()函数


rtmpdump 是一个用来处理 RTMP 流媒体的工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps:// 等。
之前在学习RTMP协议的时候,发现没有讲它源代码的,只好自己分析,现在打算把自己学习的成果写出来,可能结果不一定都对,
先暂且记录一下。




使用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()进行下载的。


注:可以参考:RTMP流媒体播放过程


下面贴上自己注释的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里面的内容。


原文链接:
http://blog.csdn.net/leixiaohua1020/article/details/12952977
阅读(2886) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~