RTMP理解

简介

RTMP(Real Time Messaging Protocol)是Adobe公司开发的实时消息协议,下面是Adobe的RTMP协议文档中的简介:

an application-level protocol designed for multiplexing and packetizing multimedia transport streams (such as audio, video, and interactive content) over a suitable transport protocol (such as TCP).

大致意思是:是一种应用层协议,使用合适的传输层协议对多媒体流进行多路复用(一个连接内传输多种媒体流)。

RTMP的通用实现都是使用TCP作为传输层协议,但是这也带来了一些问题,比如由于TCP的特性,传输的实时性相比UDP要差,再加上中间服务器转发使得从用户推流到服务器到最终拉流播放这一整条路径不确定中间要经过多少网络节点,所以延时普遍在秒级。但是它最大的优点是可以在一路连接上复用多种消息,极大地节省了网络资源。

协议组成

协议主要有几个部分:握手(也就是建立RTMP连接),表示消息的Chunk(分块)。

握手

握手部分分为三个阶段,分别为:C0 S0, C1 S1, C2 S2

在Adobe公布的RTMP文档里描述的握手,在FMS和Flash中并不可用,在传输h264+aac的时候会出现有数据,但是无法播放音视频,原因是Adobe私自变更了握手,但是没有公布出现。所以RTMP的握手被称为两种握手,按照Adobe的RTMP文档的握手成为简单握手,Adobe私自变更的握手成为复杂握手。参考srs大神的文章:rtmp复杂握手

复杂握手实现C1实现如下:代码摘抄自ZLMediaKit

class RtmpHandshake {
public:
    RtmpHandshake(uint32_t _time, uint8_t *_random = nullptr) {
        _time = htonl(_time);
        memcpy(time_stamp, &_time, 4);
        if (!_random) {
            random_generate((char *) random, sizeof(random));
        } else {
            memcpy(random, _random, sizeof(random));
        }
    }

    uint8_t time_stamp[4];
    uint8_t zero[4] = {0};
    uint8_t random[RANDOM_LEN];

    void random_generate(char *bytes, int size) {
        static char cdata[] = {0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2d, 0x72,
                               0x74, 0x6d, 0x70, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
                               0x2d, 0x77, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x2d, 0x77, 0x69,
                               0x6e, 0x74, 0x65, 0x72, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
                               0x40, 0x31, 0x32, 0x36, 0x2e, 0x63, 0x6f, 0x6d};
        for (int i = 0; i < size; i++) {
            bytes[i] = cdata[rand() % (sizeof(cdata) - 1)];
        }
    }

    void create_complex_c0c1();

}PACKED;

//发送复杂握手c0c1
void RtmpHandshake::create_complex_c0c1() {
#ifdef ENABLE_OPENSSL
    //复杂握手时,zero字段不再是全0,而是指定的版本0x80,0x00,0x07,0x02
    memcpy(zero, "\x80\x00\x07\x02", 4);
    //复杂握手时,C1S1结构的random[1528]数据变成 key[764]+digest[764],也可能是digest[764]+key[764]
    //key[764]的结构:random[offset]+key[128]+random[764-offset-128-4]+offset[4]
    //digest[764]的结构:offset[4]+random[offset]+digest[32]+random[764-4-offset-32]
    //Adobe的FMS只认识digest-key结构,digest结构可以认为是key结构的循环右移

    //C2S2结构:random[1504]+digest[32],主要是提供对C1S1的验证

    //digest随机偏移长度
    auto offset_value = rand() % (C1_SCHEMA_SIZE - C1_OFFSET_SIZE - C1_DIGEST_SIZE);
    //设置digest偏移长度
    auto offset_ptr = random + C1_SCHEMA_SIZE;
    offset_ptr[0] = offset_ptr[1] = offset_ptr[2] = offset_value / 4;
    offset_ptr[3] = offset_value - 3 * (offset_value / 4);
    //去除digest后的剩余随机数据
    string str((char *) this, sizeof(*this));
    //这里生成的C1是key-digest结构
    //加上8是因为RtmpHandshake前面存储的是ts[4]+zero[4]这两个字段
    //C1_SCHEMA_SIZE是key[764]的长度,C1_OFFSET_SIZE是digest结构中offset[4]的长度
    //offset_value是digest结构中random[offset]的长度,接下来就是digest[32]
    //需要擦除digest[32],拿剩下的数据生成32字节的摘要
    str.erase(8 + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, C1_DIGEST_SIZE);
    //获取摘要
    auto digest_value = openssl_HMACsha256(FPKey, C1_FPKEY_SIZE, str.data(), str.size());
    //插入摘要
    memcpy(random + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, digest_value.data(), digest_value.size());
#endif
}

void RtmpProtocol::send_complex_S0S1S2(int schemeType,const string &digest){
    //S1S2计算参考自:https://github.com/hitYangfei/golang/blob/master/rtmpserver.go
    //发送S0
    char handshake_head = HANDSHAKE_PLAINTEXT;
    onSendRawData(obtainBuffer(&handshake_head, 1));
    //S1
    RtmpHandshake s1(0);
    //复杂握手模式S1的zero字段是指定的版本号:0x04,0x05,0x00,0x01
    memcpy(s1.zero, "\x04\x05\x00\x01", 4);
    char *digestPos;
    //解出C1的digest的位置
    if (scheme
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值