【流媒体】RTMPDump—主流程简单分析

目录

  • [1. main函数](#1. main函数)
    • [1.1 初始化socket(InitSockets)](#1.1 初始化socket(InitSockets))
    • [1.2 初始化RTMP(RTMP_Init)](#1.2 初始化RTMP(RTMP_Init))
    • [1.3 解析URL(RTMP_ParseURL)](#1.3 解析URL(RTMP_ParseURL))
    • [1.4 配置流信息(RTMP_SetupStream)](#1.4 配置流信息(RTMP_SetupStream))

RTMP协议相关:
【流媒体】RTMP协议概述
【流媒体】RTMP协议的数据格式
【流媒体】RTMP协议的消息类型

参考雷博的系列文章(可以从一篇链接到其他文章):
RTMPdump 源代码分析 1: main()函数

RTMPDump是RMTP协议的一个官方发布的参考代码(下载地址为:RTMPDump),最新版本为2.4。本文参考这个代码对RTMP协议进行深入的理解

RTMPDump代码的主要结构为

1. main函数

main函数的流程如下所示,代码还是比较长的,主要可以分为8个步骤:

(1)初始化socket(InitSockets)

(2)初始化RTMP(RTMP_Init)

(3)解析URL(RTMP_ParseURL)

(4)配置流信息(RTMP_SetupStream)

(5)建立网络连接(RTMP_Connect)

(6)建立流连接(RTMP_ConnectStream)

(7)流媒体下载(Download)

(8)清理和释放(free、RTMP_Close和CleanupSockets)

其中,前4个步骤是一些初始化和配置,可以先进行分析,后面对RTMP连接过程再进行逐步分析

c 复制代码
int
main(int argc, char** argv)
{
	extern char* optarg;

	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 };
	AVal usherToken = { 0, 0 }; //Justin.tv auth token
	int port = -1;
	int protocol = RTMP_PROTOCOL_UNDEFINED;
	int retries = 0;
	int bLiveStream = FALSE;	// is it a live stream? then we can't seek/resume
	int bRealtimeStream = FALSE;  // If true, disable the BUFX hack (be patient)
	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 fullUrl = { 0, 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;

	// 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");
	// 1. 初始化sockets,使用1.1版本
	if (!InitSockets())
	{
		RTMP_Log(RTMP_LOGERROR,
			"Couldn't load sockets support on your platform, exiting!");
		return RD_FAILED;
	}

	/* sleep(30); */
	// 2. 初始化rtmp
	RTMP_Init(&rtmp);
	// 初始化opts
	int opt;
	struct option longopts[] = {
	  {"help", 0, NULL, 'h'},		// help
	  {"host", 1, NULL, 'n'},		// 主机地址
	  {"port", 1, NULL, 'c'},		// 端口号
	  {"socks", 1, NULL, 'S'},		// socket
	  {"protocol", 1, NULL, 'l'},	// 协议类型
	  {"playpath", 1, NULL, 'y'},	// 播放路径
	  {"playlist", 0, NULL, 'Y'},	// 播放列表
	  {"url", 1, NULL, 'i'},		// URL地址,但包含选项
	  {"rtmp", 1, NULL, 'r'},		// URL地址
	  {"swfUrl", 1, NULL, 's'},		// 播放器swf文件的URL
	  {"tcUrl", 1, NULL, 't'},		// 播放流的URL
	  {"pageUrl", 1, NULL, 'p'},	// 播放节目的网址
	  {"app", 1, NULL, 'a'},		// 应用程序
	  {"auth", 1, NULL, 'u'},		// 要附加到连接字符串的身份验证字符串
	  {"conn", 1, NULL, 'C'},		// 要附加到连接字符串的任意AMF数据
  #ifdef CRYPTO
	  {"swfhash", 1, NULL, 'w'},	// 解压缩SWF文件的SHA256哈希值(32字节)
	  {"swfsize", 1, NULL, 'x'},	// 解压后的SWF文件的大小,SWFVerification需要
	  {"swfVfy", 1, NULL, 'W'},		// URL到播放器swf文件,自动计算哈希/大小
	  {"swfAge", 1, NULL, 'X'},		// 在刷新之前使用缓存的SWF散列的时长
  #endif
	  {"flashVer", 1, NULL, 'f'},	// Flash版本字符串
	  {"live", 0, NULL, 'v'},		// 保存一个直播流
	  {"realtime", 0, NULL, 'R'},	// 不要试图通过暂停/取消BUFX来加速下载
	  {"flv", 1, NULL, 'o'},		// FLV输出文件名
	  {"resume", 0, NULL, 'e'},		// 恢复部分RTMP下载
	  {"timeout", 1, NULL, 'm'},	// 超时连接数秒
	  {"buffer", 1, NULL, 'b'},		// 缓冲时间(毫秒)
	  {"skip", 1, NULL, 'k'},		// 在寻找要恢复的最后一个关键帧时,跳过num关键帧。重新链接失败时有用
	  {"subscribe", 1, NULL, 'd'},	// 订阅的流名称(如果指定了live,则默认为playpath)
	  {"start", 1, NULL, 'A'},		// 从进入流的第num秒处开始
	  {"stop", 1, NULL, 'B'},		// 在流进入第num秒时停止
	  {"token", 1, NULL, 'T'},		// SecureToken响应的密钥
	  {"hashes", 0, NULL, '#'},		// 用哈希显示进度,而不是字节计数器
	  {"debug", 0, NULL, 'z'},		// 调试级命令输出
	  {"quiet", 0, NULL, 'q'},		// 禁止所有命令输出
	  {"verbose", 0, NULL, 'V'},	// 详细命令输出
	  {"jtv", 1, NULL, 'j'},		// Justin的身份验证令牌
	  {0, 0, 0, 0}
	};

	// 解析命令行参数
	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)
	{
		switch (opt)
		{
		case 'h':	// help
			usage(argv[0]);
			return RD_SUCCESS;
#ifdef CRYPTO
		case 'w':	// swfhash, 解压缩SWF文件的SHA256哈希值(32字节)
		{
			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':	// swfsize, 解压后的SWF文件的大小,SWFVerification需要
		{
			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':	// swfVfy, URL到播放器swf文件,自动计算哈希/大小
			STR2AVAL(swfUrl, optarg);
			swfVfy = 1;
			break;
		case 'X':	// swfAge, 在刷新之前使用缓存的SWF散列的时长
		{
			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':	// skip,在寻找要恢复的最后一个关键帧时,跳过num关键帧。重新链接失败时有用
			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':	// buffer,缓冲时间(毫秒)
		{
			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':	// live, 保存一个直播流
			bLiveStream = TRUE;	// no seeking or resuming possible!
			break;
		case 'R':	// realtime, 不要试图通过暂停/取消BUFX来加速下载
			bRealtimeStream = TRUE; // seeking and resuming is still possible
			break;
		case 'd':	// subscribe, 订阅的流名称(如果指定了live,则默认为playpath)
			STR2AVAL(subscribepath, optarg);
			break;
		case 'n':	// host, 主机地址
			STR2AVAL(hostname, optarg);
			break;
		case 'c':	// port, 端口号
			port = atoi(optarg);
			break;
		case 'l':	// protocol, 协议类型
			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':	// playpath, 播放路径
			STR2AVAL(playpath, optarg);
			break;
		case 'Y':	// playlist, 播放列表
			RTMP_SetOpt(&rtmp, &av_playlist, (AVal*)& av_true);
			break;
		case 'r':	// rtmp, URL地址
		{
			AVal parsedHost, parsedApp, parsedPlaypath;
			unsigned int parsedPort = 0;
			int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;
			// 3. 解析URL
			if (!RTMP_ParseURL
			(optarg, &parsedProtocol, &parsedHost, &parsedPort,
				&parsedPlaypath, &parsedApp))
			{
				RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
					optarg);
			}
			else
			{
				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 'i':	// url, URL地址,但包含选项
			STR2AVAL(fullUrl, optarg);
			break;
		case 's':	// swfUrl, 播放器swf文件的URL
			STR2AVAL(swfUrl, optarg);
			break;
		case 't':	// tcUrl, 播放流的URL
			STR2AVAL(tcUrl, optarg);
			break;
		case 'p':	// pageUrl, 播放节目的网址
			STR2AVAL(pageUrl, optarg);
			break;
		case 'a':	// app, 应用程序
			STR2AVAL(app, optarg);
			break;
		case 'f':	// flashVer, Flash版本字符串
			STR2AVAL(flashVer, optarg);
			break;
		case 'o':	// flv, FLV输出文件名
			flvFile = optarg;
			if (strcmp(flvFile, "-"))
				bStdoutMode = FALSE;

			break;
		case 'e':	// resume, 恢复部分RTMP下载
			bResume = TRUE;
			break;
		case 'u':	// auth, 要附加到连接字符串的身份验证字符串
			STR2AVAL(auth, optarg);
			break;
		case 'C': {	// conn, 要附加到连接字符串的任意AMF数据
			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, 超时连接数秒
			timeout = atoi(optarg);
			break;
		case 'A':	// start, 从进入流的第num秒处开始
			dStartOffset = (int)(atof(optarg) * 1000.0);
			break;
		case 'B':	// stop, 在流进入第num秒时停止
			dStopOffset = (int)(atof(optarg) * 1000.0);
			break;
		case 'T': {	// token, SecureToken响应的密钥
			AVal token;
			STR2AVAL(token, optarg);
			RTMP_SetOpt(&rtmp, &av_token, &token);
		}
				  break;
		case '#':
			bHashes = TRUE;
			break;
		case 'q':	// quiet, 禁止所有命令输出
			RTMP_debuglevel = RTMP_LOGCRIT;
			break;
		case 'V':	// verbose, 详细命令输出
			RTMP_debuglevel = RTMP_LOGDEBUG;
			break;
		case 'z':	// debug, 调试级命令输出
			RTMP_debuglevel = RTMP_LOGALL;
			break;
		case 'S':	// socks
			STR2AVAL(sockshost, optarg);
			break;
		case 'j':	// jtv, Justin的身份验证令牌
			STR2AVAL(usherToken, optarg);
			break;
		default:
			RTMP_LogPrintf("unknown option: %c\n", opt);
			usage(argv[0]);
			return RD_FAILED;
			break;
		}
	}

	// 检查hostname
	if (!hostname.av_len && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGERROR,
			"You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
		return RD_FAILED;
	}
	// 检查playpath
	if (playpath.av_len == 0 && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGERROR,
			"You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
		return RD_FAILED;
	}
	// 检查protocol
	if (protocol == RTMP_PROTOCOL_UNDEFINED && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGWARNING,
			"You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
		protocol = RTMP_PROTOCOL_RTMP;
	}
	// 检查端口
	if (port == -1 && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGWARNING,
			"You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
		port = 0;
	}
	if (port == 0 && !fullUrl.av_len)
	{
		if (protocol & RTMP_FEATURE_SSL)
			port = 443;
		else if (protocol & RTMP_FEATURE_HTTP)
			port = 80;
		else
			port = 1935;
	}
	// 检查flv文件
	if (flvFile == 0)
	{
		RTMP_Log(RTMP_LOGWARNING,
			"You haven't specified an output file (-o filename), using stdout");
		bStdoutMode = TRUE;
	}
	// 检查stdout
	if (bStdoutMode && bResume)
	{
		RTMP_Log(RTMP_LOGWARNING,
			"Can't resume in stdout mode, ignoring --resume option");
		bResume = FALSE;
	}
	// 检查live stream
	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, &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
	// 播放流的URL
	if (tcUrl.av_len == 0)
	{
		tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
			hostname.av_len + app.av_len + sizeof("://:65535/");
		tcUrl.av_val = (char*)malloc(tcUrl.av_len);
		if (!tcUrl.av_val)
			return RD_FAILED;
		tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
			RTMPProtocolStringsLower[protocol], hostname.av_len,
			hostname.av_val, port, app.av_len, app.av_val);
	}

	int first = 1;

	// User defined seek offset
	if (dStartOffset > 0)
	{
		// Live stream
		if (bLiveStream)
		{
			RTMP_Log(RTMP_LOGWARNING,
				"Can't seek in a live stream, ignoring --start option");
			dStartOffset = 0;
		}
	}
	// URL地址
	if (!fullUrl.av_len)
	{	// 4. 配置流信息
		RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
			&tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
			&flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
	}
	else
	{
		// 设置URL
		if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val);
			return RD_FAILED;
		}
	}

	/* Try to keep the stream moving if it pauses on us */
	if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP))
		rtmp.Link.lFlags |= RTMP_LF_BUFX;

	off_t size = 0;

	// 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;
			}
		}
	}

#ifdef _DEBUG
	netstackdump = fopen("netstackdump", "wb");
	netstackdump_read = fopen("netstackdump_read", "wb");
#endif

	while (!RTMP_ctrlC) // 除非有ctrl-C,否则持续循环
	{
		RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
		RTMP_SetBufferMS(&rtmp, bufferTime);	// 设置缓冲区时间

		if (first)
		{
			first = 0;
			RTMP_LogPrintf("Connecting ...\n");
			// 5. RTMP连接(NetConnection)
			if (!RTMP_Connect(&rtmp, NULL))
			{
				nStatus = RD_NO_CONNECT;
				break;
			}

			RTMP_Log(RTMP_LOGINFO, "Connected...");

			// 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;
				}
			}
			// 6. 连接流 (NetStream), 连接流
			if (!RTMP_ConnectStream(&rtmp, dSeek))
			{
				nStatus = RD_FAILED;
				break;
			}
		}
		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;
		}
		// 7. 处理RTMP流媒体的下载过程
		nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
			metaHeader, nMetaHeaderSize, initialFrame,
			initialFrameType, nInitialFrameSize, nSkipKeyFrames,
			bStdoutMode, bLiveStream, bRealtimeStream, 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_LogPrintf("Download complete\n");
	}
	else if (nStatus == RD_INCOMPLETE)
	{
		RTMP_LogPrintf
		("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
			percent);
	}

	// 8. 释放和清理
clean:
	RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
	RTMP_Close(&rtmp); // 关闭rtmp

	if (file != 0)
		fclose(file);
	// 清理sockets
	CleanupSockets();

#ifdef _DEBUG
	if (netstackdump != 0)
		fclose(netstackdump);
	if (netstackdump_read != 0)
		fclose(netstackdump_read);
#endif
	return nStatus;
}

1.1 初始化socket(InitSockets)

初始化socket的代码很简单,给定一个socket版本号,然后调用WSAStartup()进行初始化,不过这里默认使用的版本号比较老,为1.1,现在的使用至少为2.2

c 复制代码
int
InitSockets()
{
#ifdef WIN32
	WORD version;
	WSADATA wsaData;

	version = MAKEWORD(1, 1);
	return (WSAStartup(version, &wsaData) == 0);
#else
	return TRUE;
#endif
}

1.2 初始化RTMP(RTMP_Init)

c 复制代码
void
RTMP_Init(RTMP * r)
{
#ifdef CRYPTO
	if (!RTMP_TLS_ctx)
		RTMP_TLS_Init();
#endif

	memset(r, 0, sizeof(RTMP));
	r->m_sb.sb_socket = -1;						// socket
	r->m_inChunkSize = RTMP_DEFAULT_CHUNKSIZE;	// chunk size默认大小为128
	r->m_outChunkSize = RTMP_DEFAULT_CHUNKSIZE;
	r->m_nBufferMS = 30000;						// 客户端应预加载媒体数据的时间,单位为毫秒
	r->m_nClientBW = 2500000;					// 客户端最大可用带宽
	r->m_nClientBW2 = 2;						// BW2描述的是limit type,即什么类型的带宽限制
	r->m_nServerBW = 2500000;					// 服务器端最大可用带宽
	r->m_fAudioCodecs = 3191.0;					// ‭标识哪些音频编码器可用 (3191 = 0b 1100 0111 0111‬)
	r->m_fVideoCodecs = 252.0;					// 标识哪些视频编码器可用 (‭252 = 0b 0010 0101 0010‬)
	r->Link.timeout = 30;						// 连接超时,单位为秒
	r->Link.swfAge = 30;						// 表示SWF文件创建或修改后的时间长度,单位为秒
}

