FFMPEG 之 RTMP 二

一  简介

上接《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)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值