librtmp源码分析之AMF格式

AMF是Adobe公司开发的一种高效的消息序列化和反序列化协议,它包括两种数据类型格式:AMF0和AMF3。其中AMF0是基本的消息格式,但是后来Adobe对AMF0进行了优化,开发了扩展的AMF3,即AMF3并不是AMF0的完全替代,使用AMF3需要在外面套一层AMF0做为容器。我们可以在librtmp的源码中找到AMF的一个实现版本,但是我不清楚什么原因,居然有些函数如AMFProp_SetNumber()至今都没有实现。

”水满则溢,月满则亏”,librtmp虽然有些瑕疵,但是仍然不影响它是那么的优秀,如果在朋友们的自研项目或者工作中有机会用到它,我还是非常推荐的。

结构定义:

AMF0支持的类型定义:

typedef enum
{ 
    AMF_NUMBER = 0,  // 数字,用8字节double表示
    AMF_BOOLEAN,  // 布尔数,用1字节整数表示
    AMF_STRING,  // 字符串,用LV范式表示
    AMF_OBJECT,  // 对象
    AMF_MOVIECLIP,  // 保留,未使用
    AMF_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支持的类型定义:

typedef enum
{
    AMF3_UNDEFINED = 0,  // 未定义
    AMF3_NULL,  // 空类型
    AMF3_FALSE,  // FALSE
    AMF3_TRUE,  // TRUE
    AMF3_INTEGER,  // 整型
    AMF3_DOUBLE,  // 浮点型
    AMF3_STRING,  // 字符串
    AMF3_XML_DOC,  // XML文档
    AMF3_DATE,  // 日期
    AMF3_ARRAY,  // 数组
    AMF3_OBJECT,  // 对象
    AMF3_XML,  // XML
    AMF3_BYTE_ARRAY  // 字节数组
} AMF3DataType;

字符串定义:

typedef struct AVal
{
    char *av_val;
    int av_len;
} AVal;

// 初使化串
#define AVC(str)        {str,sizeof(str)-1}
// 比较串
#define AVMATCH(a1,a2)  ((a1)->av_len == (a2)->av_len && !memcmp((a1)->av_val,(a2)->av_val,(a1)->av_len))

对象属性定义:

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;
} AMFObjectProperty;

对象定义:

typedef struct AMFObject
{
    int o_num;
    struct AMFObjectProperty *o_props;
} AMFObject;

函数定义:

整数操作:

注意:由于整数不属于AMF的基本类型,序列化时不会压入类型字节。

// 16位整数序列化
char *AMF_EncodeInt16(char *output, char *outend, short nVal);
// 24位整数序列化
char *AMF_EncodeInt24(char *output, char *outend, int nVal);
// 32位整数序列化
char *AMF_EncodeInt32(char *output, char *outend, int nVal);

// 16位整数反序列化
unsigned short AMF_DecodeInt16(const char *data);
// 24位整数反序列化
unsigned int AMF_DecodeInt24(const char *data);
// 32位整数反序列化
unsigned int AMF_DecodeInt32(const char *data);
基本类型操作:

注意:基本类型序列化时会压入类型字节,但反序列化时需跳过类型字节。

// 浮点数序列化
char *AMF_EncodeNumber(char *output, char *outend, double dVal);
// Bool型序列化
char *AMF_EncodeBoolean(char *output, char *outend, int bVal);
// 字符串序列化,自动支持短串和长串,即当串长度>=65536时使用长串
char *AMF_EncodeString(char *output, char *outend, const AVal * str);

// 浮点数反序列化
double AMF_DecodeNumber(const char *data);
// Bool型反序列化
int AMF_DecodeBoolean(const char *data);
// 短串反序列化
void AMF_DecodeString(const char *data, AVal * str);
// 长串反序列化
void AMF_DecodeLongString(const char *data, AVal * str);
命名的基本类型操作:

注意:命名的基本类型是复合操作,序列化时首先压入不带类型的字符串,然后压入带类型的基本类型。

