LibRTMP源代码分析3

AMF协议是Action Message Format协议的简称,AMF协议是Adobe公司的协议,主要用于数据交互和远程过程调用,在功能上与WebService相当,但AMF与WebService中的xml不同在于AMF是二进制数据,而xml是文本数据,AMF的传输效率比xml高。AMF使用http方式传输,目前主要用于ActionScript中,实现Flex与Service之间的通信。AMF目前有两种版本,AMF0和AMF3,他们在数据类型的定义上有细微不同。
       AMF最大的特色在于可直接将Flash内置对象,例如Object, Array, Date, XML,传回服务器端,并且在服务器端自动进行解析成适当的对象,这就减轻了开发人员繁复工作,同时也更省了开发时间。由于AMF采用二进制编码,这种方式可以高度压缩数据,因此非常适合用来传递大量的资料。数据量越大,Flash Remoting的传输效能就越高,远远超过Web Service。至于XML, 它们使用纯文本的传输方式,效能就更不能与Flash Remoting相提并论了。除了AMF编码进行高效数据操作的功能之外,ByteArray还有一个很酷的功能,就是从内存中深层次的Copy(Clone)整个对象。
具体AMF是怎么使用的在这里就不做详细讨论了。amf.c是RTMPDump解析RTMP协议的函数存放的地方,在这里贴上其源代码。对其中大部分代码作了比较详细的注释。建议看代码之前熟悉AMF协议,手头上准备好AMF0和AMF3的官方协议文档。

// 大端Big-Endian : 
// 低地址存放最高有效位(MSB),既高位字节排放在内存的低地址端,
// 低位字节排放在内存的高地址端。 符合人脑逻辑,与计算机逻辑不同。
//
// 网络字节序 Network Order : 
// TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的
// 字节序通常称之为网络字节序。  
//
// 主机序 Host Order : 
// 它遵循Little-Endian规则。所以当两台主机之间要通过TCP/IP协议进行
// 通信的时候就需要调用相应的函数进行主机序(Little-Endian)和网络
// 序(Big-Endian)的转换。
//
// AMF数据采用 Big-Endian(大端模式),主机采用Little-Endian(小端模式)

/**
 * @brief 将以data为起始地址的16位数据转化为无符号短整型值
 */
unsigned short AMF_DecodeInt16(const char *data);

/**
 * @brief 将以data为起始地址的24位数据转化为无符号整型值
 */
unsigned int AMF_DecodeInt24(const char *data);

/**
 * @brief 将以data为起始地址的32位数据转化为无符号整型值
 */
unsigned int AMF_DecodeInt32(const char *data);

/**
 * @brief 将以data为起始地址的字符串数据解析出来,保存在bv中.
 *
 *  data中的数据格式为: | 字符串长度(2字节) |  实际字符串 |
 *
 *  其中  bv->av_len 表示字符串的长度,占2个字节
 *           bv->av_val 缓冲区用于保存实际的字符串。
 */
void AMF_DecodeString(const char *data, AVal *bv);

/**
 * @brief 将以data为起始地址的长字符串数据解析出来,保存在bv中.
 *
 *  data中的数据格式为: |  字符串长度(4字节) |  实际字符串 |
 *
 *  其中  bv->av_len 表示字符串的长度,占4个字节
 *           bv->av_val 缓冲区用于保存实际的字符串。
 *  长字符串的长度可能超过16位无符号整型表示范围,因此采用32位.
 */
void AMF_DecodeLongString(const char *data, AVal *bv);

/**
 * @brief 将以data为起始地址的8个字节数据转化double值
 */
double AMF_DecodeNumber(const char *data);

/**
 * @brief 将以data为起始地址的1个字节数据转化boolean值
 */
int AMF_DecodeBoolean(const char *data);

/**
 * @brief 将短整型值转化为2个字节保存到缓冲区output中.
 *        
 * @param output  :  输出缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param nVal     : 待编码的短整型值
 *
 * @return 编码成功的话,output向后移动2个字节,否则返回NULL.
 */
char *AMF_EncodeInt16(char *output, char *outend, short nVal);

/**
 * @brief 将整型值转化为3个字节保存到缓冲区output中.
 *        
 * @param output  : 输出缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param nVal     : 待编码的整型值
 *
 * @return 编码成功的话,output向后移动3个字节,否则返回NULL.
 */
char *AMF_EncodeInt24(char *output, char *outend, int nVal);

