me icon indicating copy to clipboard operation
me copied to clipboard

学习 rtmp 开发 (Part 6: AMF0)

Open nonocast opened this issue 2 years ago • 0 comments

decode

当client play stream的时候server会发送这个stream的metadata,同时也是flv中的第一个tag,可以看到binary如下:

INFO:   0000:  02 00 0a 6f 6e 4d 65 74  61 44 61 74 61 03 00 05   ...onMetaData...  
INFO:   0010:  77 69 64 74 68 00 40 69  00 00 00 00 00 00 00 06   width.@i........  
INFO:   0020:  68 65 69 67 68 74 00 40  69 00 00 00 00 00 00 00   height.@i.......  
INFO:   0030:  0c 64 69 73 70 6c 61 79  57 69 64 74 68 00 40 69   .displayWidth.@i  
INFO:   0040:  00 00 00 00 00 00 00 0d  64 69 73 70 6c 61 79 48   ........displayH  
INFO:   0050:  65 69 67 68 74 00 40 69  00 00 00 00 00 00 00 08   eight.@i........  
INFO:   0060:  64 75 72 61 74 69 6f 6e  00 00 00 00 00 00 00 00   duration........  
INFO:   0070:  00 00 09 66 72 61 6d 65  72 61 74 65 00 40 24 00   ...framerate.@$.  
INFO:   0080:  00 00 00 00 00 00 03 66  70 73 00 40 24 00 00 00   .......fps.@$...  
INFO:   0090:  00 00 00 00 0d 76 69 64  65 6f 64 61 74 61 72 61   .....videodatara  
INFO:   00a0:  74 65 00 40 a3 88 00 00  00 00 00 00 0c 76 69 64   [email protected]  
INFO:   00b0:  65 6f 63 6f 64 65 63 69  64 00 40 1c 00 00 00 00   eocodecid.@.....  
INFO:   00c0:  00 00 00 0d 61 75 64 69  6f 64 61 74 61 72 61 74   ....audiodatarat  
INFO:   00d0:  65 00 40 64 00 00 00 00  00 00 00 0c 61 75 64 69   [email protected]  
INFO:   00e0:  6f 63 6f 64 65 63 69 64  00 40 24 00 00 00 00 00   ocodecid.@$.....  
INFO:   00f0:  00 00 07 70 72 6f 66 69  6c 65 02 00 20 00 00 00   ...profile.. ...  
INFO:   0100:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................  
INFO:   0110:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 05 6c   ...............l  
INFO:   0120:  65 76 65 6c 02 00 20 00  00 00 00 00 00 00 00 00   evel.. .........  
INFO:   0130:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................  
INFO:   0140:  00 00 00 00 00 00 00 00  06 53 65 72 76 65 72 02   .........Server.  
INFO:   0150:  00 07 54 65 6e 67 69 6e  65 00 0e 76 69 64 65 6f   ..Tengine..video  
INFO:   0160:  63 6f 64 65 63 72 65 61  6c 00 00 00 00 00 00 00   codecreal.......  
INFO:   0170:  00 00 00 08 66 69 6c 65  53 69 7a 65 00 00 00 00   ....fileSize....  
INFO:   0180:  00 00 00 00 00 00 0f 61  75 64 69 6f 73 61 6d 70   .......audiosamp  
INFO:   0190:  6c 65 72 61 74 65 00 40  e7 70 00 00 00 00 00 00   [email protected]......  
INFO:   01a0:  0f 61 75 64 69 6f 73 61  6d 70 6c 65 73 69 7a 65   .audiosamplesize  
INFO:   01b0:  00 40 30 00 00 00 00 00  00 00 0d 61 75 64 69 6f   [email protected]  
INFO:   01c0:  63 68 61 6e 6e 65 6c 73  00 40 00 00 00 00 00 00   channels.@......  
INFO:   01d0:  00 00 06 73 74 65 72 65  6f 01 01 00 03 32 2e 31   ...stereo....2.1  
INFO:   01e0:  01 00 00 03 33 2e 31 01  00 00 03 34 2e 30 01 00   ....3.1....4.0..  
INFO:   01f0:  00 03 34 2e 31 01 00 00  03 35 2e 31 01 00 00 03   ..4.1....5.1....  
INFO:   0200:  37 2e 31 01 00 00 07 65  6e 63 6f 64 65 72 02 00   7.1....encoder..  
INFO:   0210:  29 6f 62 73 2d 6f 75 74  70 75 74 20 6d 6f 64 75   )obs-output modu  
INFO:   0220:  6c 65 20 28 6c 69 62 6f  62 73 20 76 65 72 73 69   le (libobs versi  
INFO:   0230:  6f 6e 20 32 37 2e 32 2e  34 29 00 00 09            on 27.2.4)...     

librtmp为我们准备好了amf套餐,直接拿来用就行,

int main() {
  RTMP_LogSetLevel(RTMP_LOGALL);

  uint32_t offset = 0x0d + 11;
  size_t size = 573;
  byte buffer[size];

  FILE *file = fopen("out.flv", "r");
  memset(buffer, 0, size);
  fseek(file, offset, 0);
  fread(buffer, 1, size, file);
  RTMP_LogHexString(RTMP_LOGINFO, buffer, size);
  fclose(file);

  // parse amf
  AMFObject obj;
  AMF_Decode(&obj, (char *) buffer, size, FALSE);
  AMF_Dump(&obj);
  return 0;
}

dump 输出如下:

DEBUG: (object begin)
DEBUG: Property: <Name:           no-name., STRING:     onMetaData>
DEBUG: Property: <Name:           no-name., OBJECT>
DEBUG: (object begin)
DEBUG: Property: <Name:              width, NUMBER:     200.00>
DEBUG: Property: <Name:             height, NUMBER:     200.00>
DEBUG: Property: <Name:       displayWidth, NUMBER:     200.00>
DEBUG: Property: <Name:      displayHeight, NUMBER:     200.00>
DEBUG: Property: <Name:           duration, NUMBER:     0.00>
DEBUG: Property: <Name:          framerate, NUMBER:     10.00>
DEBUG: Property: <Name:                fps, NUMBER:     10.00>
DEBUG: Property: <Name:      videodatarate, NUMBER:     2500.00>
DEBUG: Property: <Name:       videocodecid, NUMBER:     7.00>
DEBUG: Property: <Name:      audiodatarate, NUMBER:     160.00>
DEBUG: Property: <Name:       audiocodecid, NUMBER:     10.00>
DEBUG: Property: <Name:            profile, STRING:     >
DEBUG: Property: <Name:              level, STRING:     >
DEBUG: Property: <Name:             Server, STRING:     Tengine>
DEBUG: Property: <Name:     videocodecreal, NUMBER:     0.00>
DEBUG: Property: <Name:           fileSize, NUMBER:     0.00>
DEBUG: Property: <Name:    audiosamplerate, NUMBER:     48000.00>
DEBUG: Property: <Name:    audiosamplesize, NUMBER:     16.00>
DEBUG: Property: <Name:      audiochannels, NUMBER:     2.00>
DEBUG: Property: <Name:             stereo, BOOLEAN:    TRUE>
DEBUG: Property: <Name:                2.1, BOOLEAN:    FALSE>
DEBUG: Property: <Name:                3.1, BOOLEAN:    FALSE>
DEBUG: Property: <Name:                4.0, BOOLEAN:    FALSE>
DEBUG: Property: <Name:                4.1, BOOLEAN:    FALSE>
DEBUG: Property: <Name:                5.1, BOOLEAN:    FALSE>
DEBUG: Property: <Name:                7.1, BOOLEAN:    FALSE>
DEBUG: Property: <Name:            encoder, STRING:     obs-output module (libobs version 27.2.4)>
DEBUG: (object end)
DEBUG: (object end)

DumpMetaData定义在rtmp.c中,因为是static,所以直接复制过来,

static int DumpMetaData(AMFObject *obj) {
  AMFObjectProperty *prop;
  int n, len;
  for (n = 0; n < obj->o_num; n++) {
    char str[256] = "";
    prop = AMF_GetProp(obj, NULL, n);
    switch (prop->p_type) {
    case AMF_OBJECT:
    case AMF_ECMA_ARRAY:
    case AMF_STRICT_ARRAY:
      if (prop->p_name.av_len) RTMP_Log(RTMP_LOGINFO, "%.*s:", prop->p_name.av_len, prop->p_name.av_val);
      DumpMetaData(&prop->p_vu.p_object);
      break;
    case AMF_NUMBER:
      snprintf(str, 255, "%.2f", prop->p_vu.p_number);
      break;
    case AMF_BOOLEAN:
      snprintf(str, 255, "%s", prop->p_vu.p_number != 0. ? "TRUE" : "FALSE");
      break;
    case AMF_STRING:
      len = snprintf(str, 255, "%.*s", prop->p_vu.p_aval.av_len, prop->p_vu.p_aval.av_val);
      if (len >= 1 && str[len - 1] == '\n') str[len - 1] = '\0';
      break;
    case AMF_DATE:
      snprintf(str, 255, "timestamp:%.2f", prop->p_vu.p_number);
      break;
    default:
      snprintf(str, 255, "INVALID TYPE 0x%02x", (unsigned char) prop->p_type);
    }
    if (str[0] && prop->p_name.av_len) {
      RTMP_Log(RTMP_LOGINFO, "  %-22.*s%s", prop->p_name.av_len, prop->p_name.av_val, str);
    }
  }
  return FALSE;
}

DumpMetaData对原生的AMFObject做了修饰,忽略了最外层的object,找到AMF_OBJECT或ARRAY对它做二次Dump,这样输出就干净多了。

INFO:   width                 200.00
INFO:   height                200.00
INFO:   displayWidth          200.00
INFO:   displayHeight         200.00
INFO:   duration              0.00
INFO:   framerate             10.00
INFO:   fps                   10.00
INFO:   videodatarate         2500.00
INFO:   videocodecid          7.00
INFO:   audiodatarate         160.00
INFO:   audiocodecid          10.00
INFO:   Server                Tengine
INFO:   videocodecreal        0.00
INFO:   fileSize              0.00
INFO:   audiosamplerate       48000.00
INFO:   audiosamplesize       16.00
INFO:   audiochannels         2.00
INFO:   stereo                TRUE
INFO:   2.1                   FALSE
INFO:   3.1                   FALSE
INFO:   4.0                   FALSE
INFO:   4.1                   FALSE
INFO:   5.1                   FALSE
INFO:   7.1                   FALSE
INFO:   encoder               obs-output module (libobs version 27.2.4)

encode

AMF_EncodeString

void encode() {
  char pbuf[256], *pend = pbuf + sizeof(pbuf);
  char *enc = pbuf;

  memset(pbuf, 0, sizeof(pbuf));

  AVal str = AVC("nonocast");
  enc = AMF_EncodeString(enc, pend, &str);

  RTMP_LogHexString(RTMP_LOGINFO, (unsigned char *) pbuf, sizeof(pbuf));
}

AVC是在amf.h中定义的marco: #define AVC(str) { str, sizeof(str) - 1 }

输出的内容如下:

INFO:   0000:  02 00 08 6e 6f 6e 6f 63  61 73 74 00 00 00 00 00   ...nonocast.....  
INFO:   0010:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................  

可以接着enc往下encode,

AVal str = AVC("nonocast");
AVal str_foo = AVC("foo");
AVal str_bar = AVC("bar");

enc = AMF_EncodeString(enc, pend, &str);
enc = AMF_EncodeString(enc, pend, &str_foo);
enc = AMF_EncodeString(enc, pend, &str_bar);

enc = AMF_EncodeBoolean(enc, pend, true);
enc = AMF_EncodeNumber(enc, pend, 200.0);

输出的内容如下:

INFO:   0000:  02 00 08 6e 6f 6e 6f 63  61 73 74 02 00 03 66 6f   ...nonocast...fo  
INFO:   0010:  6f 02 00 03 62 61 72 01  01 00 40 69 00 00 00 00   o...bar...@i.... 

AMF_EncodeNamedString

AVal str_foo = AVC("foo");
AVal str_bar = AVC("bar");

// way 1: shortcut for AMFObjectProperty
enc = AMF_EncodeNamedString(enc, pend, &str_foo, &str_bar);

// way 2
AMFObjectProperty prop;
prop.p_type = AMF_STRING;
prop.p_name = str_foo;
prop.p_vu.p_aval = str_bar;

enc = AMFProp_Encode(&prop, enc, pend);

可以通过两种方式实现prop的encode,输出的字节一致:

00 03 66 6f 6f 02 00 03 62 61 72

说明:

  • AMF的格式: type (1 byte) - len (2 byte) - payload
  • 属性因为是需要在object中,所以key默认是string,就省略了第一个type的字节,key - value type - value len - value, 上面的第六个字节0x02表示value的type

AMFObject

void encode() {
  char pbuf[256], *pend = pbuf + sizeof(pbuf);
  char *enc = pbuf;

  memset(pbuf, 0, sizeof(pbuf));

  AMFObject obj;

  AVal str_foo = AVC("foo");
  AVal str_bar = AVC("bar");
  AVal str_fullscreen = AVC("fullscreen");

  AMFObjectProperty prop1;
  prop1.p_type = AMF_STRING;
  prop1.p_name = str_foo;
  prop1.p_vu.p_aval = str_bar;

  AMFObjectProperty prop2;
  prop2.p_type = AMF_BOOLEAN;
  prop2.p_name = str_fullscreen;
  prop2.p_vu.p_number = true;

  AMFObjectProperty props[] = {prop1, prop2};
  obj.o_num = 2;
  obj.o_props = props;

  enc = AMF_Encode(&obj, enc, pend);

  RTMP_LogHexString(RTMP_LOGINFO, (unsigned char *) pbuf, enc - pbuf);
}

输出的内容如下:

INFO:   0000:  03 00 03 66 6f 6f 02 00  03 62 61 72 00 0a 66 75   ...foo...bar..fu  
INFO:   0010:  6c 6c 73 63 72 65 65 6e  01 01 00 00 09            llscreen.....   

另外一种手法会更简单一些,

char pbuf[256], *pend = pbuf + sizeof(pbuf);
char *enc = pbuf;

memset(pbuf, 0, sizeof(pbuf));

AVal str_foo = AVC("foo");
AVal str_bar = AVC("bar");
AVal str_fullscreen = AVC("fullscreen");

*enc++ = AMF_OBJECT;

enc = AMF_EncodeNamedString(enc, pend, &str_foo, &str_bar);
enc = AMF_EncodeNamedBoolean(enc, pend, &str_fullscreen, true);

*enc++ = 0;
*enc++ = 0;
*enc++ = AMF_OBJECT_END;

RTMP_LogHexString(RTMP_LOGINFO, (unsigned char *) pbuf, enc - pbuf);
  • 最后补充一点,生成的内容的size = enc - pbuf,这个很巧妙。

nonocast avatar May 19 '22 00:05 nonocast