// 串值KV序列化,用于属性或数组元素
char *AMF_EncodeNamedString(char *output, char *outend, const AVal * name, const AVal * value);
// 浮点数KV序列化,用于属性或数组元素
char *AMF_EncodeNamedNumber(char *output, char *outend, const AVal * name, double dVal);
// Bool型KV序列化,用于属性或数组元素
char *AMF_EncodeNamedBoolean(char *output, char *outend, const AVal * name, int bVal);
对象操作:
// 打印对象信息
void AMF_Dump(AMFObject * obj);

// 添加新属性到对象中,若容量不够,可能会申请空间
void AMF_AddProp(AMFObject * obj, const AMFObjectProperty * prop);
// 获取对象的属性个数
int AMF_CountProp(AMFObject * obj);

// 对象序列化
char *AMF_Encode(AMFObject * obj, char *pBuffer, char *pBufEnd);
// 对象反序列化,bDecodeName为FALSE表示不解析属性名称
int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName);

// 重置对象,释放属性空间
void AMF_Reset(AMFObject * obj);
对象属性操作:
// 打印对象属性
void AMFProp_Dump(AMFObjectProperty * prop);

// 对象属性序列化
char *AMFProp_Encode(AMFObjectProperty * prop, char *pBuffer, char *pBufEnd);
// 对象属性反序列化,bDecodeName为FALSE表示不解析属性名称
int AMFProp_Decode(AMFObjectProperty * prop, const char *pBuffer, int nSize, int bDecodeName);

// 重置对象属性
void AMFProp_Reset(AMFObjectProperty * prop);
对象属性访问:
// 根据索引位置或Key名称找属性,当nIndex>=0时,表示按索引查找,否则按名称查找
AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex);

// 检查是否无效属性
int AMFProp_IsValid(AMFObjectProperty * prop);

// 设置属性名称
void AMFProp_SetName(AMFObjectProperty * prop, AVal * name);
// 获取属性名称
void AMFProp_GetName(AMFObjectProperty * prop, AVal * name);

// 获取属性的值类型
AMFDataType AMFProp_GetType(AMFObjectProperty * prop);

// 获取浮点数属性值
double AMFProp_GetNumber(AMFObjectProperty * prop);
// 获取Bool型属性值
int AMFProp_GetBoolean(AMFObjectProperty * prop);
// 获取字符串属性值
void AMFProp_GetString(AMFObjectProperty * prop, AVal * str);
// 获取对象属性值
void AMFProp_GetObject(AMFObjectProperty * prop, AMFObject * obj);
数组操作:

注意:librtmp的实现中没有数组的序列化操作接口,我查阅了源代码,发现AMF_DecodeArray()是可以反序列化对象的,但是首先必须先获得数组元素的个数。

// 数组反序列化
int AMF_DecodeArray(AMFObject * obj, const char *pBuffer, int nSize, int nArrayLen, int bDecodeName);

源码解析:

为了加深对实现的理解,下面我摘录一部分源码,代码中加了一些注册,与诸位一起分享。

整数的序列化与反序列化:

char *
AMF_EncodeInt32(char *output, char *outend, int nVal)
{
  // 预判长度溢出
  if (output+4 > outend)
    return NULL;

  // 小端转大端
  output[3] = nVal & 0xff;
  output[2] = nVal >> 8;
  output[1] = nVal >> 16;
  output[0] = nVal >> 24;
  return output+4;
}

unsigned int
AMF_DecodeInt32(const char *data)
{
  unsigned char *c = (unsigned char *)data;
  unsigned int val;
  // 大端转小端
  val = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
  return val;
}

浮点数的序列化与反序列化:

char *
AMF_EncodeNumber(char *output, char *outend, double dVal)
{
  // 预判长度溢出,需要1字节类型+8字节浮点
  if (output+1+8 > outend)
    return NULL;

  // 压入类型
  *output++ = AMF_NUMBER;       /* type: Number */

  // 小端转大端
  {
    unsigned char *ci, *co;
    ci = (unsigned char *)&dVal;
    co = (unsigned char *)output;
    co[0] = ci[7];
    co[1] = ci[6];
    co[2] = ci[5];
    co[3] = ci[4];
    co[4] = ci[3];
    co[5] = ci[2];
    co[6] = ci[1];
    co[7] = ci[0];
  }

  return output+8;
}

