RTMP Handshake导致Flash不能播放H264流
详细分析和例子参考: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。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |