rtmpdump代码分析 转
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
rtmpdump代码分析 转的更多相关文章
- 转:RTMPDump源代码分析
0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...
- Android代码分析工具lint学习
1 lint简介 1.1 概述 lint是随Android SDK自带的一个静态代码分析工具.它用来对Android工程的源文件进行检查,找出在正确性.安全.性能.可使用性.可访问性及国际化等方面可能 ...
- pmd静态代码分析
在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...
- [Asp.net 5] DependencyInjection项目代码分析-目录
微软DI文章系列如下所示: [Asp.net 5] DependencyInjection项目代码分析 [Asp.net 5] DependencyInjection项目代码分析2-Autofac [ ...
- [Asp.net 5] DependencyInjection项目代码分析4-微软的实现(5)(IEnumerable<>补充)
Asp.net 5的依赖注入注入系列可以参考链接: [Asp.net 5] DependencyInjection项目代码分析-目录 我们在之前讲微软的实现时,对于OpenIEnumerableSer ...
- 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)
构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...
- STM32启动代码分析 IAR 比较好
stm32启动代码分析 (2012-06-12 09:43:31) 转载▼ 最近开始使用ST的stm32w108芯片(也是一款zigbee芯片).开始看他的启动代码看的晕晕呼呼呼的. 还好在c ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- SonarQube-5.6.3 代码分析平台搭建使用
python代码分析 官网主页: http://docs.sonarqube.org/display/PLUG/Python+Plugin Windows下安装使用: 快速使用: 1.下载jdk ht ...
随机推荐
- 关于cmd的东西
为了督促自己,就从简单的开始学起,希望用博客来纪念我的成长 1)输入 CMD 回车.进入DOS提示符状态下.输入cd\ 回车 表示进入 c:\> 也到了C盘根目录下 2)d: 回车 是进入D盘当 ...
- 【经典】Linux开发人员必看资料+工具
Linux是一种自由和开放源码的类Unix操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核.Linux可安装在各种计算机硬件设备中,比如手机.平板电脑.路由器.视频游戏控制台.台 ...
- 【转】Android进阶2之Activity之间数据交流(onActivityResult的用法)----不错
原文网址:http://blog.csdn.net/sjf0115/article/details/7387467 主要功能: 在一个主界面(主Activity)上能连接往许多不同子功能模块(子Act ...
- Matlab:回归分析(1)
1.多元线性回归 %数据的输入 x = [ ]; y = [ ]; %转换为列向量 X = [ones(,) x']; Y = y'; %回归分析及检验 [b, bint, r, rint, stat ...
- 关于group by【转载】
1.概述 “Group By”从字面意义上理解就是根据“By”指定的规则对数据进行分组,所谓的分组就是将一个“数据集”划分成若干个“小区域”,然后针对若干个“小区域”进行数据处理. 2.原始表 3.G ...
- js打开新的链接2
window.open打开新的连接时可能会被浏览器拦截掉. 所以采用动态创建a标签的形式. var a = document.createElement('a'); a.href = myUrl; ...
- EMV/PBOC解析(三) TLV格式解析(C#)
1.什么是TLV格式? TLV即Tag-Length-Value,常在IC卡与POS终端设备中通过这样的一个应用通信协议进行数据交换. 金融系统中的TLV是BER-TLV编码的一个特例编码规范,而BE ...
- Linux系统下查看某文件修改的时间戳
Linux系统下查看某文件修改的时间戳查看文件时间戳命令:stat awk.txt File: `awk.txt' Size: 20 Blocks: 8 I ...
- BaseAdapter的ArrayIndexOutOfBoundsException
最近写一个listView中多个listItem布局时,convertView缓存及使用,类似微信的聊天界面的listView,报了一个异常: 11-25 15:51:49.076: E/InputE ...
- 【转】IOS7 MPMoviePlayerViewController横屏显示
在应用程序中用到MPMoviePlayerViewController时,有时需要保持应用程序为竖屏状态,而视频播放器显示为横屏,如何做呢?如果采用强制横屏的方法,应用审核的时候是不会通过的,因为该方 ...