最近在学习OBS开源项目,借此学习流媒体方面的知识。
1)简介
rtmp 是Adobe公司为FLASH和服务器之间传输音视频数据开发的一种数据。基于TCP协议,使用1935默认端口。
2)一个RTMP连接已握手开始,握手以三个固定大小的块组成chunk[0]1字节,chunk[1]1536字节chunk[2]1536字节。客户端和服务端各自发送三个相同的块。client:c0,c1,c2;server:s0,s1,s2。
3)发送规则
1,握手开始于client发送c0,c1;
2,client在发送c2前必须等待接收s1;
3,client 在发送任何数据之前必须等待接收s2;
4,server在发送s0和s1之前必须等待接收c0,也可以等待接收c1;
5,server在发送s2前必须等待接收c1;
6,server在发送任何数据之前必须等待接收c2;
4)数据格式
C0或S0
C0和S0的长度是一个字节,
在 S0 中这个字段表示服务器选择的 RTMP 版本。
rtmp1.0规范所定义的版本是 3;0-2 是早期产品所用的,已被丢弃;4-31保留在未来使用;32-255 不允许使用(为了区分其他以某一字符开始的文本协议)。
如果服务无法识别客户端请求的版本,应该返回 3 。客户端可以选择减到版本 3 或选择取消握手。
C1或S1
C1 和 S1 有 1536 字节长,由下列字段组成
- 时间:4 字节 本字段包含时间戳。该时间戳应该是发送这个数据块的端点的后续块的时间起始点。可以是 0,或其他的 任何值。为了同步多个流,端点可能发送其块流的当前值。
- 零:4 字节 本字段必须是全零。
- 随机数据:1528 字节。 本字段可以包含任何值。 因为每个端点必须用自己初始化的握手和对端初始化的握 手来区分身份,所以这个数据应有充分的随机性。但是并不需要加密安全的随机值,或者动态值
-
C2或S2
C2 和 S2 消息有 1536 字节长。只是 S1 和 C1 的回复。本消息由下列字段组成。
-
时间:4 字节 本字段必须包含对等段发送的时间(对 C2 来说是 S1,对 S2 来说是 C1)。
-
时间 2:4 字节 本字段必须包含先前发送的并被对端读取的包的时间戳。
-
随机回复:1528 字节 本字段必须包含对端发送的随机数据字段(对 C2 来说是 S1,对 S2 来说是 C1) 。 每个对等端可以用时间和时间 2 字段中的时间戳来快速地估计带宽和延迟。 但这样做可 能并不实用。
HandShake(RTMP *r, int FP9HandShake)
{
int i;
uint32_t uptime, suptime;
int bMatch;
char type;
//RTMP_SIG_SIZE:1536,c1,c2长度;clientsig 指向了clientbuf数组的下表1开始的位置即C1的头
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;
char serversig[RTMP_SIG_SIZE];
// "clientbuf的长度刚好是1536+1,此处第一个字节因为是0x03"
// "这是rtmp规范中定义的客户端要求使用的rtmp版本,即C0"
clientbuf[0] = 0x03; /* not encrypted */
//RTMP_GetTime()是用sysconf(_SC_CLK_TCK)函数获取系统clock的再处理(linux下)之后转化网络字节序作为C1的时间戳"
uptime = htonl(RTMP_GetTime())
//"void *memcpy(void *dest, const void *src, int n) 将时间戳填到clientsig前四个字节中 (C1)"
memcpy(clientsig, &uptime, 4);
//"将clientsig中4~7字节置为0 (C1)"
memset(&clientsig[4], 0, 4);
//"给clientsig中后面 1528 个字节填上随机数(C1)"
#ifdef _DEBUG
for (i = 8; i < RTMP_SIG_SIZE; i++)
clientsig[i] = 0xff;
#else
for (i = 8; i < RTMP_SIG_SIZE; i++)
clientsig[i] = (char)(rand() % 256);
#endif
//"发送clientbuf共1537字节(C0+C1)到服务器"
if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
return FALSE;
//"从服务器接受了一个字节即S0,并检查版本"
if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
return FALSE;
RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type);
//"若S0没问题的话,继续接受服务器的S1"
if (type != clientbuf[0])
RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d",
__FUNCTION__, clientbuf[0], type);
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
/* decode server response */
//以下一段是保存服务器的S1然后把服务器的S1作为客户端的C2发送给服务器"
memcpy(&suptime, serversig, 4);
suptime = ntohl(suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, suptime);
RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version : %d.%d.%d.%d", __FUNCTION__,
serversig[4], serversig[5], serversig[6], serversig[7]);
/* 2nd part of handshake */
if (!WriteN(r, serversig, RTMP_SIG_SIZE))
return FALSE;
//"接受服务器的S2"
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
//"比较服务器的S2是否跟客户端C1相同,若是,则握手成功"
bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
if (!bMatch)
{
RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
}
/* er, totally unused? */
(void)FP9HandShake;
return TRUE;
}