double
AMF_DecodeNumber(const char *data)
{
  double dVal;
  unsigned char *ci, *co; 
  ci = (unsigned char *)data;
  co = (unsigned char *)&dVal;
  // 大端转小端
  co[0] = ci[7];
  co[1] = ci[6];
  co[2] = ci[5];
  co[3] = ci[4];
  co[4] = ci[3];
  co[5] = ci[2];
  co[6] = ci[1];
  co[7] = ci[0];
  return dVal;
}

Bool型的序列化与反序列化:

char *
AMF_EncodeBoolean(char *output, char *outend, int bVal)
{
  // 预判长度溢出,需要1字节类型+1字节Bool
  if (output+2 > outend)
    return NULL;

  // 压入类型
  *output++ = AMF_BOOLEAN;

  *output++ = bVal ? 0x01 : 0x00;

  return output;
}

int
AMF_DecodeBoolean(const char *data)
{
  return *data != 0;
}

字符串的序列化与反序列化:

char *
AMF_EncodeString(char *output, char *outend, const AVal *bv)
{
  // 预判长度溢出,短串需要1字节类型+2字节短长度+字符串长度,或长串需要1字节类型+4字节长长度+字符串长度
  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;

      // 压入2字节长度
      output = AMF_EncodeInt16(output, outend, bv->av_len);
    }
  else
    {
      // 压入类型
      *output++ = AMF_LONG_STRING;

      // 压入4字节长度
      output = AMF_EncodeInt32(output, outend, bv->av_len);
    }

  // 拷贝bv指向的字符中到序列化缓冲
  memcpy(output, bv->av_val, bv->av_len);
  output += bv->av_len;

  return output;
}

void
AMF_DecodeString(const char *data, AVal *bv)
{
  // 解2字节大小
  bv->av_len = AMF_DecodeInt16(data);
  // 设置bv串指向
  bv->av_val = (bv->av_len > 0) ? (char *)data + 2 : NULL;
}

void
AMF_DecodeLongString(const char *data, AVal *bv)
{
  // 解4字节大小
  bv->av_len = AMF_DecodeInt32(data);
  // 设置bv串指向
  bv->av_val = (bv->av_len > 0) ? (char *)data + 4 : NULL;
}

命名的基本类型的序列化与反序列化:

char *
AMF_EncodeNamedString(char *output, char *outend, const AVal *strName, const AVal *strValue)
{
  // 预判长度溢出,需要2字节串长度+命名串长度
  if (output+2+strName->av_len > outend)
    return NULL;

  // 压入2字节长度
  output = AMF_EncodeInt16(output, outend, strName->av_len);

  // 拷贝串
  memcpy(output, strName->av_val, strName->av_len);
  output += strName->av_len;

  // 继续压入基本串
  return AMF_EncodeString(output, outend, strValue);
}

char *
AMF_EncodeNamedNumber(char *output, char *outend, const AVal *strName, double dVal)
{
  // 预判长度溢出,需要2字节串长度+命名串长度
  if (output+2+strName->av_len > outend)
    return NULL;

  // 压入2字节长度
  output = AMF_EncodeInt16(output, outend, strName->av_len);

  // 拷贝串
  memcpy(output, strName->av_val, strName->av_len);
  output += strName->av_len;

  // 继续压入基本浮点数
  return AMF_EncodeNumber(output, outend, dVal);
}

char *
AMF_EncodeNamedBoolean(char *output, char *outend, const AVal *strName, int bVal)
{
  // 预判长度溢出,需要2字节串长度+命名串长度
  if (output+2+strName->av_len > outend)
    return NULL;

  // 压入2字节长度
  output = AMF_EncodeInt16(output, outend, strName->av_len);

  // 拷贝串
  memcpy(output, strName->av_val, strName->av_len);
  output += strName->av_len;

  // 继续压入基本Bool
  return AMF_EncodeBoolean(output, outend, bVal);
}

