阅读了librtmp的源码,简单记录下。
首先补充下AMF格式基本知识
1 AMF格式
AMF是Action Message Format(动作消息格式)的简写,它是一种二进制的数据格式。它的设计是为了把actionscript里面的数据(包括Object, Array, Boolean, Number等)序列化成二进制数据,然后把这段数据随意发送给其他接收方程序,比如发给远程的服务器,在远程服务器那边,可以把这段数据给还原出来,以此达到一个数据传输的作用。
1.1 AMFObject
AMF分成两种: 1. AMF0,基本的数据转换规则; 2. AMF3,是AMF0的扩展
AMF0数据类型:
// AMF0数据类型
typedef enum
{
AMF_NUMBER = 0, // 数字(double)
AMF_BOOLEAN, // 布尔
AMF_STRING, // 字符串
AMF_OBJECT, // 对象
AMF_MOVIECLIP, // 保留,未使用
AMF_NULL, // null
AMF_UNDEFINED, // 未定义
AMF_REFERENCE, // 引用
AMF_ECMA_ARRAY, // 数组
AMF_OBJECT_END, // 对象结束
AMF_STRICT_ARRAY, // 严格的数组
AMF_DATE, // 日期
AMF_LONG_STRING, // 长字符串
AMF_UNSUPPORTED, // 未支持
AMF_RECORDSET, // 保留,未使用
AMF_XML_DOC, // xml文档
AMF_TYPED_OBJECT, // 有类型的对象
AMF_AVMPLUS, // 需要扩展到AMF3
AMF_INVALID = 0xff // 无效的
}AMFDataType;
AMF3数据类型:
// AMF3数据类型
typedef enum
{
AMF3_UNDEFINED = 0, // 未定义
AMF3_NULL, // null
AMF3_FALSE, // false
AMF3_TRUE, // true
AMF3_INTEGER, // 数字int
AMF3_DOUBLE, // double
AMF3_STRING, // 字符串
AMF3_XML_DOC, // xml文档
AMF3_DATE, // 日期
AMF3_ARRAY, // 数组
AMF3_OBJECT, // 对象
AMF3_XML, // xml
AMF3_BYTE_ARRAY // 字节数组
} AMF3DataType;
AMF定义了自己的字符串类型:
// AMF自定义的字符串
typedef struct AVal
{
char *av_val;
int av_len;
} AVal;
// AVal的快速初始化
#define AVC(str) {str,sizeof(str)-1}
// 比较AVal字符串
#define AVMATCH(a1,a2) ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len))
AMFObject表示AMF对象,o_num 代表 o_props的个数, 一个对象内部可以包含N个对象属性
// AMF对象, 就是由一系列的属性构成的
typedef struct AMFObject
{
int o_num; // 属性数目;
struct AMFObjectProperty *o_props; // 属性数组;
} AMFObject;
AMFObjectProperty表示AMF对象属性,即key-value键值对。p_name表示key;p_type表示value的类型;p_vu表示value的数值。
// AMF对象的属性;
typedef struct AMFObjectProperty
{
AVal p_name; // 属性名称;
AMFDataType p_type; // 属性类型;
union
{
double p_number;
AVal p_aval;
AMFObject p_object;
} p_vu; // 属性数值;
int16_t p_UTCoffset; // UTC偏移;
} AMFObjectProperty;
p_vu设置为联合体的目的:
当p_type为number时, m_vu取值double类型 p_number;
当p_type为string时, m_vu取值AVal类型 p_aval;
当p_type为object时, m_vu取值AMFObject类型 p_object。
1.2 编码格式
浮点数:
0x00 + 8字节浮点数
Bool型:
0x01 + 1字节Bool值
短字符串:
0x02 + 2字节长度 + 字符串
长字符串
0x02 + 4字节长度 + 字符串
对象:
0x03 + 属性1名称长度 + 属性1名称 + 1字节属性1类型 + n字节属性1值 + 属性2名称长度 + 属性2名称 + 1字节属性2类型 + n字节属性2值 + 3字节结尾标志
2 librtmp源码分析
2.1 RTMP_ParseURL
解析URL,得到协议名称(protocol),主机名称(host),应用程序名称(app)
2.2 HandShake(握手)
handshake.h文件里面的HandShake有些代码是处理rtmp加密版协议,考虑普通的rtmp协议,分析rtmp.c文件里的HandShake
static int
HandShake(RTMP *r, int FP9HandShake)
{
int i;
uint32_t uptime, suptime;
int bMatch;
char type;
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1;
char serversig[RTMP_SIG_SIZE];
clientbuf[0] = 0x03; /* not encrypted */
uptime = htonl(RTMP_GetTime());
memcpy(clientsig, &uptime, 4);
memset(&clientsig[4], 0, 4);
#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
if (!WriteN(r, clientbuf, RTMP_SIG_SIZE + 1))
return FALSE;
if (ReadN(r, &type, 1) != 1) /* 0x03 or 0x06 */
return FALSE;
RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer : %02X", __FUNCTION__, type);
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 */
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;
if (ReadN(r, serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
return FALSE;
bMatch = (memcmp(serversig, clientsig, RTMP_SIG_SIZE) == 0);
if (!bMatch)
{
RTMP_Log(RTMP_LOGWARNING, "%s, client signature does not match!", __FUNCTION__);
}
return TRUE;
}
1)填充C0=0x3;C1填充时间戳和随机数共1536byte
2)发送C0 C1给服务器
3)从服务器读取S0比对是否为0x3,从服务器读取S1
4)把S1作为C2发送给服务器
5)从服务器读取S2,比对C1和S2,相同则握手成功
2.3 RTMP_Connect
建立NetConnection
主要调用了两个函数,RTMP_Connect0和RTMP_Connect1
RTMP_Connect0
建立Socket连接
RTMP_Connect1
建立RTMP连接,HandShake完成握手,SendConnectPacket发送"connect"命令建立RTMP连接
SendConnectPacket
填充packet头
m_nChannel --> chunk Stream ID
m_headerType --> chunk header中的basic header中的fmt
m_packetType --> Message Type ID,填充的0x14,表示命令消息
m_nTimeStamp --> 时间戳
m_nInfoField2 --> chunk fmt为0时,header的最后四个字节,即Message Stream ID
m_hasAbsTimestamp --> 时间戳是绝对的还是相对的,即chunk type为0时为绝对时间戳,其他类型时为时间戳增量
packet.m_nChannel = 0x03; /* control channel (invoke) */
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
packet.m_packetType = RTMP_PACKET_TYPE_INVOKE;
packet.m_nTimeStamp = 0;
packet.m_nInfoField2 = 0;
packet.m_hasAbsTimestamp = 0;
packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
将av_x串化为"x",如: