目录
- [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;
}
}