/**
 * @brief 将整型值转化为4个字节保存到缓冲区output中.
 *        
 * @param output  : 输出缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param nVal     : 待编码的整型值
 *
 * @return 编码成功的话,output向后移动4个字节,否则返回NULL.
 */
char *AMF_EncodeInt32(char *output, char *outend, int nVal);

/**
 * @brief 将bv中的字符串编码到缓冲区output中. 编码的格式为:
 *  | 类型(1字节) |  长度(2或4字节) |     实际的字符串    |
 *        
 * @param output  : 缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param bv        : 结构体保存有字符串数据和长度
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeString(char *output, char *outend, const AVal *bv)
{
// 判断缓冲区空间是否足够大 
if ((bv->av_len < 65536 && output + 1 + 2 + bv->av_len > outend) || 
     output + 1 + 4 + bv->av_len > outend) 
return NULL; 

// 短字符串 
if (bv->av_len < 65536) 
*output++ = AMF_STRING; 
output = AMF_EncodeInt16(output, outend, bv->av_len); 
else // 长字符串
*output++ = AMF_LONG_STRING; 
output = AMF_EncodeInt32(output, outend, bv->av_len); 

// 将bv中的字符串数据拷贝到output中,并修改指针output值 
memcpy(output, bv->av_val, bv->av_len); output += bv->av_len; return output;
}

/**
 * @brief 将double值编码到缓冲区output中. 编码的格式为:
 *  | 类型(1字节) |   double值(8字节)   |
 *        
 * @param output  : 输出缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param dVal     : 待编码的double值
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeNumber(char *output, char *outend, double dVal);

/**
 * @brief 将boolean值编码到缓冲区output中. 编码的格式为:
 *  | 类型(1字节) |  0或1(1字节)  |
 *        
 * @param output  : 输出缓冲区用于保存编码值
 * @param outend : 缓冲区末端地址
 * @param bVal     : 待编码的boolean值
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeBoolean(char *output, char *outend, int bVal);

/**
 * @brief 将(strName, strValue)组编码到缓冲区output中. 编码格式:
 *
 * | name长度(2字节) | name实际字符串 | 类型(1字节) |  value长度(2或4字节) |  value字符串  |
 *
 *  举例: strName = {"ip", 2},  strValue = {"192.168.19.201", 14}
 *  其中strName中的字符串长度一定小于65536,而strValue中的字符串长度不定.
 *        
 * @param output     : 缓冲区用于保存编码值
 * @param outend    : 缓冲区末端地址
 * @param strName  : 某个变量的名字,用字符串表示
 * @param strValue  : 上述变量的值,也用字符串表示
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeNamedString(char *output, char *outend, const AVal *strName, const AVal *strValue);

/**
 * @brief 将(strName, dVal)组编码到缓冲区output中. 编码格式:
 *
 * | name长度(2字节) | name实际字符串 |  类型(1字节) |   double值(8字节)   |
 *
 *  举例: strName = {"width", 5},  dVal = 640.
 *  其中strName中的字符串长度一定小于65536.
 *        
 * @param output   : 缓冲区用于保存编码值
 * @param outend  : 缓冲区末端地址
 * @param strName : 某个变量的名字,类型为字符串
 * @param dVal       : 上述变量的值,类型为double
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal *strName, double dVal);

/**
 * @brief 将(strName, bVal)组编码到缓冲区output中. 编码格式:
 *
 * | name长度(2字节) | name实际字符串 |  类型(1字节) |  bVal值(1字节) |
 *
 *  举例: strName = {"stereo", 6},  bVal = 1.
 *  其中strName中的字符串长度一定小于65536.
 *        
 * @param output     : 缓冲区用于保存编码值
 * @param outend    : 缓冲区末端地址
 * @param strName : 某个变量的名字,类型为字符串
 * @param bVal        : 上述变量的值,类型为boolean
 *
 * @return 编码成功返回编码后新的output地址值,否则返回NULL.
 */
char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal *strName, int bVal);

/**
 * @brief 获取amf对象属性的名字
 */
void AMFProp_GetName(AMFObjectProperty *prop, AVal *name);

/**
 * @brief 设置amf对象属性的名字
 */
void AMFProp_SetName(AMFObjectProperty *prop, AVal *name);

/**
 * @brief 获取amf对象属性的数据类型
 */
AMFDataType AMFProp_GetType(AMFObjectProperty *prop);

/**
 * @brief 获取amf对象属性的double值
 */
