直播推流兼容性问题追踪
好久没有写了。说说最近遇到的一个坑吧。
很多直播产品都用的librtmp来做rtmp流的传输。
关于RTMP的协议,在官方文档rtmp_specification_1.0.pdf中有具体的说明。
首先建立一个RTMP链接,建立链接的步骤先要handshake,需要客户端向服务器发送C0,C1,C2三个RTMP Chunk。
rtmp_specification_1.0中规定C0是RTMP version,一个字节。目前一般使用的0x03,1和2废弃了,超过4预留。
C1是1536个字节,由4个字节的时间戳、4个字节的0和1528个字节的随机数组成。
一般定义#define RTMP_SIG_SIZE 1536
通常在客户端中C0和C1是同时发送,这里通常的代码是:
char clientbuf[RTMP_SIG_SIZE + 1], *clientsig = clientbuf + 1; //clientbuf中存有C0和C1数据,clientsig只含有C1数据 char serversig[RTMP_SIG_SIZE]; // RTMP协议版本号为0x03,即C0数据 clientbuf[0] = 0x03; /* not encrypted,没有加密 */ // 获取系统时间(毫秒为单位),将其写入到C1中,占4个字节 uptime = htonl(RTMP_GetTime()); memcpy(clientsig, &uptime, 4); // 系统时间之后是4个字节的0,固定的 memset(&clientsig[4], 0, 4); #ifdef _DEBUG // debug版,后面的1528个随机数简单的都设为0xff for (i = 8; i < RTMP_SIG_SIZE; i++) clientsig[i] = 0xff; #else // release版,使用rand()循环生成1528个伪随机数 for (i = 8; i < RTMP_SIG_SIZE; i++) clientsig[i] = (char)(rand() % 256); #endif
而百度出来结果还一种错误的写法类似,
conn->clientbuf[0] = 0x03; uptime = htonl( RTMP_GetTime() ); memcpy( conn->clientbuf + 1, &uptime, 4 ); memset( conn->clientbuf + 4, 0, 4 ); for( i = 8; i < RTMP_SIG_SIZE; i++ ) conn->clientbuf[i] = ( char )( rand() % 256 );
很不幸,我们最初版本的代码就是百度出来的这个版本。
早期的直播产品使用simple handshake,都没有做握手协议校验。甚至使用了几家CDN也是同样没做校验。
于是,从未怀疑过这段代码。至少从我接手后,从未改过这几句。
一次做迅雷星域CDN推流兼容的时候,发现我们的直播产品推流失败。
进一步查看,发现握手都未成功。抓包后发现C0C1发送后,并未收到服务器发来的S0S1chunk。
和星域CDN的技术沟通后发现才发现问题所在。
原来CDN做了simple handshake和complex handshake校验,验证handshake中的C0C1。而我的代码
C1错位了一个字节。正确的写法如下:
memcpy( conn->clientbuf + 1, &uptime, 4 ); memset( conn->clientbuf + 5, 0, 4 ); for( i = 9; i < RTMP_SIG_SIZE; i++ ) conn->clientbuf[i] = ( char )( rand() % 256 );
修改后,推流到星域CDN完全没问题了。
这个问题相信不少人都遇到过。轻易的发布博客或者转帖而没有验证具体代码的执行结果,导致看到博客的人信以为真。为了避免类似的问题,还需要每一个开发者谨慎对待自己发出去的技术博客。我的每篇博客都是自己验证了代码的。