【FFmpeg】av_write_trailer函数

目录

  • 1.av_writer_trailer
    • [1.1 将未输出的pkt写入输出流(interleaved_write_packet)](#1.1 将未输出的pkt写入输出流(interleaved_write_packet))
    • [1.2 写入流的尾部信息(write_trailer)](#1.2 写入流的尾部信息(write_trailer))

FFmpeg相关记录:

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染

流程分析:
【FFmpeg】编码链路上主要函数的简单分析
【FFmpeg】解码链路上主要函数的简单分析

结构体分析:
【FFmpeg】AVCodec结构体
【FFmpeg】AVCodecContext结构体
【FFmpeg】AVStream结构体
【FFmpeg】AVFormatContext结构体
【FFmpeg】AVIOContext结构体
【FFmpeg】AVPacket结构体

函数分析:

【通用】
【FFmpeg】avcodec_find_encoder和avcodec_find_decoder
【FFmpeg】关键结构体的初始化和释放(AVFormatContext、AVIOContext等)
【FFmpeg】avcodec_open2函数
【FFmpeg】内存分配和释放(av_malloc、av_realloc等)

【推流】
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
【FFmpeg】avio_open2函数
【FFmpeg】avformat_write_header函数
【FFmpeg】av_write_frame函数

【编码】
【FFmpeg】avcodec_send_frame函数

【解码】
【FFmpeg】avcodec_send_packet函数

1.av_writer_trailer

函数的主要功能是向输出的媒体文件中写入流的尾部信息,同时释放文件的私有数据。从注释上看,这个函数只能在成功调用了avformat_write_header之后进行调用

c 复制代码
/**
 * 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);

av_write_trailer的定义如下

c 复制代码
int av_write_trailer(AVFormatContext *s)
{
    FFFormatContext *const si = ffformatcontext(s);
    AVPacket *const pkt = si->parse_pkt;
    int ret1, ret = 0;

    for (unsigned i = 0; i < s->nb_streams; i++) {
        AVStream *const st  = s->streams[i];
        FFStream *const sti = ffstream(st);
        // 如果使用了bitstream filter context,则从bitstream中拿到packet
        if (sti->bsfc) {
            ret1 = write_packets_from_bsfs(s, st, pkt, 1/*interleaved*/);
            if (ret1 < 0)
                av_packet_unref(pkt);
            if (ret >= 0)
                ret = ret1;
        }
    }
    // 对packet进行interleaved之后,write packet
    // 这是将前面可能还没有输出的pkt输出
    ret1 = interleaved_write_packet(s, pkt, 1, 0);
    if (ret >= 0)
        ret = ret1;

    if (ffofmt(s->oformat)->write_trailer) {
        if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
            avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
        // 写入流的尾部信息
        ret1 = ffofmt(s->oformat)->write_trailer(s);
        if (ret >= 0)
            ret = ret1;
    }
	// 释放muxer
    deinit_muxer(s);

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

1.1 将未输出的pkt写入输出流(interleaved_write_packet)

函数首先调用interleave_packet执行interleave过程(暂时不是很理解),随后使用write_packet将可能剩余的packet写入到输出流中

c 复制代码
static int interleaved_write_packet(AVFormatContext *s, AVPacket *pkt,
                                    int flush, int has_packet)
{
    FFFormatContext *const si = ffformatcontext(s);
    for (;; ) {
        int ret = si->interleave_packet(s, pkt, flush, has_packet);
        if (ret <= 0)
            return ret;

        has_packet = 0;

        ret = write_packet(s, pkt);
        av_packet_unref(pkt);
        if (ret < 0)
            return ret;
    }
}

1.2 写入流的尾部信息(write_trailer)

假设现在使用的格式是FLV格式,使用的是flv_write_trailer进行trailer的写入

c 复制代码
const FFOutputFormat ff_flv_muxer = {
    .p.name         = "flv",
    .p.long_name    = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .p.mime_type    = "video/x-flv",
    .p.extensions   = "flv",
    .priv_data_size = sizeof(FLVContext),
    .p.audio_codec  = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .p.video_codec  = AV_CODEC_ID_FLV1,
    .init           = flv_init,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .deinit         = flv_deinit,
    .check_bitstream= flv_check_bitstream,
    .p.codec_tag    = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .p.flags        = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .p.priv_class   = &flv_muxer_class,
};

flv_write_trailer定义在libavformat\flvenc.c中,主要作用是:

(1)更新信息,包括videosize,lasttimestamp和lastkeyframetimestamp等

(2)H264格式或MPEG4格式,put_eos_tag用于添加包含EOS NALU的Tag

c 复制代码
static int flv_write_trailer(AVFormatContext *s)
{
    int64_t file_size;
    AVIOContext *pb = s->pb;
    FLVContext *flv = s->priv_data;
	// FLV_ADD_KEYFRAME_INDEX主要用途是在FLV文件的头信息中添加关键帧索引
	// 以加快视频的seek速度
    int build_keyframes_idx = flv->flags & FLV_ADD_KEYFRAME_INDEX;
    int i, res;
    int64_t cur_pos = avio_tell(s->pb);
	// 1.更新信息,例如videosize,lasttimestamp和lastkeyframetimestamp等
    if (build_keyframes_idx) {
        const FLVFileposition *newflv_posinfo;
		// offset是偏移量
		// SEEK_SET表示从文件起始处开始计算偏移量
        avio_seek(pb, flv->videosize_offset, SEEK_SET);
		// put_amf_double用于在AMF数据类型中放置一个双精度浮点数值
		// AMF: Action message format(动作消息格式)
        put_amf_double(pb, flv->videosize);

        avio_seek(pb, flv->audiosize_offset, SEEK_SET);
        put_amf_double(pb, flv->audiosize);

        avio_seek(pb, flv->lasttimestamp_offset, SEEK_SET);
        put_amf_double(pb, flv->lasttimestamp);

        avio_seek(pb, flv->lastkeyframetimestamp_offset, SEEK_SET);
        put_amf_double(pb, flv->lastkeyframetimestamp);

        avio_seek(pb, flv->lastkeyframelocation_offset, SEEK_SET);
        put_amf_double(pb, flv->lastkeyframelocation + flv->keyframe_index_size);
        avio_seek(pb, cur_pos, SEEK_SET);
		// 处理数据移位,可能的作用是
		// 1.数据对齐
		//	  在写入文件尾的过程中,可能需要确保某些数据结构对齐到特定的边界
		// 2.空间回收
		//	  在动态内存管理中,可能需要滑动数据以回收未使用的内存空间。在文件尾写入过程中,可能不再需要某些之前写入
		//	  的数据,此时可以利用shift_data来移动数据,从而释放一些不必要的内存
		// 3.数据整合
		//    在一些情况下,为了提高效率或者减少存储需求,可能需要将多个小的数据块合并成一个完整的大块
		//	  shift_data可以用来重新排列数据,以便整合成一个连续的块
        res = shift_data(s);
        if (res < 0) {
             goto end;
        }
        avio_seek(pb, flv->keyframes_info_offset, SEEK_SET);
        put_amf_string(pb, "filepositions");
        put_amf_dword_array(pb, flv->filepositions_count);
        for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
            put_amf_double(pb, newflv_posinfo->keyframe_position + flv->keyframe_index_size);
        }

        put_amf_string(pb, "times");
        put_amf_dword_array(pb, flv->filepositions_count);
        for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
            put_amf_double(pb, newflv_posinfo->keyframe_timestamp);
        }

        put_amf_string(pb, "");
        avio_w8(pb, AMF_END_OF_OBJECT);

        avio_seek(pb, cur_pos + flv->keyframe_index_size, SEEK_SET);
    }

end:
    if (flv->flags & FLV_NO_SEQUENCE_END) {
        av_log(s, AV_LOG_DEBUG, "FLV no sequence end mode open\n");
    } else {
        /* Add EOS tag */
		// 添加EOS(end of sequence)的tag
        for (i = 0; i < s->nb_streams; i++) {
            AVCodecParameters *par = s->streams[i]->codecpar;
            if (par->codec_type == AVMEDIA_TYPE_VIDEO &&
                    (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4))
				// 2.H264格式或MPEG4格式,put_eos_tag用于添加包含EOS NALU的Tag
                put_eos_tag(pb, flv->last_ts[i], par->codec_id);
        }
    }

    file_size = avio_tell(pb);

    if (build_keyframes_idx) {
        flv->datasize = file_size - flv->datastart_offset;
        avio_seek(pb, flv->datasize_offset, SEEK_SET);
        put_amf_double(pb, flv->datasize);
    }
    if (!(flv->flags & FLV_NO_METADATA)) {
        if (!(flv->flags & FLV_NO_DURATION_FILESIZE)) {
            /* 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);
            }
        }
    }

    return 0;
}

put_eos_tag函数的定义如下

c 复制代码
static void put_eos_tag(AVIOContext *pb, unsigned ts, enum AVCodecID codec_id)
{
    uint32_t tag = ff_codec_get_tag(flv_video_codec_ids, codec_id);
    /* ub[4] FrameType = 1, ub[4] CodecId */
    tag |= 1 << 4;
    avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    avio_wb24(pb, 5);               /* Tag Data Size */
    put_timestamp(pb, ts);
    avio_wb24(pb, 0);               /* StreamId = 0 */
    avio_w8(pb, tag);
    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 */
}

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen

相关推荐
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn5 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
青花瓷5 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
观音山保我别报错5 小时前
C语言扫雷小游戏
c语言·开发语言·算法
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉6 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
岁月小龙6 小时前
如何让ffmpeg运行时从当前目录加载库,而不是从/lib64
ffmpeg·origin·ffprobe·rpath