当前位置: 首页 > 工具软件 > AMF > 使用案例 >

AMF编码

锺离声
2023-12-01

转载自:http://nkwavelet.blog.163.com/blog/static/227756038201412031720398/

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_MAX268435455// 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);

 类似资料: