FFmpeg rtp & rtp_mpegts的区别

rtp

RTP是一种用于实时传输音视频数据的协议,常用于视频会议、流媒体等场景。

在FFmpeg中,rtpenc是一个用于将音视频数据封装成RTP(Real-time Transport Protocol)数据包并发送到网络上的编码器。

rtpenc可以将音视频数据封装成RTP数据包,并通过UDP协议发送到指定的IP地址和端口号。它支持多种音视频编码格式,如H.264、AAC等,并可以设置RTP头部信息、负载类型、时间戳等参数。

ff_rtp_muxer

了解rtpenc,就来看rtp_muxer,rtp_muxer也是由AVOutputFormat定义,位于libavformat/rtpenc.c,在rtp muxer的定义中,默认的codec格式是AV_CODEC_ID_MPEG4AV_CODEC_ID_PCM_MULAWrtp_write_packet可以看到这个rtp packet的封包发送过程。

const AVOutputFormat ff_rtp_muxer = {
    .name              = "rtp",
    .long_name         = NULL_IF_CONFIG_SMALL("RTP output"),
    .priv_data_size    = sizeof(RTPMuxContext),
    .audio_codec       = AV_CODEC_ID_PCM_MULAW,
    .video_codec       = AV_CODEC_ID_MPEG4,
    .write_header      = rtp_write_header,
    .write_packet      = rtp_write_packet,
    .write_trailer     = rtp_write_trailer,
    .priv_class        = &rtp_muxer_class,
    .flags             = AVFMT_TS_NONSTRICT,
};

这里再说一下AVOutputFormat

虽然AVOutputFormat在libavdevice和libavformat中都存在,但是它们的作用不同。在libavdevice中,AVOutputFormat用于描述音视频设备的输出格式,而在libavformat中,AVOutputFormat用于描述音视频文件的输出格式。

因此,AVOutputFormat在libavdevice和libavformat中的区别在于它们所描述的对象不同。

rtp_write_packet

rtp_write_packet中根据codec_id,调用对应的ff_rtp_send_xxx函数,比如H264对应的是ff_rtp_send_h264_hevc:

static int rtp_write_packet(AVFormatContext *s1, AVPacket *pkt) {
    RTPMuxContext *s = s1->priv_data;
    AVStream *st = s1->streams[0];
    int rtcp_bytes;
    int size= pkt->size;

    
    switch(st->codecpar->codec_id) {
    case AV_CODEC_ID_AAC:
        if (s->flags & FF_RTP_FLAG_MP4A_LATM)
            ff_rtp_send_latm(s1, pkt->data, size);
        else
            ff_rtp_send_aac(s1, pkt->data, size);
        break;
     case AV_CODEC_ID_H264:
        ff_rtp_send_h264_hevc(s1, pkt->data, size);
        break;   
}

ff_rtp_send_h264_hevc

该函数的主要作用是将H.264/HEVC视频流分割成NAL单元,并将其打包成RTP数据包进行传输。

该函数首先将传入的视频流数据buf1拷贝到RTPMuxContext结构体中,并设置当前时间戳为s->cur_timestamp。然后,该函数通过调用ff_avc_find_startcodeff_avc_mp4_find_startcode函数找到NAL单元的起始位置,并将NAL单元的数据通过nal_send函数打包成RTP数据包进行传输。最后,该函数通过调用flush_buffered函数将缓存中的数据进行传输。

需要注意的是,该函数中的s->nal_length_size表示NAL单元长度的字节数,不为0,buffer就是avcc类型,如果为0,就是annex

格式,根据起始码0x0000010x00000001来找到buffer的结束位置。

void ff_rtp_send_h264_hevc(AVFormatContext *s1, const uint8_t *buf1, int size)
{
    const uint8_t *r, *end = buf1 + size;
    RTPMuxContext *s = s1->priv_data;

    s->timestamp = s->cur_timestamp;
    s->buf_ptr   = s->buf;
    if (s->nal_length_size)
        r = ff_avc_mp4_find_startcode(buf1, end, s->nal_length_size) ? buf1 : end;
    else
        r = ff_avc_find_startcode(buf1, end);
    while (r < end) {
        const uint8_t *r1;

        if (s->nal_length_size) {
            r1 = ff_avc_mp4_find_startcode(r, end, s->nal_length_size);
            if (!r1)
                r1 = end;
            r += s->nal_length_size;
        } else {
            while (!*(r++));
            r1 = ff_avc_find_startcode(r, end);
        }
        nal_send(s1, r, r1 - r, r1 == end);
        r = r1;
    }
    flush_buffered(s1, 1);
}

然后nal_send将NAL单元发送到RTP流中。NAL单元是视频编码中的基本单元,它包含了视频编码中的一个帧或片段。

nal_send首先检查NAL单元的大小是否小于等于最大负载大小,如果是,则将NAL单元添加到缓冲区中。如果缓冲区中已经有了一些NAL单元,则将它们作为STAP-A/AP包发送。如果NAL单元的大小大于最大负载大小,则将其分割成多个分片,并将它们作为FU-A包发送。

nal_send还根据编解码器类型设置了一些标志位,以便在发送NAL单元时进行适当的处理。例如,对于H.264编解码器,如果使用了FF_RTP_FLAG_H264_MODE0标志,则将NAL单元分割成多个分片。

ff_rtp_send_data

ff_rtp_send_data是rtp用于将数据通过RTP协议发送出去的函数。具体来说,它会构建RTP头部,将数据写入输出流,并更新一些统计信息。

void ff_rtp_send_data(AVFormatContext *s1, const uint8_t *buf1, int len, int m)
{
    RTPMuxContext *s = s1->priv_data;

    av_log(s1, AV_LOG_TRACE, "rtp_send_data size=%d\n", len);

    /* build the RTP header */
    avio_w8(s1->pb, RTP_VERSION << 6);
    avio_w8(s1->pb, (s->payload_type & 0x7f) | ((m & 0x01) << 7));
    avio_wb16(s1->pb, s->seq);
    avio_wb32(s1->pb, s->timestamp);
    avio_wb32(s1->pb, s->ssrc);

    avio_write(s1->pb, buf1, len);
    avio_flush(s1->pb);

    s->seq = (s->seq + 1) & 0xffff;
    s->octet_count += len;
    s->packet_count++;
}

函数的参数包括一个AVFormatContext结构体指针s1,一个指向数据缓冲区的指针buf1,数据长度len,以及一个标志位m。其中,s1包含了输出流的相关信息,buf1是要发送的数据,len是数据长度,m表示是否是最后一个数据包。

函数首先从s1中获取RTPMuxContext结构体指针s,该结构体包含了一些RTP相关的信息。然后,函数会使用avio_w8、avio_wb16、avio_wb32等函数将RTP头写入输出流中。其中,RTP头部的格式如下:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

其中,V表示协议版本号,P表示是否有填充,X表示是否有扩展头部,CC表示CSRC计数器,M表示是否是最后一个数据包,PT表示负载类型,sequence number表示序列号,timestamp表示时间戳,SSRC表示同步源标识符,CSRC表示贡献源标识符。

接下来,函数会使用avio_write函数将数据写入输出流中,并使用avio_flush函数将输出流中的数据刷新。最后,函数会更新s中的一些统计信息,包括序列号、字节计数和数据包计数。

rtp_mpegts

rtp_mpegts是ffmpeg中支持的唯一一个rtp muxer,通过rtp_mpegts发送音视频数据,可以解决rtp只支持一路流的问题,默认支持的audio codec是PCM_MULAW,video codec是MPEG4,通过acodec和vcodec参数可以指定codec。

比如可以这样发送:

ffmpeg -re -stream_loop -1 -i h264.mp4 -i aac.mp4 -f rtp_mpegts rtp://10.0.1.1:5008

指定codec:

ffmpeg -re -stream_loop -1 -i h264.mp4 -i aac.mp4 -acodec aac -vcodec h264 -f rtp_mpegts rtp://10.0.1.1:5008

h264.mp4 - 只包含h264编码的视频文件

aac.mp4 - 只包含aac编码的音频文件

如果使用rtp发送,则会输出ERROR:“Only one stream supported in the RTP muxer”。

ffmpeg -re -stream_loop -1 -i h264.mp4 -i aac.mp4 -f rtp rtp://10.0.1.1:5008

ff_rtp_mpegts_muxer

const AVOutputFormat ff_rtp_mpegts_muxer = {
    .name              = "rtp_mpegts",
    .long_name         = NULL_IF_CONFIG_SMALL("RTP/mpegts output format"),
    .priv_data_size    = sizeof(MuxChain),
    .audio_codec       = AV_CODEC_ID_AAC,
    .video_codec       = AV_CODEC_ID_MPEG4,
    .write_header      = rtp_mpegts_write_header,
    .write_packet      = rtp_mpegts_write_packet,
    .write_trailer     = rtp_mpegts_write_close,
    .priv_class        = &rtp_mpegts_class,
};

write_header函数中,指定codec id为AV_CODEC_ID_MPEG2TS,这个会在rtp enc中处理,调用rtp_send_mpegts_raw,发送mpegts数据。

    st->time_base.num   = 1;
    st->time_base.den   = 90000;
    st->codecpar->codec_id = AV_CODEC_ID_MPEG2TS;
    rtp_ctx->pb = s->pb;

所以,rtp muxer直接对音视频数据封包成rtp payload进行发送,而rtp_mpegts则对音视频数据先进行mpegts封装,然后再封装为rtp payload发送。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值