RTMP协议分析之Chunk

Adobe官方文档描述不清,而且跟实际实现又有些不一样,实在很混乱,还是自己做做笔记靠谱。

这篇只讲Chunk,握手另外写篇文章。

Chunk Format


Chunk Format

RTMP收发的数据称为Message,所谓Message不外乎音视频数据和信令,而真正收发数据时把Message拆分为Chunk发送,每个Chunk的默认大小是128字节,当然这个大小可以通过控制信息来修改。
Message要分块很容易理解,因为音视频数据特别是视频数据太大了,不分块直接发的话会造成不同Message之间阻塞,自然出现音视频卡顿、信令不能及时得到反馈等问题,这跟以往我们用udp实现私有协议音视频实时传输一个道理,都需要分块。

Chunk如上图由Chunk Header和Chuk Data组成,Chunk Header又由3个部分组成,下面一个个来细说。

1. Chunk Basic Header


Chunk Basic Header包含了chunk stream ID(流通道Id)和chunk type(chunk的类型),chunk stream id在下图中简写为cs id,chunk type下图为fmt。
csid用来唯一标识一个特定的流通道,fmt决定了后面Message Header的格式。
Basic Header的长度可能是1,2,或3个字节,其中fmt的长度是固定的(占2位,注意单位是位,bit),Basic Header的长度取决于csid的大小,在足够存储这两个字段的前提下最好用尽量少的字节从而减少由于引入Header增加的数据量。

注意在这儿csid和fmt是没有关系的,只有csid的长度才决定整个Basic Header的长度,而fmt决定的是后面Message Header的格式。

RTMP协议支持用户自定义[3,65599]之间的CSID,0,1,2由协议保留表示特殊信息。0代表Basic Header总共要占用2个字节,CSID在[64,319]之间,1代表占用3个字节,CSID在[64,65599]之间,2代表该chunk是控制信息和一些命令信息。
chunk type的长度固定为2位,因此CSID的长度是(6=8-2)、(14=16-2)、(22=24-2)中的一个。

下图分别表示Basic Header分别为1、2、3个字节的情况:

  1. 当Basic Header为1个字节时,CSID占6位,6位最多可以表示64个数,因此这种情况下CSID在[0,63]之间,其中用户可自定义的范围为[3,63]。


    1个字节
  2. 当Basic Header为2个字节时,CSID占14位,此时协议将与chunk type所在字节的其他位都置为0,剩下的一个字节来表示CSID-64,这样共有8个二进制位来存储CSID,8位可以表示[0,255]共256个数,因此这种情况下CSID在[64,319],其中319=255+64。

    2个字节

  3. 当Basic Header为3个字节时,CSID占22位,此时协议将[2,8]字节置为1,余下的16个字节表示CSID-64,这样共有16个位来存储CSID,16位可以表示[0,65535]共65536个数,因此这种情况下CSID在[64,65599],其中65599=65535+64,需要注意的是,Basic Header是采用小端存储的方式,越往后的字节数量级越高,因此通过这3个字节每一位的值来计算CSID时,应该是:<第三个字节的值>x256+<第二个字节的值>+64。

    3个字节

可以看到2个字节和3个字节的Basic Header所能表示的CSID是有交集的[64,319],但实际实现时还是应该秉着最少字节的原则使用2个字节的表示方式来表示[64,319]的CSID。

代码实现:

来对比一下srs中的实现(srs_rtmp_stack.cpp):

int SrsProtocol::read_basic_header(char& fmt, int& cid)
{
    int ret = ERROR_SUCCESS;
    
    if ((ret = in_buffer->grow(skt, 1)) != ERROR_SUCCESS) {
        if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {
            srs_error("read 1bytes basic header failed. required_size=%d, ret=%d", 1, ret);
        }
        return ret;
    }
    
    fmt = in_buffer->read_1byte();
    cid = fmt & 0x3f;
    fmt = (fmt >> 6) & 0x03;
    
    // 2-63, 1B chunk header
    if (cid > 1) {//大于1肯定是1个字节,所以直接返回即可
        srs_verbose("basic header parsed. fmt=%d, cid=%d", fmt, cid);
        return ret;
    }

    // 64-319, 2B chunk header
    if (cid == 0) {//为0表示2个字节
        if ((ret = in_buffer->grow(skt, 1)) != ERROR_SUCCESS) {
            if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {
                srs_error("read 2bytes basic header failed. required_size=%d, ret=%d", 1, ret);
            }
            return ret;
        }
        
        cid = 64;
        cid += (u_int8_t)in_buffer->read_1byte();
        srs_verbose("2bytes basic header parsed. fmt=%d, cid=%d", fmt, cid);
    // 64-65599, 3B chunk header
    } else if (cid == 1) {//为1表示3个字节
        if ((ret = in_buffer->grow(skt, 2)) != ERROR_SUCCESS) {
            if (ret != ERROR_SOCKET_TIMEOUT && !srs_is_client_gracefully_close(ret)) {
                srs_error("read 3bytes basic header failed. required_size=%d, ret=%d", 2, ret);
            }
            return ret;
        }
        
        cid = 64;
        cid += (u_int8_t)in_buffer->read_1byte();
        cid += ((u_int8_t)in_buffer->read_1byte()) * 256;
        srs_verbose("3bytes basic header parsed. fmt=%d, cid=%d", fmt, cid);
    } else {
        srs_error("invalid path, impossible basic header.");
        srs_assert(false);
    }
    
    return ret;
}

而同为srs作者的ossrs/go-oryx-lib的rtmp.go的代码是有bug的,这个库的代码不能用(不仅仅是这个bug),不仅n久没更新也没正式Release:

func (v *Protocol) readBasicHeader() (format formatType, cid chunkID, err error) {
    // 2-63, 1B chunk header
    var t uint8
    if err = binary.Read(v.r, binary.BigEndian, &t); err != nil {
        return format, cid, oe.Wrap(err, "read basic header")
    }
    cid = chunkID(t & 0x3f)
    format = formatType((t >> 6) & 0x03)

    if cid > 1 {//大于1肯定是1个字节,所以直接返回即可
        return
    }

    // 64-319, 2B chunk header
    if err = binary.Read(v.r, binary.BigEndian, &t); err != nil {
        return format, cid, oe.Wrapf(err, "read basic header for cid=%v", cid)
    }
    cid = chunkID(64 + uint32(t))

    // 64-65599, 3B chunk header
    if cid == 1 {//这儿有bug!!!
        if err = binary.Read(v.r, binary.BigEndian, &t); err != nil {
            return format, cid, oe.Wrapf(err, "read basic header for cid=%v", cid)
        }
        cid += chunkID(uint32(t) * 256)
    }

    return
}

2. Message Header


未完待续......

参考文章

这篇比较好:带你吃透RTMP
Adobe的RTMP 协议官方1.0文档
Adobe 官方公布的 RTMP 规范+未公布的部分
RTMP协议分析及H.264打包原理

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

推荐阅读更多精彩内容

  • RTMP协议是Real Time Message Protocol(实时信息传输协议)的缩写,它是由Adobe公司...
    iOS小肖阅读 3,468评论 0 4
  • 版本记录 前言 大家都知道很多视频应用的app中都是使用RTMP格式的协议,这个是国际上共同使用的协议,我自己虽然...
    刀客传奇阅读 12,186评论 5 15
  • 2018年8月4日第三次更新,详细介绍了RTMP协议与遇到的坑 1. 简介 RTMP协议是Real Time Me...
    devzhaoyou阅读 78,346评论 13 105
  • 个人翻译,转载请注明出处,谢谢! Adobe's Real Time Messaging Protocol 摘要 ...
    SniperPan阅读 2,712评论 1 17
  • 说起冬天,总会想起小时候和兄长一起写作业的情景。那时的冬天格外的冷,我们都穿着母亲做的厚棉袄,厚棉裤,袄袖上还要套...
    雍敏阅读 426评论 7 9