属性的操作,这些都属于普通的结构体操作:

void
AMFProp_GetName(AMFObjectProperty *prop, AVal *name)
{
  *name = prop->p_name;
}

void
AMFProp_SetName(AMFObjectProperty *prop, AVal *name)
{
  prop->p_name = *name;
}

AMFDataType
AMFProp_GetType(AMFObjectProperty *prop)
{
  return prop->p_type;
}

double
AMFProp_GetNumber(AMFObjectProperty *prop)
{
  return prop->p_vu.p_number;
}

int
AMFProp_GetBoolean(AMFObjectProperty *prop)
{
  return prop->p_vu.p_number != 0;
}

void
AMFProp_GetString(AMFObjectProperty *prop, AVal *str)
{
  *str = prop->p_vu.p_aval;
}

void
AMFProp_GetObject(AMFObjectProperty *prop, AMFObject *obj)
{
  *obj = prop->p_vu.p_object;
}

int
AMFProp_IsValid(AMFObjectProperty *prop)
{
  return prop->p_type != AMF_INVALID;
}

void
AMF_AddProp(AMFObject *obj, const AMFObjectProperty *prop)
{
  if (!(obj->o_num & 0x0f))
    obj->o_props =
      realloc(obj->o_props, (obj->o_num + 16) * sizeof(AMFObjectProperty));
  obj->o_props[obj->o_num++] = *prop;
}

int
AMF_CountProp(AMFObject *obj)
{
  return obj->o_num;
}

AMFObjectProperty *
AMF_GetProp(AMFObject *obj, const AVal *name, int nIndex)
{
  // 索引有效时,按索引取属性
  if (nIndex >= 0)
    {
      if (nIndex <= obj->o_num)
        return &obj->o_props[nIndex];
    }
  // 否则按名称取属性
  else
    {
      int n;
      for (n = 0; n < obj->o_num; n++)
        {
          if (AVMATCH(&obj->o_props[n].p_name, name))
            return &obj->o_props[n];
        }
    }

  return (AMFObjectProperty *)&AMFProp_Invalid;
}

属性的序列化与反序列化:

char *
AMFProp_Encode(AMFObjectProperty *prop, char *pBuffer, char *pBufEnd)
{
  // 无效属性不处理
  if (prop->p_type == AMF_INVALID)
    return NULL;

  // 预判长度溢出,需要2字节串长度+属性名称长度+1字节类型
  if (prop->p_type != AMF_NULL && pBuffer + prop->p_name.av_len + 2 + 1 >= pBufEnd)
    return NULL;

  // 属性名称非空,压入属性名称
  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)
    {
    case AMF_NUMBER:
      pBuffer = AMF_EncodeNumber(pBuffer, pBufEnd, prop->p_vu.p_number);
      break;

    case AMF_BOOLEAN:
      pBuffer = AMF_EncodeBoolean(pBuffer, pBufEnd, prop->p_vu.p_number != 0);
      break;

    case AMF_STRING:
      pBuffer = AMF_EncodeString(pBuffer, pBufEnd, &prop->p_vu.p_aval);
      break;

    // 压入NULL
    case AMF_NULL:
      if (pBuffer+1 >= pBufEnd)
        return NULL;
      *pBuffer++ = AMF_NULL;
      break;

    // 压入子对象
    case AMF_OBJECT:
      pBuffer = AMF_Encode(&prop->p_vu.p_object, pBuffer, pBufEnd);
      break;

    default:
      RTMP_Log(RTMP_LOGERROR, "%s, invalid type. %d", __FUNCTION__, prop->p_type);
      pBuffer = NULL;
    };

  return pBuffer;
}