double AMFProp_GetNumber(AMFObjectProperty *prop);

/**
 * @brief 获取amf对象属性的boolean值
 */
int AMFProp_GetBoolean(AMFObjectProperty *prop);

/**
 * @brief 获取amf对象属性的AVal值,即字符串本身及其长度
 */
void AMFProp_GetString(AMFObjectProperty *prop, AVal *str);

/**
 * @brief 获取amf对象属性所属的amf对象
 */
void AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj);

/**
 * @brief 判断amf对象属性的类型是否有效
 */
int AMFProp_IsValid(AMFObjectProperty *prop);

/**
 * @brief 编码一个amf对象属性,包括属性名及其值
 */
char *AMFProp_Encode(AMFObjectProperty *prop, char *pBuffer, char *pBufEnd)
{
// 判断属性类型是否有效
if (prop->p_type == AMF_INVALID) 
return NULL;

// 判断缓冲区空间是否足够大
if (prop->p_type != AMF_NULL && pBuffer + prop->p_name.av_len + 2 + 1 >= pBufEnd) 
return NULL;

// 编码属性名字,格式为: | 长度(2字节) | 名字字符串 | 
if (prop->p_type != AMF_NULL && prop->p_name.av_len) 
*pBuffer++ = prop->p_name.av_len >> 8; 
*pBuffer++ = prop->p_name.av_len & 0xff; 
memcpy(pBuffer, prop->p_name.av_val, prop->p_name.av_len); 
pBuffer += prop->p_name.av_len; 
}

// 根据属性值的类型选择相应的编码方式 
switch (prop->p_type)
{
... ... (省略)
}

return pBuffer;
}

#define   AMF3_INTEGER_MAX 268435455 // 0x000000000FFFFFFF
#define   AMF3_INTEGER_MIN  -268435456 // 0xFFFFFFFFF0000000

/**
 * @brief AMF3使用可变长度的无符号29位整数编码。对于一个正常的32-bit的整数,
 *  需要4个字节来存储,然而前3个字节的最高位是用于标识下一个字节是不是整数的一部分。
 *  该编码规则可参考AMF3官方协议1.3.1节
 *
 *  例如:
 *   0x7A    : 表示一个7-bit整数,因为首位是0,后面的字节就不是该整数的一部分.
 *            该编码表示的整数值为 0x7A
 *  0x8A7B : 表示一个14-bit整数,因为第2个字节的首位为0,之后字节就不是该整数的一部分。
 *                该编码表示的整数值为 (0x8A & 0x7F) << 7 | 0x7B,
 *   也就是这两个字节的后7位拼在一起构成的14-bit值
 *  0x8A9B6C : 表示一个21-bit整数,因为第3个字节的首位为0,之后字节就不是该整数的一部分。
 *    该编码表示的整数值为  ( (0x8A & 0x7F) << 7 | (0x9B & 0x7F) )<< 7 | 0x6C
 *    也就是这三个字节的后7位拼在一起构成的21-bit值
 *  0x8A9BAC6D : 表示一个29-bit整数,也就是前三个字节的后7位和最后一个字节拼在一起构成的29-bit值,
 *    该表吗表示的整数值为  ( ( (0x8A & 0x7F) << 7 | (0x9B & 0x7F) )<< 7 | 0xAC ) << 8 | 0x6D
 *
 * @param data : [输入] 存有编码数据
 * @param valp  : [输出] 用于存放获取到的整数值
 *
 * @return 返回用于表示该整数的字节个数.
 */
int AMF3ReadInteger(const char *data, int32_t *valp);

/**
 * @brief 从data中读取字符串,字符串格式(参考AMF3官方协议1.3.2节) :
 *
 *   | 可变长度的无符号整数(1/2/3/4字节) |  字符串数据 (可选,依据前面整数最后一位确定) |
 *
 *   如果无符号整数值的最低位为0, 则该值表示字符串引用的索引指数。
 *   如果无符号整数值的最低位为1, 则该值剩下的位数表示字符串的长度,其后跟字符串数据。
 *
 * @param data : [输入]原始数据区
 * @param str   : [输出] 结构体存放读取出来的字符串数据及长度
 *
 * @return 总共读取的字节个数.
 */
int AMF3ReadString(const char *data, AVal *str)
{
int32_t ref = 0;
int len;
assert(str != 0);

// 读取可变长度的无符号29位整数,该值保存在ref中,返回值len表示该整数值的字节个数
len = AMF3ReadInteger(data, &ref); 
data += len;

// 如果无符号整数值的最低位为0,则表示字符串引用的编码,余下的位数用来编码字符串引用的索引指数
if ((ref & 0x1) == 0)
{
/* reference: 0xxx */
uint32_t refIndex = (ref >> 1);
return len;
}
else // 如果无符号整数值的最低位为1, 则字符串是字面上编码的,余下的位数用来表示UTF-8编码的字节的长度
{
uint32_t nSize = (ref >> 1); // 去掉最后一位,剩下的位数表示长度,所以左移一位
str->av_val = (char *)data;
str->av_len = nSize;
return len + nSize;
}

return len;
}

/**
 * @brief 解码一个AMF3属性,包括属性的名字、类型及值。
 *        AMF3数据类型的定义见官方文档第3章。
 *
 * @param prop     : [输出]用于保存解码出来的AMF3属性
 * @param pBuffer : [输入]缓冲区存有已编码的AMF3属性
 * @param nSize   : [输入]pBuffer的大小
 * @param bDecodeName : [输入]是否解码属性名字
 *
 * @return 解码过程中所读取的字节个数。
 */
int AMF3Prop_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName);

/**
 * @brief 解码一个AMF属性,包括属性的名字、长度、类型和值
 *
 * @param prop     : [输出]用于保存解码出来的AMF属性
 * @param pBuffer : [输入]缓冲区存有已编码的AMF属性
 * @param nSize   : [输入]pBuffer的大小
 * @param bDecodeName : [输入]是否解码属性名字
 *
 * @return 解码过程中所读取的字节个数。
 */
int AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize, int bDecodeName)
{
... ... (省略部分代码)
if (bDecodeName)
{
// 获取属性名字字符串的长度,该长度值是一个short整型,占2个字节
unsigned short nNameSize = AMF_DecodeInt16(pBuffer);

// 属性名字的字符串长度比给定的数据长度还要长,表示名字长度超出范围 
if (nNameSize > nSize - 2) 
return -1;

// 解码字符串,前2个字节表示长度,其后是字符串 
AMF_DecodeString(pBuffer, &prop->p_name); 
nSize -= 2 + nNameSize; 
pBuffer += 2 + nNameSize;
}

// 属性名字其后没有数据了,无法得到该名字对应的值,因此解码失败,返回-1。 
if (nSize == 0)
return -1;

// 名字的值的类型占1个字节,所以此处nSize需要减1,*pBuffer就是类型 
nSize--; 
prop->p_type = *pBuffer++;

// AMF0数据类型介绍,参考AMF0官方文档第2章 
switch (prop->p_type)
{
case AMF_NUMBER: // double值,8个字节
... ... (省略部分代码)
case AMF_BOOLEAN: // boolean值,1个字节
... ... (省略部分代码)
case AMF_STRING: // 字符串格式 : | 长度(2字节)| 真实字符串 |
... ... (省略部分代码)
case AMF_OBJECT: // AMF对象,里面存有该对象的各种属性
... ... (省略部分代码)
case AMF_MOVIECLIP: // 保留,但不支持
... ... (省略部分代码)
... ...(省略剩下的case)
}

return nOriginalSize - nSize;
}

/**
 * @brief 输出显示AMF属性的名字和值
 */
void AMFProp_Dump(AMFObjectProperty *prop);

/**
 * @brief 重置AMF属性
 */
void AMFProp_Reset(AMFObjectProperty *prop);

/**
 * @brief 编码AMF对象,一个AMF对象包括多个AMF属性及其个数。
 */
char *AMF_Encode(AMFObject *obj, char *pBuffer, char *pBufEnd)
{
... ... (省略部分代码)
// 循环编码每一个AMF属性
for (i = 0; i < obj->o_num; i++)
{
char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd);
... ... (省略部分代码)
}

if (pBuffer + 3 >= pBufEnd) 
return NULL; /* no room for the end marker */

// 将AMF结束标志编码进去,占3个字节,即0x000009 
pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END); 
return pBuffer;
}

/**
 * @brief 解码一个属性数组,将解码出来的属性存放在AMF对象obj中
 *
 * @return 如果解码成功则返回读取的字节数,否则返回-1.
 */
int AMF_DecodeArray(AMFObject *obj, const char *pBuffer, int nSize, int nArrayLen, int bDecodeName);

/**
 * @brief 解码一个AMF3数据
 *
 * @return 读取的字节数.
 */
