加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

RTMP Handshake导致Flash不能播放H264流

发布时间:2020-12-15 18:40:21 所属栏目:百科 来源:网络整理
导读:详细分析和例子参考:http://www.voidcn.com/article/p-fylvtpuj-so.html 服务器端实现参考: 高性能流媒体服务器SRS: https://github.com/winlinvip/simple-rtmp-server Adobe在2009年公开了rtmp协议,wikipedia说是部分公开(an incomplete version)而且

详细分析和例子参考:http://www.voidcn.com/article/p-fylvtpuj-so.html

服务器端实现参考:高性能流媒体服务器SRS:https://github.com/winlinvip/simple-rtmp-server


Adobe在2009年公开了rtmp协议,wikipedia说是部分公开(an incomplete version)而且handshake也有变更。

simple handshake是rtmp spec 1.0定义的握手方式。而complex handshake是后来变更的方式,Adobe没有公开。若研发rtmp server,将h264数据给FP播放时,必须为complex handshake,否则数据能传输但无法播放。

读rtmpd(ccrtmpserver)代码发现,handshake确实变更了。BTW:rtmpd的代码可读性要强很多,很快就能知道它在做什么;不过,它是部分C++部分C的混合体;譬如,使用了BaseRtmpProtocol和InboundRtmpProtocol这种C++的解决方式;以及在解析complex handshake时对1536字节的包直接操作,offset=buf[772]+buf[773]+buf[774]+buf[775],这个就很难看明白在做什么了,其实1536是由4字节的time+4字节的version+764字节的key+764字节的digest,key的offset在后面,digest的offset在前面,若定义两个结构再让它们自己去解析,就很明白在做什么了。

sourcesthelibsrcprotocolsrtmpinboundrtmpprotocol.cpp: 51

InboundRTMPProtocol::PerformHandshake
    // 没有完成握手。
    case RTMP_STATE_NOT_INITIALIZED:
        // buffer[1537]
        // 第一个字节,即c0,表示握手类型(03是明文,06是加密,其他值非法)
        handshakeType = GETIBPOINTER(buffer)[0];
        // 删除第一个字节,buffer[1536] 即c1
        buffer.Ignore(1);
        // 第5-8共4个字节表示FPVersion,这个必须非0,0表示不支持complex handshake。
        _currentFPVersion = ENTOHLP(GETIBPOINTER(buffer) + 4);
        // 进行明文握手(false表示明文)
        PerformHandshake(buffer,false);
        
InboundRTMPProtocol::PerformHandshake
    // 先验证client,即验证c1
    // 先尝试scheme0
    valid = ValidClientScheme(0);
    // 若失败,再尝试scheme1
    valid = ValidClientScheme(1)
    // 若验证成功
    if(valid)
        // 复杂的handshake:PerformComplexHandshake,主要流程如下:
        S0 = 3或6 
        随机数初始化S1S2
        根据C1的public key生成S1的128byets public key
        生成S1的32bytes digest
        根据C1和S2生成S2的32bytes digest 
    else
        // rtmp spec 1.0定义的握手方式 
        PerformSimpleHandshake();


其实到后面看明白了,scheme1和scheme2这两种方式,是包结构的调换。

complex的包结构如下:C1/S1 1536bytes
    time: 4bytes 开头是4字节的当前时间。(u_int32_t)time(NULL)
    peer_version: 4bytes 为程序版本。C1一般是0x80000702。S1是0x04050001。
    764bytes: 可能是KeyBlock或者DigestBlock
    764bytes: 可能是KeyBlock或者DigestBlock
其中scheme1就是KeyBlock在前面DigestBlock在后面,而scheme0是DigestBlock在前面KeyBlock在后面。
子结构KeyBlock定义:
    760bytes: 包含128bytes的key的数据。
    key_offset: 4bytes 最后4字节定义了key的offset(相对于KeyBlock开头而言)
字结构DigestBlock定义:
    digest_offset: 4bytes 开头4字节定义了digest的offset(相对于第DigestBlock的第5字节而言,offset=3表示digestBlock[7~38]为digest
    760bytes: 包含32bytes的digest的数据。

其中,key和digest的主要算法是:C1的key为128bytes随机数。C1_32bytes_digest = HMACsha256(P1+P2,1504,FPKey,30) ,其中P1为digest之前的部分,P2为digest之后的部分,P1+P2是将这两部分拷贝到新的数组,共1536-32长度。S1的key根据C1的key算出来,算法如下:

DHWrapper dhWrapper(1024);
dhWrapper.Initialize()
dhWrapper.CreateSharedKey(c1_key,128)
dhWrapper.CopyPublicKey(s1_key,128)
S1的digest算法同C1。注意,必须先计算S1的key,因为key变化后digest也也重新计算。
S2/C2没有key,只有一个digest,是根据C1/S1算出来的:
先用随机数填充S2
s2data=S2[0-1504]; 前1502字节为随机的数据。
s2digest=S2[1505-1526] 后32bytes为digest。
// 计算s2digest方法如下:
ptemphash[512]: HMACsha256(c1digest,32,FMSKey,68,ptemhash)
ps2hash[512]: HMACsha256(s2data,ptemphash,ps2hash)
将ps2hash[0-31]拷贝到s2的后32bytes。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读