FFmpeg源代码简单分析:av_write_trailer()

本文深入分析了FFmpeg中的av_write_trailer()函数,详细介绍了其作用和内部实现流程,包括如何输出文件尾以及更新FLV文件的时长和大小信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

=====================================================

FFmpeg的库函数源代码分析文章列表:

【架构图】

FFmpeg源代码结构图 - 解码

FFmpeg源代码结构图 - 编码

【通用】

FFmpeg 源代码简单分析:av_register_all()

FFmpeg 源代码简单分析:avcodec_register_all()

FFmpeg 源代码简单分析:内存的分配和释放(av_malloc()av_free()等)

FFmpeg 源代码简单分析:常见结构体的初始化和销毁(AVFormatContextAVFrame等)

FFmpeg 源代码简单分析:avio_open2()

FFmpeg 源代码简单分析:av_find_decoder()av_find_encoder()

FFmpeg 源代码简单分析:avcodec_open2()

FFmpeg 源代码简单分析:avcodec_close()

【解码】

图解FFMPEG打开媒体的函数avformat_open_input

FFmpeg 源代码简单分析:avformat_open_input()

FFmpeg 源代码简单分析:avformat_find_stream_info()

FFmpeg 源代码简单分析:av_read_frame()

FFmpeg 源代码简单分析:avcodec_decode_video2()

FFmpeg 源代码简单分析:avformat_close_input()

【编码】

FFmpeg 源代码简单分析:avformat_alloc_output_context2()

FFmpeg 源代码简单分析:avformat_write_header()

FFmpeg 源代码简单分析:avcodec_encode_video()

FFmpeg 源代码简单分析:av_write_frame()

FFmpeg 源代码简单分析:av_write_trailer()

【其它】

FFmpeg源代码简单分析:日志输出系统(av_log()等)

FFmpeg源代码简单分析:结构体成员管理系统-AVClass

FFmpeg源代码简单分析:结构体成员管理系统-AVOption

FFmpeg源代码简单分析:libswscalesws_getContext()

FFmpeg源代码简单分析:libswscalesws_scale()

FFmpeg源代码简单分析:libavdeviceavdevice_register_all()

FFmpeg源代码简单分析:libavdevicegdigrab

【脚本】

FFmpeg源代码简单分析:makefile

FFmpeg源代码简单分析:configure

【H.264】

FFmpegH.264解码器源代码简单分析:概述

=====================================================


打算写两篇文章简单分析FFmpeg的写文件用到的3个函数avformat_write_header(),av_write_frame()以及av_write_trailer()。这篇文章继续分析av_write_trailer()。


av_write_trailer()用于输出文件尾,它的声明位于libavformat\avformat.h,如下所示。
/**
 * Write the stream trailer to an output media file and free the
 * file private data.
 *
 * May only be called after a successful call to avformat_write_header.
 *
 * @param s media file handle
 * @return 0 if OK, AVERROR_xxx on error
 */
int av_write_trailer(AVFormatContext *s);

它只需要指定一个参数,即用于输出的AVFormatContext。
函数正常执行后返回值等于0。

这2个函数最典型的例子可以参考:

最简单的基于FFMPEG的视频编码器(YUV编码为H.264)


函数调用关系图

av_write_trailer()的调用关系如下图所示。



av_write_trailer()

av_write_trailer()的定义位于libavformat\mux.c,如下所示。
int av_write_trailer(AVFormatContext *s)
{
    int ret, i;

    for (;; ) {
        AVPacket pkt;
        ret = interleave_packet(s, &pkt, NULL, 1);
        if (ret < 0)
            goto fail;
        if (!ret)
            break;
        //写入AVPacket
        ret = write_packet(s, &pkt);
        if (ret >= 0)
            s->streams[pkt.stream_index]->nb_frames++;

        av_free_packet(&pkt);

        if (ret < 0)
            goto fail;
        if(s->pb && s->pb->error)
            goto fail;
    }

fail:
	//写文件尾
    if (s->oformat->write_trailer)
        if (ret >= 0) {
        ret = s->oformat->write_trailer(s);
        } else {
            s->oformat->write_trailer(s);
        }

    if (s->pb)
       avio_flush(s->pb);
    if (ret == 0)
       ret = s->pb ? s->pb->error : 0;
    for (i = 0; i < s->nb_streams; i++) {
        av_freep(&s->streams[i]->priv_data);
        av_freep(&s->streams[i]->index_entries);
    }
    if (s->oformat->priv_class)
        av_opt_free(s->priv_data);
    av_freep(&s->priv_data);
    return ret;
}

从源代码可以看出av_write_trailer()主要完成了以下两步工作:

(1)循环调用interleave_packet()以及write_packet(),将还未输出的AVPacket输出出来。

