一 简介
上接《FFMPEG 之 RTMP 一》,讲解关于RTMP 协议相关的知识。 本文讲解在FFMPEG 中是如何对RTMP 协议进行封装的,相关代码位于rtmproto.c 中,在rtmproto.c 中,它封装了RTMP 协议,相比与librtmp.c 要复杂很多, 前者是对TCP 协议的封装, 后者乃是直接对librtmp.so 的封装。
如下函数乃是开给使用者的API, 我们将以此为切入点,对rtmproto.c 进行分析。
const URLProtocol ff_##flavor##_protocol = { \
.name = #flavor, \
.url_open2 = rtmp_open, \
.url_read = rtmp_read, \
.url_read_seek = rtmp_seek, \
.url_read_pause = rtmp_pause, \
.url_write = rtmp_write, \
.url_close = rtmp_close, \
.priv_data_size = sizeof(RTMPContext), \
.flags = URL_PROTOCOL_FLAG_NETWORK, \
.priv_data_class= &flavor##_class, \
};
二 rtmp_open()
此函数完成五项工作:1 解析输入的Url, 2 封装出送给TCP 的URL, 3 open tcp 流, 4 进行RTMP 握手, 5 发送connet packet,确定客户端信息。具体如下图所示:
1)rtmp_handshake()
当执行rtmp_open() 函数,对应如下图所示的四个网络包。前三个网络包是握手包,也就是rtmp_handshake() 执行完后发送的包。
根据如下函数, 我们可知,RTMP 握手流程如下:1 客户端给服务端 发 C0, C1 包, 2 服务端给客户端:回S0 , S1 包, 3 服务端给客户端回 S2 包,4 客户端给服务端发C2 包,握手结束。
static int rtmp_handshake(URLContext *s, RTMPContext *rt)
{
AVLFG rnd;
uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = {
3, // unencrypted data
0, 0, 0, 0, // client uptime
RTMP_CLIENT_VER1,
RTMP_CLIENT_VER2,
RTMP_CLIENT_VER3,
RTMP_CLIENT_VER4,
}; //#define RTMP_HANDSHAKE_PACKET_SIZE 1536
...
if ((ret = ffurl_write(rt->stream, tosend,
RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n");
return ret;
}//发送C0 + C1 包, 共1536 +1 个字节
if ((ret = ffurl_read_complete(rt->stream, serverdata,
RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) {
av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
return ret;
} // 接收S0 + S1 包,共1536 + 1 字节
if ((ret = ffurl_read_complete(rt->stream, clientdata,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0) {
av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n");
return ret;
} // 接收S2 包,共1536 字节
if (rt->is_input && serverdata[5] >= 3) {
...
for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++)
tosend[i] = av_lfg_get(&rnd) >> 24;
ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0,
rtmp_player_key, sizeof(rtmp_player_key),
digest);
if (ret < 0)
return ret;
ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0,
digest, 32,
tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32);
if (ret < 0)
return ret;
// write reply back to the server
if ((ret = ffurl_write(rt->stream, tosend,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0) // 发送C2 包
return ret;
}
....
return 0;
}
2 gen_connect()
摘自于源码中解释“Generate 'connect' call and send it to the server.”, 实际上这函数将客户端的一些info 告知服务端。
如下图是解析gen_connect() 函数网络包, 通过网络包可知,客户端传送给服务端的信息。
对应的具体代码为:
static int gen_connect(URLContext *s, RTMPContext *rt)
{
RTMPPacket pkt;
uint8_t *p;
int ret;
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 4096 + APP_MAX_LENGTH)) < 0) // 创建发送给服务端,type 为INVOKE 的rtmp packet
return ret;
p = pkt.data;
ff_amf_write_string(&p, "connect");//AMF 连接层命令Connect
ff_amf_write_number(&p, ++rt->nb_invokes);
ff_amf_write_object_start(&p);
ff_amf_write_field_name(&p, "app");
ff_amf_write_string2(&p, rt->app, rt->auth_params);
if (!rt->is_input) {
ff_amf_write_field_name(&p, "type");
ff_amf_write_string(&p, "nonprivate");
}
ff_amf_write_field_name(&p, "flashVer");
ff_amf_write_string(&p, rt->flashver);
if (rt->swfurl || rt->swfverify) {
ff_amf_write_field_name(&p, "swfUrl");
if (rt->swfurl)
ff_amf_write_string(&p, rt->swfurl);
else
ff_amf_write_string(&p, rt->swfverify);
}
ff_amf_write_field_name(&p, "tcUrl");
ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
if (rt->is_input) {
ff_amf_write_field_name(&p, "fpad");
ff_amf_write_bool(&p, 0);
ff_amf_write_field_name(&p, "capabilities");
ff_amf_write_number(&p, 15.0);
/* Tell the server we support all the audio codecs except
* SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010)
* which are unused in the RTMP protocol implementation. */
ff_amf_write_field_name(&p, "audioCodecs");
ff_amf_write_number(&p, 4071.0);
ff_amf_write_field_name(&p, "videoCodecs");
ff_amf_write_number(&p, 252.0);
ff_amf_write_field_name(&p, "videoFunction");
ff_amf_write_number(&p, 1.0);
if (rt->pageurl) {
ff_amf_write_field_name(&p, "pageUrl");
ff_amf_write_string(&p, rt->pageurl);
}
}
ff_amf_write_object_end(&p);
...
pkt.size = p - pkt.data;
return rtmp_send_packet(rt, &pkt, 1);
}
三 rtmp_read()
rtmp 是基于tcp 协议的, rtmp_read() 往下call 到ffurl_read_complete(),后面就是tcp 的read()。
四 rtmp_seek()
rtmp 协议seek 功能,构建一个seek 的packet 包,然后send 给服务端。其包类型为: RTMP_PT_INVOKE, ///< invoke some stream action
seek, pause 等等控制命令,其创建的packek 类型都是RTMP_PT_INVOKE, 然后在amf 中加上"seek"字符,以及timestamp ,然后发送给server 端。
五 rtmp_pause()
rtmp_pause(int pause), 这支函数向服务器发送pause/resume 命令。也是通过构建一个packet 包,然后传送给rtmp 服务器, 其流程与rtmp_seek() 相似。
六 rtmp_close()
rtmp 在退出播放时, 除了释放alloc 资源外,还需发送“FCUnpulish”,"deleteStream", 给rtmp 服务器。
七 总结
1 rtmp 是基于tcp 的 (其他变种可能不是),往下都是通过TCP 协议来传输数据,在TCP 协议上加了一些自己的规范,如rtmp 握手,rtmp 连接,数据传输,命令控制。
2 rtmp 握手,通过发送C0/C1/C2,接收S0/S1/S2 数据包。C0/S1确定rtmp版本号,C1/S1,C2/S2 确定时间信息,握手后即可传递rtmp packet。
3 rtmp 连接,通过如下代码创建connect packet:
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE,
0, 4096 + APP_MAX_LENGTH)) < 0)
然后以AMF 格式填充连接信息的,然后传递给rtmp 服务器, 连接信息如下:
ff_amf_write_string(&p, "connect");
ff_amf_write_field_name(&p, "app");
ff_amf_write_string2(&p, rt->app, rt->auth_params);
ff_amf_write_field_name(&p, "flashVer");
ff_amf_write_string(&p, rt->flashver);
ff_amf_write_field_name(&p, "tcUrl");
ff_amf_write_string2(&p, rt->tcurl, rt->auth_params);
ff_amf_write_field_name(&p, "audioCodecs");
ff_amf_write_field_name(&p, "videoCodecs");
4 数据传送, 通过rtmp_read()/rtmp_write()
5 命令控制,seek, pause/resume,播放,退出 ,也都是通过如下代码创建相应的包,然后将控制命令组装成AMF 格式,打包成packet, 传送给rtmp 服务器。
if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE,
0, 29 + strlen(rt->playpath))) < 0)