int AMF3_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bAMFData)
{
... ... (省略部分代码)
if ((ref & 1) == 0) // 不是实例,而是一个引用,参考AMF3协议3.12节关于U290-ref的解释
{
/* object reference, 0xxx */
uint32_t objectIndex = (ref >> 1);
RTMP_Log(RTMP_LOGDEBUG, "Object reference, index: %d", objectIndex);
}
else /* object instance,对象实例 */
{
int32_t classRef = (ref >> 1);
AMF3ClassDef cd = { {0, 0} };
AMFObjectProperty prop;

if ((classRef & 0x1) == 0) // 引用发送对象特性,参考AMF3协议3.12节关于U290-traits-ref的解释
{
/* class reference */
uint32_t classIndex = (classRef >> 1);
RTMP_Log(RTMP_LOGDEBUG, "Class reference: %d", classIndex);
}
else // 参考AMF3协议3.12节关于U290-traits-ext 和 U290-traits 的解释
{
int32_t classExtRef = (classRef >> 1); 
int i;

cd.cd_externalizable = (classExtRef & 0x1) == 1;   // 如果为1,则剩下的位数无意义(特性成员数量必须是0)
cd.cd_dynamic = ((classExtRef >> 1) & 0x1) == 1; // 是否动态,0表示不是动态的,1表示是动态的
cd.cd_num = classExtRef >> 2; // 编码封装好的特性成员的名称的数量,在它之后是类名

... ... (省略部分代码)
// 循环获取特性成员的名称,并将其添加到类cd中
for (i = 0; i < cd.cd_num; i++)
{
... ... (省略部分代码)
}
}

... ... (省略部分代码)
}

return nOriginalSize - nSize;
}

/**
 * @brief 解码一个AMF对象
 *
 * @return 如果解码成功则返回读取的字节数,否则返回-1.
 */
int AMF_Decode(AMFObject *obj, const char *pBuffer, int nSize, int bDecodeName)
{
int nOriginalSize = nSize; 
int bError = FALSE; /* if there is an error while decoding - try to at least find the end mark AMF_OBJECT_END */ 

obj->o_num = 0; 
obj->o_props = NULL;

// 循环解码一系列AMF属性,将其添加到AMF对象obj中
while (nSize > 0)
{
AMFObjectProperty prop; 
int nRes;

// 接下来的3个字节是AMF_OBJECT_END,表示AMF对象结束
if (nSize >=3 && AMF_DecodeInt24(pBuffer) == AMF_OBJECT_END)
{
nSize -= 3; 
bError = FALSE; 
break;
}

if (bError)
RTMP_Log(RTMP_LOGERROR, "DECODING ERROR, IGNORING BYTES UNTIL NEXT KNOWN PATTERN!"); 
nSize--; 
pBuffer++;
continue; 
}

// 解码一个AMF属性,属性相关信息存放在prop中,返回读取的字节个数 
nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
if (nRes == -1) 
bError = TRUE;
else
{
nSize -= nRes;
pBuffer += nRes;
AMF_AddProp(obj, &prop); // 将解码出来的属性添加到AMF对象obj中
}
}

if (bError)
return -1;
return nOriginalSize - nSize;
}

/**
 * @brief 将一个AMF属性添加到AMF对象的属性数组中。
 */
void AMF_AddProp(AMFObject *obj, const AMFObjectProperty *prop);

/**
 * @brief 获取一个AMF对象的属性数目
 */
int AMF_CountProp(AMFObject *obj);

/**
 * @brief 根据属性名字或者索引指数获取相应的AMF属性。优先考虑索引指数,
 *  除非索引指数为负数,才考虑采用属性名字进行搜索。
 *
 * @return 搜索成功则返回相应的属性,否则返回无效的属性。
 */
AMFObjectProperty *AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex);

/**
 * @brief 输出显示一个AMF对象的所有属性的信息。
 */
void AMF_Dump(AMFObject *obj);

/**
 * @brief 重置一个AMF对象,首先重置所有属性,释放属性空间。然后设置AMF属性为空,数目为0。
 */
void AMF_Reset(AMFObject *obj);

/**
 * @brief 添加一个属性到类cd中.
 */
void AMF3CD_AddProp(AMF3ClassDef *cd, AVal *prop);

/**
 * @brief 根据索引指数获取类cd的属性
 */
AVal *AMF3CD_GetProp(AMF3ClassDef *cd, int nIndex);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值