(2)调用AVOutputFormat的write_trailer(),输出文件尾。

其中第一步和av_write_frame()中的步骤大致是一样的(interleave_packet()这一部分在并不包含在av_write_frame()中,而是包含在av_interleaved_write_frame()中,这一部分源代码还没有分析),可以参考文章《FFmpeg源代码简单分析:av_write_frame()》。下面分析一下第二步。


AVOutputFormat->write_trailer()

AVOutputFormat的write_trailer()是一个函数指针,指向特定的AVOutputFormat中的实现函数。我们以FLV对应的AVOutputFormat为例,看一下它的定义,如下所示。
AVOutputFormat ff_flv_muxer = {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .mime_type      = "video/x-flv",
    .extensions     = "flv",
    .priv_data_size = sizeof(FLVContext),
    .audio_codec    = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .video_codec    = AV_CODEC_ID_FLV1,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .codec_tag      = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
};

从FLV对应的AVOutputFormat结构体的定义我们可以看出,write_trailer()指向了flv_write_trailer()函数。


flv_write_trailer()

flv_write_trailer()函数的定义位于libavformat\flvenc.c,如下所示。

static int flv_write_trailer(AVFormatContext *s)
{
    int64_t file_size;

    AVIOContext *pb = s->pb;
    FLVContext *flv = s->priv_data;
    int i;

    /* Add EOS tag */
    for (i = 0; i < s->nb_streams; i++) {
        AVCodecContext *enc = s->streams[i]->codec;
        FLVStreamContext *sc = s->streams[i]->priv_data;
        if (enc->codec_type == AVMEDIA_TYPE_VIDEO &&
                (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4))
            put_avc_eos_tag(pb, sc->last_ts);
    }

    file_size = avio_tell(pb);

    /* update information */
    if (avio_seek(pb, flv->duration_offset, SEEK_SET) < 0)
        av_log(s, AV_LOG_WARNING, "Failed to update header with correct duration.\n");
    else
        put_amf_double(pb, flv->duration / (double)1000);
    if (avio_seek(pb, flv->filesize_offset, SEEK_SET) < 0)
        av_log(s, AV_LOG_WARNING, "Failed to update header with correct filesize.\n");
    else
        put_amf_double(pb, file_size);

    avio_seek(pb, file_size, SEEK_SET);
    return 0;
}

从flv_write_trailer()的源代码可以看出该函数做了以下两步工作:
(1)如果视频流是H.264,则添加包含EOS(End Of Stream) NALU的Tag。
(2)更新FLV的时长信息,以及文件大小信息。
其中,put_avc_eos_tag()函数用于添加包含EOS NALU的Tag(包含结尾的一个PreviousTagSize),如下所示。
static void put_avc_eos_tag(AVIOContext *pb, unsigned ts)
{
    avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    avio_wb24(pb, 5);               /* Tag Data Size */
    avio_wb24(pb, ts);              /* lower 24 bits of timestamp in ms */
    avio_w8(pb, (ts >> 24) & 0x7F); /* MSB of ts in ms */
    avio_wb24(pb, 0);               /* StreamId = 0 */
    avio_w8(pb, 23);                /* ub[4] FrameType = 1, ub[4] CodecId = 7 */
    avio_w8(pb, 2);                 /* AVC end of sequence */
    avio_wb24(pb, 0);               /* Always 0 for AVC EOS. */
    avio_wb32(pb, 16);              /* Size of FLV tag */
}

可以参考FLV封装格式理解上述函数。由于前面的文章中已经描述过FLV封装格式,在这里不再重复叙述,在这里仅在此记录一下AVCVIDEOPACKET的格式,如下所示。
 

可以看出包含EOS NALU的AVCVIDEOPACKET的AVCPacketType为2。在这种情况下,AVCVIDEOPACKET的CompositionTime字段取0,并且无需包含Data字段。


雷霄骅
leixiaohua1020@126.com
http://blog.youkuaiyun.com/leixiaohua1020



