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

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

【架构图】

FFmpeg源代码结构图 - 解码

FFmpeg源代码结构图 - 编码

【通用】

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

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

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

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

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源代码简单分析:libswscale的sws_getContext()

FFmpeg源代码简单分析:libswscale的sws_scale()

FFmpeg源代码简单分析:libavdevice的avdevice_register_all()

FFmpeg源代码简单分析:libavdevice的gdigrab

【脚本】

FFmpeg源代码简单分析:makefile

FFmpeg源代码简单分析:configure

【H.264】

FFmpeg的H.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.csdn.net/leixiaohua1020

FFmpeg源代码简单分析:av_write_trailer()的更多相关文章

  1. FFmpeg源代码简单分析:libavdevice的gdigrab

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  2. FFmpeg源代码简单分析:libavdevice的avdevice_register_all()

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  3. FFmpeg源代码简单分析:configure

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  4. FFmpeg源代码简单分析:makefile

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  5. FFmpeg源代码简单分析:libswscale的sws_scale()

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  6. FFmpeg源代码简单分析:libswscale的sws_getContext()

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

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

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

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

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

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

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

随机推荐

  1. 计蒜客NOIP模拟赛4 D1T1 小X的质数

    小 X 是一位热爱数学的男孩子,在茫茫的数字中,他对质数更有一种独特的情感.小 X 认为,质数是一切自然数起源的地方. 在小 X 的认知里,质数是除了本身和 1以外,没有其他因数的数字. 但由于小 X ...

  2. ●UOJ58 [WC2013]糖果公园

    题链: http://uoj.ac/problem/58题解: 树上带修莫队. 每个块的大小为$n^{\frac{2}{3}}$,在dfs时,把点集分为若干块. 然后类似序列带修莫队,三个关键字:be ...

  3. 2015 多校联赛 ——HDU5303(贪心)

    Delicious Apples Time Limit: 5000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Other ...

  4. bzoj 2212: [Poi2011]Tree Rotations

    Description Byteasar the gardener is growing a rare tree called Rotatus Informatikus. It has some in ...

  5. 【Miller-Rabin随机判素数算法】

    实用性介绍: #include<bits/stdc++.h> #define go(i,a,b) for(int i=a;i<=b;i++) #define T 5 #define ...

  6. [Codeforces]860E Arkady and a Nobody-men

    屯一个虚树的板子,顺便总结一下这样的题型. Description 给定一棵n个节点的有根树,在输入数据通过给出每个节点的父亲来表示这棵树.若某个节点的父亲为0,那么该节点即为根.现在对于每个点,询问 ...

  7. getopt_long函数使用【转】

    转自:https://blog.csdn.net/cashey1991/article/details/7942809 平时在写程序时常常需要对命令行参数进行处理,当命令行参数个数较多时,如果按照顺序 ...

  8. C语言程序设计第二次作业——

    1,编译过程过程中的错误缺引号和分号并且拼写错误. 正确结果: 2,编译过程 改正错误: 正确结果: 3,利用SIZEOF运算符求出的数据类型所占字节大小: 4,在头文件LIMITS.H中相关的编译 ...

  9. C语言第三次程序设计作业

    (一)改错题 计算f(x)的值:输入实数x,计算并输出下列分段函数f(x)的值,输出时保留1位小数. 1)源程序(有错误的程序) #include <stdio.h> int main(v ...

  10. java 第三次作业

    (一)学习总结 1.阅读下面程序,分析是否能编译通过?如果不能,说明原因.应该如何修改?程序的运行结果是什么?为什么子类的构造方法在运行之前,必须调用父 类的构造方法?能不能反过来? class Gr ...