1.3 解析URL(RTMP_ParseURL)

c 复制代码
int RTMP_ParseURL(const char* url, int* protocol, AVal* host, unsigned int* port,
	AVal* playpath, AVal* app)
{
	char* p, * end, * col, * ques, * slash;

	RTMP_Log(RTMP_LOGDEBUG, "Parsing...");

	*protocol = RTMP_PROTOCOL_RTMP; // 默认设置为RTMP
	*port = 0;
	playpath->av_len = 0;
	playpath->av_val = NULL;
	app->av_len = 0;
	app->av_val = NULL;

	/* Old School Parsing */

	/* look for usual :// pattern */
	// 解析RTMP协议
	p = strstr(url, "://");
	if (!p) {
		RTMP_Log(RTMP_LOGERROR, "RTMP URL: No :// in url!");
		return FALSE;
	}
	{
		int len = (int)(p - url);

		if (len == 4 && strncasecmp(url, "rtmp", 4) == 0)
			* protocol = RTMP_PROTOCOL_RTMP;
		else if (len == 5 && strncasecmp(url, "rtmpt", 5) == 0)
			* protocol = RTMP_PROTOCOL_RTMPT;
		else if (len == 5 && strncasecmp(url, "rtmps", 5) == 0)
			* protocol = RTMP_PROTOCOL_RTMPS;
		else if (len == 5 && strncasecmp(url, "rtmpe", 5) == 0)
			* protocol = RTMP_PROTOCOL_RTMPE;
		else if (len == 5 && strncasecmp(url, "rtmfp", 5) == 0)
			* protocol = RTMP_PROTOCOL_RTMFP;
		else if (len == 6 && strncasecmp(url, "rtmpte", 6) == 0)
			* protocol = RTMP_PROTOCOL_RTMPTE;
		else if (len == 6 && strncasecmp(url, "rtmpts", 6) == 0)
			* protocol = RTMP_PROTOCOL_RTMPTS;
		else {
			RTMP_Log(RTMP_LOGWARNING, "Unknown protocol!\n");
			goto parsehost;
		}
	}

	RTMP_Log(RTMP_LOGDEBUG, "Parsed protocol: %d", *protocol);

parsehost:
	/* let's get the hostname */
	p += 3;

	/* check for sudden death */
	if (*p == 0) {
		RTMP_Log(RTMP_LOGWARNING, "No hostname in URL!");
		return FALSE;
	}

	end = p + strlen(p);
	col = strchr(p, ':');
	ques = strchr(p, '?');
	slash = strchr(p, '/');
	// 解析hostname (地址)
	{
		int hostlen;
		if (slash)
			hostlen = slash - p;
		else
			hostlen = end - p;
		if (col && col - p < hostlen)
			hostlen = col - p;

		if (hostlen < 256) {
			host->av_val = p;
			host->av_len = hostlen;
			RTMP_Log(RTMP_LOGDEBUG, "Parsed host    : %.*s", hostlen, host->av_val);
		}
		else {
			RTMP_Log(RTMP_LOGWARNING, "Hostname exceeds 255 characters!");
		}

		p += hostlen;
	}

	/* get the port number if available */
	// 检查是否有端口号
	if (*p == ':') {
		unsigned int p2;
		p++;
		p2 = atoi(p);
		if (p2 > 65535) {
			RTMP_Log(RTMP_LOGWARNING, "Invalid port number!");
		}
		else {
			*port = p2;
		}
	}
	// 没有app或playpath
	if (!slash) {
		RTMP_Log(RTMP_LOGWARNING, "No application or playpath in URL!");
		return TRUE;
	}
	p = slash + 1;

	{
		/* parse application
		 *
		 * rtmp://host[:port]/app[/appinstance][/...]
		 * application = app[/appinstance]
		 */

		char* slash2, * slash3 = NULL, * slash4 = NULL;
		int applen, appnamelen;

		slash2 = strchr(p, '/');
		if (slash2)
			slash3 = strchr(slash2 + 1, '/');
		if (slash3)
			slash4 = strchr(slash3 + 1, '/');

		applen = end - p; /* ondemand, pass all parameters as app */
		appnamelen = applen; /* ondemand length */

		if (ques && strstr(p, "slist=")) { /* whatever it is, the '?' and slist= means we need to use everything as app and parse plapath from slist= */
			appnamelen = ques - p;
		}
		else if (strncmp(p, "ondemand/", 9) == 0) {
			/* app = ondemand/foobar, only pass app=ondemand */
			applen = 8;
			appnamelen = 8;
		}
		else { /* app!=ondemand, so app is app[/appinstance] */
			if (slash4)
				appnamelen = slash4 - p;
			else if (slash3)
				appnamelen = slash3 - p;
			else if (slash2)
				appnamelen = slash2 - p;

			applen = appnamelen;
		}

		app->av_val = p;
		app->av_len = applen;
		RTMP_Log(RTMP_LOGDEBUG, "Parsed app     : %.*s", applen, p);

		p += appnamelen;
	}

	if (*p == '/')
		p++;

	if (end - p) {
		AVal av = { p, end - p };
		RTMP_ParsePlaypath(&av, playpath); // 解析play path
	}

	return TRUE;
}

1.4 配置流信息(RTMP_SetupStream)

c 复制代码
void
RTMP_SetupStream(RTMP * r,
	int protocol,
	AVal * host,
	unsigned int port,
	AVal * sockshost,
	AVal * playpath,
	AVal * tcUrl,
	AVal * swfUrl,
	AVal * pageUrl,
	AVal * app,
	AVal * auth,
	AVal * swfSHA256Hash,
	uint32_t swfSize,
	AVal * flashVer,
	AVal * subscribepath,
	AVal * usherToken,
	int dStart,
	int dStop, int bLiveStream, long int timeout)
{
	// 先打印输出即将要赋值的信息
	RTMP_Log(RTMP_LOGDEBUG, "Protocol : %s", RTMPProtocolStrings[protocol & 7]);
	RTMP_Log(RTMP_LOGDEBUG, "Hostname : %.*s", host->av_len, host->av_val);
	RTMP_Log(RTMP_LOGDEBUG, "Port     : %d", port);
	RTMP_Log(RTMP_LOGDEBUG, "Playpath : %s", playpath->av_val);

	if (tcUrl && tcUrl->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "tcUrl    : %s", tcUrl->av_val);
	if (swfUrl && swfUrl->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "swfUrl   : %s", swfUrl->av_val);
	if (pageUrl && pageUrl->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "pageUrl  : %s", pageUrl->av_val);
	if (app && app->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "app      : %.*s", app->av_len, app->av_val);
	if (auth && auth->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "auth     : %s", auth->av_val);
	if (subscribepath && subscribepath->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "subscribepath : %s", subscribepath->av_val);
	if (usherToken && usherToken->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "NetStream.Authenticate.UsherToken : %s", usherToken->av_val);
	if (flashVer && flashVer->av_val)
		RTMP_Log(RTMP_LOGDEBUG, "flashVer : %s", flashVer->av_val);
	if (dStart > 0)
		RTMP_Log(RTMP_LOGDEBUG, "StartTime     : %d msec", dStart);
	if (dStop > 0)
		RTMP_Log(RTMP_LOGDEBUG, "StopTime      : %d msec", dStop);

	RTMP_Log(RTMP_LOGDEBUG, "live     : %s", bLiveStream ? "yes" : "no");
	RTMP_Log(RTMP_LOGDEBUG, "timeout  : %ld sec", timeout);

#ifdef CRYPTO
	if (swfSHA256Hash != NULL && swfSize > 0)
	{
		memcpy(r->Link.SWFHash, swfSHA256Hash->av_val, sizeof(r->Link.SWFHash));
		r->Link.SWFSize = swfSize;
		RTMP_Log(RTMP_LOGDEBUG, "SWFSHA256:");
		RTMP_LogHex(RTMP_LOGDEBUG, r->Link.SWFHash, sizeof(r->Link.SWFHash));
		RTMP_Log(RTMP_LOGDEBUG, "SWFSize  : %u", r->Link.SWFSize);
	}
	else
	{
		r->Link.SWFSize = 0;
	}
#endif
	// 配置socket
	SocksSetup(r, sockshost);
	// 配置其他信息
	if (tcUrl && tcUrl->av_len)
		r->Link.tcUrl = *tcUrl;
	if (swfUrl && swfUrl->av_len)
		r->Link.swfUrl = *swfUrl;
	if (pageUrl && pageUrl->av_len)
		r->Link.pageUrl = *pageUrl;
	if (app && app->av_len)
		r->Link.app = *app;
	if (auth && auth->av_len)
	{
		r->Link.auth = *auth;
		r->Link.lFlags |= RTMP_LF_AUTH;
	}
	if (flashVer && flashVer->av_len)
		r->Link.flashVer = *flashVer;
	else
		r->Link.flashVer = RTMP_DefaultFlashVer;
	if (subscribepath && subscribepath->av_len)
		r->Link.subscribepath = *subscribepath;
	if (usherToken && usherToken->av_len)
		r->Link.usherToken = *usherToken;
	r->Link.seekTime = dStart;
	r->Link.stopTime = dStop;
	if (bLiveStream)
		r->Link.lFlags |= RTMP_LF_LIVE;
	r->Link.timeout = timeout;

	r->Link.protocol = protocol;
	r->Link.hostname = *host;
	r->Link.port = port;
	r->Link.playpath = *playpath;

	if (r->Link.port == 0)
	{
		if (protocol & RTMP_FEATURE_SSL)
			r->Link.port = 443;
		else if (protocol & RTMP_FEATURE_HTTP)
			r->Link.port = 80;
		else
			r->Link.port = 1935;	// 默认端口为1935
	}
}

socket配置

c 复制代码
static void
SocksSetup(RTMP * r, AVal * sockshost)
{
	if (sockshost->av_len)
	{
		const char* socksport = strchr(sockshost->av_val, ':');
		char* hostname = strdup(sockshost->av_val);

		if (socksport)
			hostname[socksport - sockshost->av_val] = '\0';
		r->Link.sockshost.av_val = hostname;
		r->Link.sockshost.av_len = strlen(hostname);

		r->Link.socksport = socksport ? atoi(socksport + 1) : 1080;
		RTMP_Log(RTMP_LOGDEBUG, "Connecting via SOCKS proxy: %s:%d", r->Link.sockshost.av_val,
			r->Link.socksport);
	}
	else
	{
		r->Link.sockshost.av_val = NULL;
		r->Link.sockshost.av_len = 0;
		r->Link.socksport = 0;
	}
}
相关推荐
大胆飞猪1 小时前
C++9--前置++和后置++重载,const,日期类的实现(对前几篇知识点的应用)
c++
1 9 J1 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
夕泠爱吃糖1 小时前
C++中如何实现序列化和反序列化?
服务器·数据库·c++
长潇若雪1 小时前
《类和对象:基础原理全解析(上篇)》
开发语言·c++·经验分享·类和对象
染指11103 小时前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
Code out the future4 小时前
【C++——临时对象,const T&】
开发语言·c++
sam-zy4 小时前
MFC用List Control 和Picture控件实现界面切换效果
c++·mfc
aaasssdddd964 小时前
C++的封装(十四):《设计模式》这本书
数据结构·c++·设计模式
发呆小天才O.oᯅ5 小时前
YOLOv8目标检测——详细记录使用OpenCV的DNN模块进行推理部署C++实现
c++·图像处理·人工智能·opencv·yolo·目标检测·dnn
qincjun5 小时前
文件I/O操作:C++
开发语言·c++