int
AMFProp_Decode(AMFObjectProperty *prop, const char *pBuffer, int nSize,
               int bDecodeName)
{
  int nOriginalSize = nSize;
  int nRes;

  prop->p_name.av_len = 0;
  prop->p_name.av_val = NULL;

  if (nSize == 0 || !pBuffer)
    {
      RTMP_Log(RTMP_LOGDEBUG, "%s: Empty buffer/no buffer pointer!", __FUNCTION__);
      return -1;
    }

  // 包含属性名称时,最小长度为2字节属性名称长度指示+至少1字节属性+至少1字节值的类型
  if (bDecodeName && nSize < 4)
    {                           /* at least name (length + at least 1 byte) and 1 byte of data */
      RTMP_Log(RTMP_LOGDEBUG,
          "%s: Not enough data for decoding with name, less than 4 bytes!",
          __FUNCTION__);
      return -1;
    }

  // 解属性名称
  if (bDecodeName)
    {
      unsigned short nNameSize = AMF_DecodeInt16(pBuffer);
      if (nNameSize > nSize - 2)
        {
          RTMP_Log(RTMP_LOGDEBUG,
              "%s: Name size out of range: namesize (%d) > len (%d) - 2",
              __FUNCTION__, nNameSize, nSize);
          return -1;
        }

      AMF_DecodeString(pBuffer, &prop->p_name);
      nSize -= 2 + nNameSize;
      pBuffer += 2 + nNameSize;
    }

  if (nSize == 0)
    {
      return -1;
    }

  nSize--;

  // 解属性值
  prop->p_type = *pBuffer++;
  switch (prop->p_type)
    {
    case AMF_NUMBER:
      if (nSize < 8)
        return -1;
      prop->p_vu.p_number = AMF_DecodeNumber(pBuffer);
      nSize -= 8;
      break;
    case AMF_BOOLEAN:
      if (nSize < 1)
        return -1;
      prop->p_vu.p_number = (double)AMF_DecodeBoolean(pBuffer);
      nSize--;
      break;
    case AMF_STRING:
      {
        unsigned short nStringSize = AMF_DecodeInt16(pBuffer);

        if (nSize < (long)nStringSize + 2)
          return -1;
        AMF_DecodeString(pBuffer, &prop->p_vu.p_aval);
        nSize -= (2 + nStringSize);
        break;
      }
    case AMF_OBJECT:
      {
        int nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE);
        if (nRes == -1)
          return -1;
        nSize -= nRes;
        break;
      }
    case AMF_MOVIECLIP:
      {
        RTMP_Log(RTMP_LOGERROR, "AMF_MOVIECLIP reserved!");
        return -1;
        break;
      }
    case AMF_NULL:
    case AMF_UNDEFINED:
    case AMF_UNSUPPORTED:
      prop->p_type = AMF_NULL;
      break;
    case AMF_REFERENCE:
      {
        RTMP_Log(RTMP_LOGERROR, "AMF_REFERENCE not supported!");
        return -1;
        break;
      }
    case AMF_ECMA_ARRAY:
      {
        nSize -= 4;

        // 如果是数组,按对象方式反序列化
        /* next comes the rest, mixed array has a final 0x000009 mark and names, so its an object */
        nRes = AMF_Decode(&prop->p_vu.p_object, pBuffer + 4, nSize, TRUE);
        if (nRes == -1)
          return -1;
        nSize -= nRes;
        prop->p_type = AMF_OBJECT;
        break;
      }
    case AMF_OBJECT_END:
      {
        return -1;
        break;
      }
    case AMF_STRICT_ARRAY:
      {
        unsigned int nArrayLen = AMF_DecodeInt32(pBuffer);
        nSize -= 4;

        nRes = AMF_DecodeArray(&prop->p_vu.p_object, pBuffer + 4, nSize,
                                   nArrayLen, FALSE);
        if (nRes == -1)
          return -1;
        nSize -= nRes;
        prop->p_type = AMF_OBJECT;
        break;
      }
    case AMF_DATE:
      {
        RTMP_Log(RTMP_LOGDEBUG, "AMF_DATE");

        if (nSize < 10)
          return -1;

        prop->p_vu.p_number = AMF_DecodeNumber(pBuffer);
        prop->p_UTCoffset = AMF_DecodeInt16(pBuffer + 8);

        nSize -= 10;
        break;
      }
    case AMF_LONG_STRING:
      {
        unsigned int nStringSize = AMF_DecodeInt32(pBuffer);
        if (nSize < (long)nStringSize + 4)
          return -1;
        AMF_DecodeLongString(pBuffer, &prop->p_vu.p_aval);
        nSize -= (4 + nStringSize);
        prop->p_type = AMF_STRING;
        break;
      }
    case AMF_RECORDSET:
      {
        RTMP_Log(RTMP_LOGERROR, "AMF_RECORDSET reserved!");
        return -1;
        break;
      }
    case AMF_XML_DOC:
      {
        RTMP_Log(RTMP_LOGERROR, "AMF_XML_DOC not supported!");
        return -1;
        break;
      }
    case AMF_TYPED_OBJECT:
      {
        RTMP_Log(RTMP_LOGERROR, "AMF_TYPED_OBJECT not supported!");
        return -1;
        break;
      }
    case AMF_AVMPLUS:
      {
        int nRes = AMF3_Decode(&prop->p_vu.p_object, pBuffer, nSize, TRUE);
        if (nRes == -1)
          return -1;
        nSize -= nRes;
        prop->p_type = AMF_OBJECT;
        break;
      }
    default:
      RTMP_Log(RTMP_LOGDEBUG, "%s - unknown datatype 0x%02x, @0x%08X", __FUNCTION__,
          prop->p_type, pBuffer - 1);
      return -1;
    }

  return nOriginalSize - nSize;
}

对象的序列化与反序列化:

char *
AMF_Encode(AMFObject *obj, char *pBuffer, char *pBufEnd)
{
  int i;

  // 预判长度溢出,需要5个字节
  if (pBuffer+4 >= pBufEnd)
    return NULL;

  // 压入类型
  *pBuffer++ = AMF_OBJECT;

  // 遍历属性并压入
  for (i = 0; i < obj->o_num; i++)
    {
      char *res = AMFProp_Encode(&obj->o_props[i], pBuffer, pBufEnd);
      if (res == NULL)
        {
          RTMP_Log(RTMP_LOGERROR, "AMF_Encode - failed to encode property in index %d",
              i);
          break;
        }
      else
        {
          pBuffer = res;
        }
    }

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

  // 压入对象结束标记
  pBuffer = AMF_EncodeInt24(pBuffer, pBufEnd, AMF_OBJECT_END);

  return pBuffer;
}

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;
  while (nSize > 0)
    {
      AMFObjectProperty prop;
      int nRes;

      // 判断结束标志
      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;
        }

      // 解属性
      nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
      if (nRes == -1)
        bError = TRUE;
      else
        {
          nSize -= nRes;
          pBuffer += nRes;
          AMF_AddProp(obj, &prop);
        }
    }

  if (bError)
    return -1;

  return nOriginalSize - nSize;
}

数组的反序列化:

int
AMF_DecodeArray(AMFObject *obj, const char *pBuffer, int nSize,
                int nArrayLen, int bDecodeName)
{
  int nOriginalSize = nSize;
  int bError = FALSE;

  obj->o_num = 0;
  obj->o_props = NULL;
  while (nArrayLen > 0)
    {
      AMFObjectProperty prop;
      int nRes;
      nArrayLen--;

      // 解属性
      nRes = AMFProp_Decode(&prop, pBuffer, nSize, bDecodeName);
      if (nRes == -1)
        bError = TRUE;
      else
        {
          nSize -= nRes;
          pBuffer += nRes;
          AMF_AddProp(obj, &prop);
        }
    }
  if (bError)
    return -1;

  return nOriginalSize - nSize;
}

编码格式:

AMF的编码基本遵循TLV范式,但是为了进一步节省空间,比如对象的属性,在序列化属性名称的时候,即使属性名是字符串,也并不压入类型。我想这有两个原因,一是压入类型会增加序列化的长度,二是开放了TLV,就等于支持属性名称可以是其它的类型。这是一个驳论,即然不能做到完美,那就做到安全。当然,类似这样的约束加入,等于在算法中加了很多特殊条件,对使用者来说也无法形成统一的规范思维,这也是无耐之举吧。

关于编码格式,如果您读懂了AMF的源码实现,基本上也就明白了序列化后的数据排队。百度上可以搜索到很多关于编码格式的精彩说明和图解,出于文档的完整性,所以我这里只简单地描述几个结构。如下:

浮点数:
0x00 + 8字节浮点数

Bool型:
0x01 + 1字节Bool值

短字符串:
0x02 + 2字节长度 + 字符串
长字符串
0x02 + 4字节长度 + 字符串

对象:
0x03 + 属性1名称长度 + 属性1名称 + 1字节属性1类型 + n字节属性1值 + 属性2名称长度 + 属性2名称 + 1字节属性2类型 + n字节属性2值 + 3字节结尾标志

使用举例:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <librtmp/amf.h>
#include <librtmp/log.h>

int main(int argc, char* argv[])
{
    char sBuf[128] = {0};

    char* pStart = sBuf;
    char* pEnd = sBuf + sizeof(sBuf);

    // 整数操作:
    {
        char* pPos = AMF_EncodeInt16(pStart, pEnd, 1);
        pPos = AMF_EncodeInt24(pPos, pEnd, 2);
        pPos = AMF_EncodeInt32(pPos, pEnd, 3);

        int n1 = AMF_DecodeInt16(pStart);
        int n2 = AMF_DecodeInt24(pStart + 2);
        int n3 = AMF_DecodeInt32(pStart + 2 + 3);
        printf("n1=%d, n2=%d, n3=%d \n", n1, n2, n3);
    }

    // 基本类型操作:
    {
        char* pPos = AMF_EncodeNumber(pStart, pEnd, 1);
        pPos = AMF_EncodeBoolean(pPos, pEnd, TRUE);
        AVal str = AVC("hello");
        pPos = AMF_EncodeString(pPos, pEnd, &str);

        double f = AMF_DecodeNumber(pStart + 1);
        int b = AMF_DecodeBoolean(pStart + 1 + 8);
        AVal str2 = {NULL, 0};
        AMF_DecodeString(pStart + 1 + 8 + 1 + 1 + 1, &str2);
        printf("f:%.02f b:%d str:%s \n", f, b, str2.av_val);
    }

    // 对象操作:
    {
        AMFObject obj = {0, NULL};

        AMFObjectProperty prop1, prop2;
        AVal key1 = AVC("key1");
        AMFProp_SetName(&prop1, &key1);
        prop1.p_type = AMF_NUMBER;
        prop1.p_vu.p_number = 1;
        AVal key2 = AVC("key2");
        AMFProp_SetName(&prop2, &key2);
        prop2.p_type = AMF_STRING;
        prop2.p_vu.p_aval = AVC("test");
        AMF_AddProp(&obj, &prop1);
        AMF_AddProp(&obj, &prop2);

        char* pPos = AMF_Encode(&obj, pStart, pEnd);

        AMFObject obj2;
        AMF_Decode(&obj2, pStart + 1, pPos - pStart - 1, TRUE);

        RTMP_LogSetLevel(RTMP_LOGDEBUG);
        AMF_Dump(&obj2);

        AMF_Reset(&obj);
        AMF_Reset(&obj2);
    }

    return 0;
}

运行输出:

n1=1, n2=2, n3=3 
f:1.00 b:1 str:hello 
DEBUG: (object begin)
DEBUG: Property: <Name:               key1, NUMBER:     1.00>
DEBUG: Property: <Name:               key2, STRING:     test>
DEBUG: (object end)

最后展示下代码中obj的二进制内容,我用线段分开了,具体交给大家解读吧。


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容