<think>好的,用户想了解如何使用FFmpeg对H264原始内存码流进行压缩并通过av_interleaved_write_frame推流。首先,我需要明确用户的需求。H264原始码流通常已经是压缩过的,所以可能用户指的是进一步调整压缩参数或重新编码?或者用户可能混淆了概念,需要确认是否要进行转码或封装。 接下来,用户提到的是内存中的码流处理,而不是从文件读取,所以需要用到FFmpeg的内存缓冲相关函数,比如avio_alloc_context来设置自定义的IO上下文。不过,在示例中,用户可能需要将H264数据包直接写入输出格式上下文。 然后,av_interleaved_write_frame函数用于写入交织的视频帧,确保正确的时序和封装。这通常用在输出流中,比如RTMP推流或写入文件。需要确保数据包的正确时间基转换,使用av_packet_rescale_ts函数调整时间戳。 接下来,我需要考虑代码的结构。首先初始化输入和输出的格式上下文,如果是内存中的H264流,可能需要解析出AVCodecParameters来获取编码参数。然后创建输出流,配置编码器(如果重新编码的话),或者直接复用原始数据。但用户提到的是压缩,可能需要配置编码参数,比如码率、GOP大小等。 但H264原始码流已经是压缩后的数据,所以如果不需要重新编码,直接封装的话,需要设置为流复制模式,使用avformat_write_header,然后读取AVPacket并调整时间戳后写入输出。这里可能需要使用av_read_frame从自定义的输入源读取数据,或者用户自行提供内存中的H264数据包。 可能的步骤包括:创建输出格式上下文,添加视频流,配置编码器参数(如果是流复制,则从输入流复制参数),打开输出IO,写入头部,循环处理每个数据包,调整时间戳,写入帧,最后写入尾部。 另外,需要注意引用中的av_interleaved_write_frame和av_packet_rescale_ts的使用,确保时间基正确。例如,输入数据包的时间基可能与输出流的不同,需要转换。 用户可能需要一个代码示例,展示如何将内存中的H264数据包通过av_interleaved_write_frame推流。这需要模拟从内存读取数据包的过程,或者使用自定义的AVIOContext读取数据。但如果是直接处理已有的AVPacket,可能更简单。 可能的问题点:如何将内存中的H264数据封装到AVPacket中,如何设置输出格式(如FLV或RTMP),如何处理时间戳,以及是否需要重新编码。如果用户需要压缩,可能需要配置编码器,但如果是直接推流,可能只需封装。 因此,示例代码的大致流程是:初始化输出上下文,添加流,复制编码参数,打开输出,写入头部,循环读取内存中的H264数据包(可能通过av_parser_parse2分割NAL单元),封装为AVPacket,调整时间戳,然后写入。 需要引用相关的FFmpeg函数,如avformat_alloc_output_context2,avio_open2,av_interleaved_write_frame等,如用户提供的引用[^1]和[^2]中的内容。 总结步骤: 1. 创建输出格式上下文(如RTMP推流使用flv格式)。 2. 添加视频流并配置编码参数(如果是流复制,则复制参数)。 3. 打开输出IO。 4. 写入头部。 5. 循环处理每个H264数据包: a. 从内存中读取数据,解析出AVPacket。 b. 调整时间戳。 c. 使用av_interleaved_write_frame写入。 6. 写入尾部。 需要注意错误处理和资源释放。此外,如果H264数据是原始NAL单元,可能需要解析成AVPacket,包括设置正确的PTS/DTS。 最后,生成相关问题,可能涉及推流优化、时间戳处理、封装格式选择等。</think>以下是使用FFmpeg处理H264原始内存码流并推流的实现步骤: ### 实现步骤 1. **初始化输出上下文** ```c AVFormatContext *ofmt_ctx = NULL; avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", "rtmp://example.com/live/stream"); ``` 2. **创建输出视频流** ```c AVStream *out_stream = avformat_new_stream(ofmt_ctx, NULL); AVCodecParameters *codecpar = avcodec_parameters_alloc(); // 假设已从原始码流中获取编码参数 avcodec_parameters_copy(out_stream->codecpar, codecpar); ``` 3. **设置自定义IO上下文**(若需要处理内存数据) ```c unsigned char *iobuffer = av_malloc(32768); AVIOContext *avio = avio_alloc_context(iobuffer, 32768, 1, NULL, NULL, NULL, NULL); ofmt_ctx->pb = avio; ``` 4. **写入封装头部** ```c avformat_write_header(ofmt_ctx, NULL); ``` 5. **处理H264数据包** ```c AVPacket pkt; av_init_packet(&pkt); while (/* 从内存获取H264数据 */) { // 解析原始H264数据到AVPacket pkt.data = h264_raw_data; pkt.size = data_size; // 设置时间戳(需根据实际帧率转换) pkt.pts = av_rescale_q(frame_count, (AVRational){1, 25}, out_stream->time_base); pkt.dts = pkt.pts; // 推流关键函数 av_interleaved_write_frame(ofmt_ctx, &pkt); av_packet_unref(&pkt); } ``` 6. **结束推流** ```c av_write_trailer(ofmt_ctx); ``` ### 关键点说明 1. **时间戳处理**:使用$av\_rescale\_q()$函数转换时间基,确保同步[^1] 2. **封装格式**:FLV格式适用于RTMP推流,MPEGTS适用于UDP传输 3. **内存管理**:推荐使用$av\_packet\_alloc()$代替已废弃的$av\_init\_packet()$[^2]
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值