me
me copied to clipboard
学习 rtmp 开发 (Part 6: AMF0)
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
,这个很巧妙。