FFmpeg源码分析:写音视频帧av_write_frame()

FFmpeg在libavformat模块提供音视频的muxer封装与demuxer解封装。其中muxer封装文件包括avformat_write_header()、av_write_frame()和av_write_trailer()。本文主要探讨av_write_frame函数如何写入音视频帧数据,包括音视频交错与音视频非交错两种情况。

av_write_frame函数位于libavformat/mux.c,写音视频帧的流程如下:
在这里插入图片描述

1、av_write_frame

av_write_frame的声明位于libavformat/avformat.h,具体如下:

/**
 * Write a packet to an output media file.
 *
 * This function passes the packet directly to the muxer, without any buffering
 * or reordering. The caller is responsible for correctly interleaving the
 * packets if the format requires it. Callers that want libavformat to handle
 * the interleaving should call av_interleaved_write_frame() instead of this
 * function.
 *
 * @param s media file handle
 * @param pkt The packet containing the data to be written. Note that unlike
 *            av_interleaved_write_frame(), this function does not take
 *            ownership of the packet passed to it (though some muxers may make
 *            an internal reference to the input packet).
 * @return < 0 on error, = 0 if OK, 1 if flushed and there is no more data to flush
 *
 * @see av_interleaved_write_frame()
 */
int av_write_frame(AVFormatContext *s, AVPacket *pkt);

大致翻译为:写一个数据包到输出媒体文件。这个函数直接传递数据包到封装器,没有任何缓存或重排序。如果格式需要,调用者要负责把数据包正确排列。如果调用者期望处理交错的数据包,应该调用av_interleaved_write_frame()函数而不是当前这个函数。

下面来看看无交错写音视频av_write_frame()函数的实现,位于libavformat/mux.c:

int av_write_frame(AVFormatContext *s, AVPacket *in)
{
   
   
    AVPacket *pkt = s->internal->pkt;
    int ret;
 
    if (!in) {
   
   
        if (s->oformat->flags & AVFMT_ALLOW_FLUSH) {
   
   
            ret = s->oformat->write_packet(s, NULL);
            flush_if_needed(s);
            if (ret >= 0 && s->pb && s->pb->error < 0)
                ret = s->pb->error;
            return ret;
        }
        return 1;
    }
    if (in->flags & AV_PKT_FLAG_UNCODED_FRAME) {
   
   
        pkt = in;
    } else {
   
   
        /* We don't own in, so we have to make sure not to modify it.
         * The following avoids copying in's data unnecessarily. */
        av_packet_unref(pkt);
        pkt->buf  = NULL;
        pkt->data = in->data;
        pkt->size = in->size;
        ret = av_packet_copy_props(pkt, in);
        if (ret < 0)
            return ret;
        if (in->buf) {
   
   
            pkt->buf = av_buffer_ref(in->buf);
            if (!pkt->buf) {
   
   
                ret = AVERROR(ENOMEM);
                goto fail;
            }
        }
    }
    ret = write_packets_common(s, pkt, 0/*non-interleaved*/);
 
fail:
    av_packet_unref(pkt);
    return ret;
}

再看看有交错写音视频帧av_interleaved_write_frame()函数的实现:

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)
{
   
   
    int ret;
 
    if (pkt) {
   
   
        ret = write_packets_common(s, pkt, 1/*interleaved*/);
        if (ret < 0)
            av_packet_unref(pkt);
        return ret;
    } else {
   
   
        av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame FLUSH\n");
        return interleaved_write_packet(s, NULL, 1/*flush*/);
    }
}

经过对比,内部都是调用write_packets_common()函数,区别在于av_write_frame()传参是无交错,而av_interleaved_write_frame()传参是有交错。

2、write_packets_common

接下来看看write_packets_common()函数的实现:

static int write_packets_common(AVFormatContext *s, AVPacket *pkt, int interleaved)
{
   
   
    AVStream *st;
	// 检查pkt的stream_index和codec_type
    int ret = check_packet(s, pkt);
    if (ret < 0)
        return ret;
    st = s->streams[pkt->stream_index];
    // 检查pkt的pts和dts
    ret = prepare_input_packet(s, st, pkt);
    if (ret < 0)
        return ret;
    // 调用s->oformat->check_bitstream()检查码流
    ret = check_bitstream(s, st, pkt);
    if (ret < 0)
        return ret;
 
    if (st->internal->bsfc) {
   
   
		// 经过bitstream filter处理再写数据包
        return write_packets_from_bsfs(s, st, pkt, interleaved);
    } else {
   
   
		// 通用方式写数据包
        return write_packet_common(s, st, pkt, interleaved);
    }
}

由源码可知,主要是分4个步骤处理:

  1. 检查pkt的stream_index和codec_type;
  2. 检查pkt的pts和dts;
  3. 调用s->oformat->check_bitstream()检查码流;
  4. 调用write_packets_from_bsfs或write_packet_common写数据包;

3、write_packet_common

write_packets_from_bsfs()函数主要是经过bitstream filter处理,比如h264要处理startcode起始码。我们主要探讨write_packet_common()函数的实现:

static int write_packet_common(AVFormatContext *s, AVStream *st, AVPacket *pkt, int interleaved)
{
   
   
    int ret;
    // 猜测pkt数据包的时长
    guess_pkt_duration(s, st, pkt);
 
#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
    if ((ret = compute_muxer_pkt_fields(s, st, pkt)) < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
        return ret;
#endif
 
    if (interleaved) {
   
   
		// 有交错写数据包
        if (pkt->dts == AV_NOPTS_VALUE && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
            return AVERROR(EINVAL);
        return interleaved_write_packet(s, pkt, 0);
    } else {
   
   
		// 无交错写数据包
        return write_packet(s, pkt);
    }
}

由此可见,主要根据i

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值