【FFmpeg】avformat_write_header函数

news2024/11/25 2:50:59

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】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数

avformat_write_header内函数调用关系为
在这里插入图片描述

1.avformat_write_header

/**
 * Allocate the stream private data and write the stream header to
 * an output media file.
 *
 * @param s        Media file handle, must be allocated with
 *                 avformat_alloc_context().
 *                 Its \ref AVFormatContext.oformat "oformat" field must be set
 *                 to the desired output format;
 *                 Its \ref AVFormatContext.pb "pb" field must be set to an
 *                 already opened ::AVIOContext.
 * @param options  An ::AVDictionary filled with AVFormatContext and
 *                 muxer-private options.
 *                 On return this parameter will be destroyed and replaced with
 *                 a dict containing options that were not found. May be NULL.
 *
 * @retval AVSTREAM_INIT_IN_WRITE_HEADER On success, if the codec had not already been
 *                                       fully initialized in avformat_init_output().
 * @retval AVSTREAM_INIT_IN_INIT_OUTPUT  On success, if the codec had already been fully
 *                                       initialized in avformat_init_output().
 * @retval AVERROR                       A negative AVERROR on failure.
 *
 * @see av_opt_find, av_dict_set, avio_open, av_oformat_next, avformat_init_output.
 */
// 函数的功能:分配流私有数据并将流标头写入输出媒体文件
// c语言中,如果一个函数的返回值没有被使用,编译器会发出警告信息
// 而av_warn_unused_result宏的作用就是告诉编译器,这个函数的返回值虽然没有被使用
// 但是它是有意义的,不应该发出警告信息
av_warn_unused_result
int avformat_write_header(AVFormatContext *s, AVDictionary **options);

函数的定义位于libavformat\mux.c中,主要的工作流程为:
(1)如果没有进行初始化,则进行初始化输出(avformat_init_output)
(2)进行头信息的写入(write_header)
(3)如果流还没有被初始化,则写入pts(init_pts)

int avformat_write_header(AVFormatContext *s, AVDictionary **options)
{
    FFFormatContext *const si = ffformatcontext(s);
    int already_initialized = si->initialized;
    int streams_already_initialized = si->streams_initialized;
    int ret = 0;
	// ----- 1.如果没有初始化,则进行output的初始化 ----- //
    if (!already_initialized)
        if ((ret = avformat_init_output(s, options)) < 0)
            return ret;
	// ----- 2.进行头信息的写入 ----- //
    if (ffofmt(s->oformat)->write_header) {
        if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
			// 将写入的字节流标记为特定类型
			// 输出中省略长度为零的范围
            avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_HEADER);
        ret = ffofmt(s->oformat)->write_header(s);
        if (ret >= 0 && s->pb && s->pb->error < 0)
            ret = s->pb->error;
        if (ret < 0)
            goto fail;
        flush_if_needed(s);
    }
    if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
        avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_UNKNOWN);
	// ----- 3.如果流还没有被初始化,则初始化pts ----- //
    if (!si->streams_initialized) {
        if ((ret = init_pts(s)) < 0)
            goto fail;
    }

    return streams_already_initialized;

fail:
    deinit_muxer(s);
    return ret;
}

1.1 如果没有初始化,则进行output的初始化(avformat_init_output)

函数进行输出的初始化,主要调用函数init_muxer和init_pts。其中,init_muxer的初始化,准备将音频、视频和其他数据流混合成一个单一的文件格式;init_pts用于初始化解码后视频帧的时间戳(PTS),确保视频能够按照正确的时间顺序显示,当处理包含B帧的复杂编码时,init_pts的作用非常重要,因为B帧依赖多个参考帧,其解码和显示顺序不同。

int avformat_init_output(AVFormatContext *s, AVDictionary **options)
{
    FFFormatContext *const si = ffformatcontext(s);
    int ret = 0;
	// 初始化复用器
    if ((ret = init_muxer(s, options)) < 0)
        return ret;

    si->initialized = 1;
    si->streams_initialized = ret;
	// 初始化pts
    if (ffofmt(s->oformat)->init && ret) {
        if ((ret = init_pts(s)) < 0)
            return ret;

        return AVSTREAM_INIT_IN_INIT_OUTPUT;
    }

    return AVSTREAM_INIT_IN_WRITE_HEADER;
}

1.1.1 初始化复用器(init_muxer)

该函数用于初始化复用器,函数的定义位于libavformat\mux.c中,函数主要的工作流程为:
(1)将传入的options设置到AVFormatContext之中
(2)遍历每个AVStream,设置和检查相关信息
 (a)设置pts
 (b)检查音频的sample_rate和block_align
 (c)检查视频的长宽比
 (d)其他一些检查
(3)初始化格式(.init)
(4)如果初始化失败,会释放格式(.deinit)

static int init_muxer(AVFormatContext *s, AVDictionary **options)
{
    FFFormatContext *const si = ffformatcontext(s);
    AVDictionary *tmp = NULL;
    const FFOutputFormat *of = ffofmt(s->oformat);
    AVDictionaryEntry *e;
    static const unsigned default_codec_offsets[] = {
        [AVMEDIA_TYPE_VIDEO]    = offsetof(AVOutputFormat, video_codec),
        [AVMEDIA_TYPE_AUDIO]    = offsetof(AVOutputFormat, audio_codec),
        [AVMEDIA_TYPE_SUBTITLE] = offsetof(AVOutputFormat, subtitle_codec),
    };
    unsigned nb_type[FF_ARRAY_ELEMS(default_codec_offsets)] = { 0 };
    int ret = 0;

    if (options)
        av_dict_copy(&tmp, *options, 0);
	// 1.将传入的options设置到AVFormatContext之中
    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;
    if (s->priv_data && s->oformat->priv_class && *(const AVClass**)s->priv_data==s->oformat->priv_class &&
        (ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0)
        goto fail;

    if (!s->url && !(s->url = av_strdup(""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    // some sanity checks
    // 完整性检查
    if (s->nb_streams == 0 && !(of->p.flags & AVFMT_NOSTREAMS)) {
        av_log(s, AV_LOG_ERROR, "No streams to mux were specified\n");
        ret = AVERROR(EINVAL);
        goto fail;
    }
	// 2.遍历每个AVStream,设置信息
    for (unsigned i = 0; i < s->nb_streams; i++) {
        AVStream          *const  st = s->streams[i];
        FFStream          *const sti = ffstream(st);
        AVCodecParameters *const par = st->codecpar;
        const AVCodecDescriptor *desc;
		// 设置pts信息
        if (!st->time_base.num) {
            /* fall back on the default timebase values */
            if (par->codec_type == AVMEDIA_TYPE_AUDIO && par->sample_rate)
                avpriv_set_pts_info(st, 64, 1, par->sample_rate);
            else
                avpriv_set_pts_info(st, 33, 1, 90000);
        }
		
        switch (par->codec_type) {
        // 检查音频的sample_rate和block_align
        case AVMEDIA_TYPE_AUDIO:
            if (par->sample_rate <= 0) {
                av_log(s, AV_LOG_ERROR, "sample rate not set\n");
                ret = AVERROR(EINVAL);
                goto fail;
            }

            if (!par->block_align)
                par->block_align = par->ch_layout.nb_channels *
                                   av_get_bits_per_sample(par->codec_id) >> 3;
            break;
        // 检查视频的长宽比
        case AVMEDIA_TYPE_VIDEO:
            if ((par->width <= 0 || par->height <= 0) &&
                !(of->p.flags & AVFMT_NODIMENSIONS)) {
                av_log(s, AV_LOG_ERROR, "dimensions not set\n");
                ret = AVERROR(EINVAL);
                goto fail;
            }
            if (av_cmp_q(st->sample_aspect_ratio, par->sample_aspect_ratio)
                && fabs(av_q2d(st->sample_aspect_ratio) - av_q2d(par->sample_aspect_ratio)) > 0.004*av_q2d(st->sample_aspect_ratio)
            ) {
                if (st->sample_aspect_ratio.num != 0 &&
                    st->sample_aspect_ratio.den != 0 &&
                    par->sample_aspect_ratio.num != 0 &&
                    par->sample_aspect_ratio.den != 0) {
                    av_log(s, AV_LOG_ERROR, "Aspect ratio mismatch between muxer "
                           "(%d/%d) and encoder layer (%d/%d)\n",
                           st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
                           par->sample_aspect_ratio.num,
                           par->sample_aspect_ratio.den);
                    ret = AVERROR(EINVAL);
                    goto fail;
                }
            }
            break;
        }
        // (1) FF_OFMT_FLAG_MAX_ONE_OF_EACH
        //	按照注释翻译过来,意思是:"如果设置此标志,则表示对于每种编解码器类型,其对应的默认编解码器都设置为只能混合一个该类型的流
        //	它进一步表明,编解码器类型没有默认编解码器或其默认编解码器为AV_CODEC_ID_NONE的流不能被混合"
        //	换句话说,在使用FFmpeg进行多媒体数据处理时,每种输出格式(OutputFormat)最多只能有一个实例被激活和使用
        // (2) FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
        //	按照注释翻译,意思是:"如果设置了这个标志,那么唯一允许的音频/视频/字幕编解码器编号是
        //	AVOutputFormat.audio/video/subtitle_codec;如果后者中的任何一个未设置(即等于AV_CODEC_ID_NONE),
        //	则不支持相应类型的流。此外,没有默认编解码器字段的编解码器类型是不允许的"
        // 换句话说,如果定义了这个flag,那么只能使用默认的编解码器
        if (of->flags_internal & (FF_OFMT_FLAG_MAX_ONE_OF_EACH | FF_OFMT_FLAG_ONLY_DEFAULT_CODECS)) {
            enum AVCodecID default_codec_id = AV_CODEC_ID_NONE;
            unsigned nb;
            if ((unsigned)par->codec_type < FF_ARRAY_ELEMS(default_codec_offsets)) {
                nb = ++nb_type[par->codec_type];
                if (default_codec_offsets[par->codec_type])
                    default_codec_id = *(const enum AVCodecID*)((const char*)of + default_codec_offsets[par->codec_type]);
            }
            // 检查codec_id是否匹配
            if (of->flags_internal & FF_OFMT_FLAG_ONLY_DEFAULT_CODECS &&
                default_codec_id != AV_CODEC_ID_NONE && par->codec_id != default_codec_id) {
                av_log(s, AV_LOG_ERROR, "%s muxer supports only codec %s for type %s\n",
                       of->p.name, avcodec_get_name(default_codec_id), av_get_media_type_string(par->codec_type));
                ret = AVERROR(EINVAL);
                goto fail;
            } else if (default_codec_id == AV_CODEC_ID_NONE ||
                       (of->flags_internal & FF_OFMT_FLAG_MAX_ONE_OF_EACH && nb > 1)) {
                const char *type = av_get_media_type_string(par->codec_type);
                av_log(s, AV_LOG_ERROR, "%s muxer does not support %s stream of type %s\n",
                       of->p.name, default_codec_id == AV_CODEC_ID_NONE ? "any" : "more than one",
                       type ? type : "unknown");
                ret = AVERROR(EINVAL);
                goto fail;
            }
        }

#if FF_API_AVSTREAM_SIDE_DATA
FF_DISABLE_DEPRECATION_WARNINGS
        /* if the caller is using the deprecated AVStream side_data API,
         * copy its contents to AVStream.codecpar, giving it priority
           over existing side data in the latter */
        for (int i = 0; i < st->nb_side_data; i++) {
            const AVPacketSideData *sd_src = &st->side_data[i];
            AVPacketSideData *sd_dst;

            sd_dst = av_packet_side_data_new(&st->codecpar->coded_side_data,
                                             &st->codecpar->nb_coded_side_data,
                                             sd_src->type, sd_src->size, 0);
            if (!sd_dst) {
                ret = AVERROR(ENOMEM);
                goto fail;
            }
            memcpy(sd_dst->data, sd_src->data, sd_src->size);
        }
FF_ENABLE_DEPRECATION_WARNINGS
#endif

        desc = avcodec_descriptor_get(par->codec_id);
        if (desc && desc->props & AV_CODEC_PROP_REORDER)
            sti->reorder = 1;

        sti->is_intra_only = ff_is_intra_only(par->codec_id);
		// 设置codec_tag
        if (of->p.codec_tag) {
            if (   par->codec_tag
                && par->codec_id == AV_CODEC_ID_RAWVIDEO
                && (   av_codec_get_tag(of->p.codec_tag, par->codec_id) == 0
                    || av_codec_get_tag(of->p.codec_tag, par->codec_id) == MKTAG('r', 'a', 'w', ' '))
                && !validate_codec_tag(s, st)) {
                // the current rawvideo encoding system ends up setting
                // the wrong codec_tag for avi/mov, we override it here
                par->codec_tag = 0;
            }
            if (par->codec_tag) {
                if (!validate_codec_tag(s, st)) {
                    const uint32_t otag = av_codec_get_tag(s->oformat->codec_tag, par->codec_id);
                    av_log(s, AV_LOG_ERROR,
                           "Tag %s incompatible with output codec id '%d' (%s)\n",
                           av_fourcc2str(par->codec_tag), par->codec_id, av_fourcc2str(otag));
                    ret = AVERROR_INVALIDDATA;
                    goto fail;
                }
            } else
                par->codec_tag = av_codec_get_tag(of->p.codec_tag, par->codec_id);
        }

        if (par->codec_type != AVMEDIA_TYPE_ATTACHMENT &&
            par->codec_id != AV_CODEC_ID_SMPTE_2038)
            si->nb_interleaved_streams++;
    }
    si->interleave_packet = of->interleave_packet;
    if (!si->interleave_packet)
        si->interleave_packet = si->nb_interleaved_streams > 1 ?
                                    ff_interleave_packet_per_dts :
                                    ff_interleave_packet_passthrough;

    if (!s->priv_data && of->priv_data_size > 0) {
        s->priv_data = av_mallocz(of->priv_data_size);
        if (!s->priv_data) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (of->p.priv_class) {
            *(const AVClass **)s->priv_data = of->p.priv_class;
            av_opt_set_defaults(s->priv_data);
            if ((ret = av_opt_set_dict2(s->priv_data, &tmp, AV_OPT_SEARCH_CHILDREN)) < 0)
                goto fail;
        }
    }

    /* set muxer identification string */
    if (!(s->flags & AVFMT_FLAG_BITEXACT)) {
        av_dict_set(&s->metadata, "encoder", LIBAVFORMAT_IDENT, 0);
    } else {
        av_dict_set(&s->metadata, "encoder", NULL, 0);
    }

    for (e = NULL; e = av_dict_get(s->metadata, "encoder-", e, AV_DICT_IGNORE_SUFFIX); ) {
        av_dict_set(&s->metadata, e->key, NULL, 0);
    }

    if (options) {
         av_dict_free(options);
         *options = tmp;
    }
	// 3.初始化格式
	// 可以在这里分配数据,并在发送数据包之前设置任何需要设置的AVFormatContext或AVStream参数
	// 对于flv格式,这里会调用flv_init()
    if (of->init) {
        if ((ret = of->init(s)) < 0) {
            if (of->deinit)
                of->deinit(s); // 4.对于flv格式,这里会调用flv_deinit()
            return ret;
        }
        return ret == 0;
    }

    return 0;

fail:
    av_dict_free(&tmp);
    return ret;
}

下面以FLV格式为例,记录如何初始化format

1.1.1.1 初始化format(flv_init)

函数的定义位于libavformat\flvenc.c中,主要用于FLV格式的初始化。具体来说,会对数据源当中的每一条流进行解析,将stream中的信息设置到AVFormatContext中的priv_data之中。原先一直对priv_data不是很理解,从这里看,似乎这样的一个void类型数据能够用于不同类型的上下文的转换,例如下面代码中的FLContext

static int flv_init(struct AVFormatContext *s)
{
    int i;
    FLVContext *flv = s->priv_data;
	// 如果nb_stream超过了FLV_STREAM_TYPE_NB(4),说明当前的流存在错误
	// 正常FLV最多包含4条流,分别是视频、音频、字幕和DATA
    if (s->nb_streams > FLV_STREAM_TYPE_NB) {
        av_log(s, AV_LOG_ERROR, "invalid number of streams %d\n",
                s->nb_streams);
        return AVERROR(EINVAL);
    }
	// 遍历每一条流,将流当中的信息设置到flv当中
    for (i = 0; i < s->nb_streams; i++) {
        AVCodecParameters *par = s->streams[i]->codecpar;

        switch (par->codec_type) {
        // 视频
        case AVMEDIA_TYPE_VIDEO:
            if (s->streams[i]->avg_frame_rate.den &&
                s->streams[i]->avg_frame_rate.num) {
                flv->framerate = av_q2d(s->streams[i]->avg_frame_rate);
            }
            if (flv->video_par) {
                av_log(s, AV_LOG_ERROR,
                       "at most one video stream is supported in flv\n");
                return AVERROR(EINVAL);
            }
            flv->video_par = par;
            if (!ff_codec_get_tag(flv_video_codec_ids, par->codec_id))
                return unsupported_codec(s, "Video", par->codec_id);

            if (par->codec_id == AV_CODEC_ID_MPEG4 ||
                par->codec_id == AV_CODEC_ID_H263) {
                int error = s->strict_std_compliance > FF_COMPLIANCE_UNOFFICIAL;
                av_log(s, error ? AV_LOG_ERROR : AV_LOG_WARNING,
                       "Codec %s is not supported in the official FLV specification,\n", avcodec_get_name(par->codec_id));

                if (error) {
                    av_log(s, AV_LOG_ERROR,
                           "use vstrict=-1 / -strict -1 to use it anyway.\n");
                    return AVERROR(EINVAL);
                }
            } else if (par->codec_id == AV_CODEC_ID_VP6) {
                av_log(s, AV_LOG_WARNING,
                       "Muxing VP6 in flv will produce flipped video on playback.\n");
            }
            break;
        // 音频
        case AVMEDIA_TYPE_AUDIO:
            if (flv->audio_par) {
                av_log(s, AV_LOG_ERROR,
                       "at most one audio stream is supported in flv\n");
                return AVERROR(EINVAL);
            }
            flv->audio_par = par;
            if (get_audio_flags(s, par) < 0)
                return unsupported_codec(s, "Audio", par->codec_id);
            if (par->codec_id == AV_CODEC_ID_PCM_S16BE)
                av_log(s, AV_LOG_WARNING,
                       "16-bit big-endian audio in flv is valid but most likely unplayable (hardware dependent); use s16le\n");
            break;
        // 数据
        case AVMEDIA_TYPE_DATA:
            if (par->codec_id != AV_CODEC_ID_TEXT && par->codec_id != AV_CODEC_ID_NONE)
                return unsupported_codec(s, "Data", par->codec_id);
            flv->data_par = par;
            break;
        // 字幕
        case AVMEDIA_TYPE_SUBTITLE:
            if (par->codec_id != AV_CODEC_ID_TEXT) {
                av_log(s, AV_LOG_ERROR, "Subtitle codec '%s' for stream %d is not compatible with FLV\n",
                       avcodec_get_name(par->codec_id), i);
                return AVERROR_INVALIDDATA;
            }
            flv->data_par = par;
            break;
        default:
            av_log(s, AV_LOG_ERROR, "Codec type '%s' for stream %d is not compatible with FLV\n",
                   av_get_media_type_string(par->codec_type), i);
            return AVERROR(EINVAL);
        }
        avpriv_set_pts_info(s->streams[i], 32, 1, 1000); /* 32 bit pts in ms */
        flv->last_ts[i] = -1;
    }

    flv->delay = AV_NOPTS_VALUE;

    return 0;
}
1.1.1.2 释放format(flv_deinit)
static void flv_deinit(AVFormatContext *s)
{
    FLVContext *flv = s->priv_data;
    FLVFileposition *filepos = flv->head_filepositions;

    while (filepos) {
        FLVFileposition *next = filepos->next;
        av_free(filepos);
        filepos = next;
    }
    flv->filepositions = flv->head_filepositions = NULL;
    flv->filepositions_count = 0;
}

1.1.2 初始化pts(init_pts)

init_pts用于初始化解码或编码过程中的时间戳(Presentation Time Stamp, PTS)

static int init_pts(AVFormatContext *s)
{
    FFFormatContext *const si = ffformatcontext(s);

    /* init PTS generation */
    // 初始化pts生成
    for (unsigned i = 0; i < s->nb_streams; i++) {
        AVStream *const st = s->streams[i];
        FFStream *const sti = ffstream(st);
        int64_t den = AV_NOPTS_VALUE;

        switch (st->codecpar->codec_type) {
        case AVMEDIA_TYPE_AUDIO:
            den = (int64_t)st->time_base.num * st->codecpar->sample_rate;
            break;
        case AVMEDIA_TYPE_VIDEO:
            den = (int64_t)st->time_base.num * st->time_base.den;
            break;
        default:
            break;
        }

        if (den != AV_NOPTS_VALUE) {
            if (den <= 0)
                return AVERROR_INVALIDDATA;

            frac_init(&sti->priv_pts, 0, 0, den);
        }
    }

    si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_UNKNOWN;
    if (s->avoid_negative_ts < 0) {
        av_assert2(s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_AUTO);
        if (s->oformat->flags & (AVFMT_TS_NEGATIVE | AVFMT_NOTIMESTAMPS)) {
            s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_DISABLED;
            si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_DISABLED;
        } else
            s->avoid_negative_ts = AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE;
    } else if (s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_DISABLED)
        si->avoid_negative_ts_status = AVOID_NEGATIVE_TS_DISABLED;

    return 0;
}

1.2 头信息的写入(write_header)

不同的FFOutputFormat会对应不同的写入方法,以FLV格式为例,会调用flv_write_header进行格式的写入

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,	// 调用flv_write_header进行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_header的定义如下

static int flv_write_header(AVFormatContext *s)
{
    int i;
    AVIOContext *pb = s->pb;
    FLVContext *flv = s->priv_data;
	// 开始写入
	// signature,文件标识
    avio_write(pb, "FLV", 3);
	// version,版本号
    avio_w8(pb, 1);
	// !! 的目的是将非0转为1
	// flags,前5位保留,第6位表示是否存在音频tag,第7位保留且必须为0
	// 第8位表示是否存在视频tag
    avio_w8(pb, FLV_HEADER_FLAG_HASAUDIO * !!flv->audio_par +
                FLV_HEADER_FLAG_HASVIDEO * !!flv->video_par);
	// header size,表示从file header开始到file body之间的字节数,
    avio_wb32(pb, 9);
	// header结束
	// previous tag size,表示前一个tag的长度
    avio_wb32(pb, 0);

    for (i = 0; i < s->nb_streams; i++)
		// codecpar->codec_tag为5,表示视频流采用了MPEG Video编码格式
        if (s->streams[i]->codecpar->codec_tag == 5) {
            avio_w8(pb, 8);     // message type
            avio_wb24(pb, 0);   // include flags
            avio_wb24(pb, 0);   // time stamp
            avio_wb32(pb, 0);   // reserved
            avio_wb32(pb, 11);  // size
            flv->reserved = 5;
        }

    if (flv->flags & FLV_NO_METADATA) {
        pb->seekable = 0;
    } else {
        write_metadata(s, 0);
    }
	// 写入codec的头
    for (i = 0; i < s->nb_streams; i++) {
        flv_write_codec_header(s, s->streams[i]->codecpar, 0);
    }

    flv->datastart_offset = avio_tell(pb);
    return 0;
}

flv_write_codec_header的定义如下

static void flv_write_codec_header(AVFormatContext* s, AVCodecParameters* par, int64_t ts) {
    int64_t data_size;
    AVIOContext *pb = s->pb;
    FLVContext *flv = s->priv_data;

    if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
            || par->codec_id == AV_CODEC_ID_MPEG4 || par->codec_id == AV_CODEC_ID_HEVC
            || par->codec_id == AV_CODEC_ID_AV1 || par->codec_id == AV_CODEC_ID_VP9) {
        int64_t pos;
        avio_w8(pb,
                par->codec_type == AVMEDIA_TYPE_VIDEO ?
                        FLV_TAG_TYPE_VIDEO : FLV_TAG_TYPE_AUDIO); // 写入codec_type
        avio_wb24(pb, 0); // size patched later
        put_timestamp(pb, ts);
        avio_wb24(pb, 0); // streamid
        pos = avio_tell(pb);
		// 写入AAC信息
        if (par->codec_id == AV_CODEC_ID_AAC) {
            avio_w8(pb, get_audio_flags(s, par));
            avio_w8(pb, 0); // AAC sequence header

            if (!par->extradata_size && (flv->flags & FLV_AAC_SEQ_HEADER_DETECT)) {
                PutBitContext pbc;
                int samplerate_index;
                int channels = par->ch_layout.nb_channels
                        - (par->ch_layout.nb_channels == 8 ? 1 : 0);
                uint8_t data[2];

                for (samplerate_index = 0; samplerate_index < 16;
                        samplerate_index++)
                    if (par->sample_rate
                            == ff_mpeg4audio_sample_rates[samplerate_index])
                        break;

                init_put_bits(&pbc, data, sizeof(data));
                put_bits(&pbc, 5, par->profile + 1); //profile
                put_bits(&pbc, 4, samplerate_index); //sample rate index
                put_bits(&pbc, 4, channels);
                put_bits(&pbc, 1, 0); //frame length - 1024 samples
                put_bits(&pbc, 1, 0); //does not depend on core coder
                put_bits(&pbc, 1, 0); //is not extension
                flush_put_bits(&pbc);

                avio_w8(pb, data[0]);
                avio_w8(pb, data[1]);

                av_log(s, AV_LOG_WARNING, "AAC sequence header: %02x %02x.\n",
                        data[0], data[1]);
            }
            avio_write(pb, par->extradata, par->extradata_size);
        } else { // 如果是视频编码格式
            if (par->codec_id == AV_CODEC_ID_HEVC) { // hevc
                avio_w8(pb, FLV_IS_EX_HEADER | PacketTypeSequenceStart | FLV_FRAME_KEY); // ExVideoTagHeader mode with PacketTypeSequenceStart
                avio_write(pb, "hvc1", 4);
            } else if (par->codec_id == AV_CODEC_ID_AV1 || par->codec_id == AV_CODEC_ID_VP9) { // av1和vp9
                avio_w8(pb, FLV_IS_EX_HEADER | PacketTypeSequenceStart | FLV_FRAME_KEY);
                avio_write(pb, par->codec_id == AV_CODEC_ID_AV1 ? "av01" : "vp09", 4);
            } else {
                avio_w8(pb, par->codec_tag | FLV_FRAME_KEY); // flags
                avio_w8(pb, 0); // AVC sequence header
                avio_wb24(pb, 0); // composition time
            }

            if (par->codec_id == AV_CODEC_ID_HEVC)
                ff_isom_write_hvcc(pb, par->extradata, par->extradata_size, 0);
            else if (par->codec_id == AV_CODEC_ID_AV1)
                ff_isom_write_av1c(pb, par->extradata, par->extradata_size, 1);
            else if (par->codec_id == AV_CODEC_ID_VP9)
                ff_isom_write_vpcc(s, pb, par->extradata, par->extradata_size, par);
            else
                ff_isom_write_avcc(pb, par->extradata, par->extradata_size);
        }
        data_size = avio_tell(pb) - pos;
        avio_seek(pb, -data_size - 10, SEEK_CUR);
        avio_wb24(pb, data_size);
        avio_skip(pb, data_size + 10 - 3);
        avio_wb32(pb, data_size + 11); // previous tag size
    }
}

2.小结

avformat_write_header用于写入头信息,大体来说,可以分为两个部分:(1)为写入信息做初始化,调用init_muxer、of->init实现;(2)初始化之后,使用write_header实现头信息的写入。初始化的信息比如时间戳pts,codec_tag等,write_header会实现写入头信息。这里的信息比较琐碎,暂时还不完全理解,后续在实际工程之中再做理解

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1876336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware中安装CentOS系统

VMware中安装CentOS系统 CentOS 镜像的准备创建虚拟机Cent OS系统的安装 CentOS 镜像的准备 下载链接&#xff1a;清华园CenOS 7镜像下载 VMware的安装参考&#xff1a;VMware workstation pro 16 虚拟机的安装 创建虚拟机 1.打开VMware workstation pro 16->创建新的虚拟…

[leetcode]insert-into-a-binary-search-tree

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:TreeNode* insertIntoBST(TreeNode* root, int val) {if (root nullptr) {return new TreeNode(val);}TreeNode* pos root;while (pos ! nullptr) {if (val < pos->val) {if (pos->left nullptr…

掌握Python编程的深层技能

一、Python基础语法、变量、列表、字典等运用 1.运行python程序的两种方式 1.交互式即时得到程序的运行结果 2.脚本方式把程序写到文件里(约定俗称文件名后缀为.py),然后用python解释器解释执行其中的内容2.python程序运行的三个步骤 python3.8 C:\a\b\c.py 1.先启动python3…

揭秘循环购模式:消费即赚钱,私域电商新纪元

消费1000送2000、每天领钱、钱还可以提现&#xff0c;这样的商业模式——循环购模式&#xff0c;确实在私域电商领域引起了广泛的关注。这种模式的成功并非偶然&#xff0c;而是基于合理的返利规则和商业模式创新。下面我将为您详细解析循环购模式为何能够吸引消费者&#xff0…

记录一次——RK100键盘按键失效修复

一、背景说明: 背景&#xff1a;购买的键盘是RK的型号RK100&#xff0c;具有紧凑的外观&#xff0c;并搭配了TTC金粉轴&#xff0c;使用起来还不错&#xff0c;目前已经是第3年了。问题&#xff1a;前几个月会出现H按键失效的问题&#xff0c;但是过一段时间又会修复。最近是Q…

深入解析链表:解锁数据结构核心奥秘

一. 链表的定义 链表是一种线性数据结构&#xff0c;由一系列节点组成。每个节点包含两个部分&#xff1a; 数据域&#xff08;Data&#xff09;&#xff1a;存储节点的数据。指针域&#xff08;Pointer&#xff09;&#xff1a;存储指向下一个节点的地址。 链表的第一个节点…

一招教你用python代码给朋友写一个爱心代码

有人问我马上要跟女朋友一周年了&#xff0c;能不能用代码给他写一个爱心代码呢&#xff1f;那算你问对人了&#xff0c;来上才艺 可以使用Python的turtle模块来绘制一个爱心形状。下面是一个简单的示例代码&#xff0c;我将详细解释每一步&#xff1a; import turtle # 创建一…

制定班规要注意哪些事项

对于如何管理班级&#xff0c;制定班规是一项至关重要的任务。关系到班级的日常秩序&#xff0c;影响着学生的集体荣誉感。制定班规并非易事&#xff0c;需要深思熟虑和周全考虑。 班规的制定应以学生为中心。深入了解学生的需求和期望&#xff0c;以及他们在学习和生活中可能遇…

统计信号处理基础 习题解答11-6

题目 考虑例11.1对WGN中单个正弦信号的数据模型&#xff0c;将模型重写为 其中&#xff1a; &#xff0c;证明A的PDF是瑞丽的&#xff0c;的PDF是&#xff0c;且和是相互独立的。 解答 根据例11.1&#xff1a; 由于N维联合高斯分布为&#xff1a; 由&#xff1a; 因此&#…

Java程序员学习Go开发Higress的WASM插件

Java程序员学习Go开发Higress的WASM插件 契机 ⚙ 今年天池大赛有higress相关挑战&#xff0c;研究一下。之前没搞过go&#xff0c;踩了很多坑&#xff0c;最主要的就是tinygo打包&#xff0c;多方寻求解决无果&#xff0c;结论是tinygo0.32go1.19无法在macos arm架构下打包。…

一键掌控,文件格式转换无忧!轻松驾驭各种文件格式,高效管理您的数字世界

信息爆炸的时代&#xff0c;我们每天都会接触到各种各样的文件格式。无论是工作文档、图片、视频还是音频文件&#xff0c;它们都以不同的格式存在于我们的电脑和移动设备中。然而&#xff0c;不同的软件和应用往往只支持特定的文件格式&#xff0c;这给我们的工作和生活带来了…

Petal-X :心血管疾病临床风险可视化工具

心血管疾病&#xff08;Cardiovascular diseases, CVDs&#xff09;是全球致死的首要原因&#xff0c;但在大多数情况下&#xff0c;它们是可以通过行为干预来预防的。因此&#xff0c;在个体层面上&#xff0c;有效地传达心血管疾病的风险以及通过风险因素的修改来预计风险降低…

无线WiFi毫米波雷达传感器成品,智能照明人体感应开关,飞睿智能点亮智慧生活

在智能科技飞速发展的今天&#xff0c;我们的生活正被各种智能设备所包围&#xff0c;其中智能照明作为智能家居的重要组成部分&#xff0c;正逐渐改变着我们的生活方式。而在这背后&#xff0c;有一个默默工作的“小助手”——飞睿智能毫米波雷达传感器&#xff0c;它就像智能…

周边美食小程序系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;美食店铺管理&#xff0c;菜品分类管理&#xff0c;标签管理&#xff0c;菜品信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;美食店铺&#…

网页用事件监听器播放声音

一、什么是监听器&#xff1a; 在前端页面中&#xff0c;事件监听器&#xff08;Event Listener&#xff09;是一种编程机制&#xff0c;它允许开发者指定当特定事件&#xff08;如用户点击按钮、鼠标悬停、页面加载完成等&#xff09;发生时执行特定的代码块。简而言之&#x…

css 滚动词云

css javascript 实现滚动词云效果 // 163css.js var radius 120; var dtr Math.PI / 180; var d 300; var mcList []; var active false; var lasta 1; var lastb 1; var distr true; var tspeed 10; var size 250; var mouseX 0; var mouseY 0; var howElliptic…

使用面向对象方式编写ROS2节点

1.使用c方式创建节点 在d2lros2/chapt2/chapt2_ws/src/example_cpp/src下新建node_03.cpp&#xff0c;接着输入下面的代码。 #include "rclcpp/rclcpp.hpp" /* 创建一个类节点&#xff0c;名字叫做Node03,继承自Node. */ class Node03 : public rclcpp::Node {…

数据脱敏学习

数据脱敏是一种保护敏感信息的方法&#xff0c;它通过修改或删除数据中的敏感部分&#xff0c;使得数据在保持一定可用性的同时&#xff0c;不再直接关联到个人隐私或重要信息。 自然人指可以直接或间接标识 直接标识&#xff1a;如姓名、身份证号码、家庭住址、电话号码、电…

权威认可 | Smartbi连续5年入选“Gartner增强数据分析代表厂商”

近日&#xff0c;全球权威技术研究与咨询公司Gartner最新发布《2024 年中国数据、分析和人工智能技术成熟度曲线》&#xff0c;Smartbi以其卓越的增强数据分析及自助分析能力&#xff0c;再次入选代表厂商&#xff0c;这也是Smartbi连续5年入选增强数据分析及自助分析代表厂商&…

统计信号处理基础 习题解答11-4

题目 观测到数据&#xff1a;, 假定未知参数A具有先验 PDF 其中&#xff0c;, 是方差为的WGN&#xff0c;且与A独立&#xff0c;求A的MAP估计量。 解答 根据题目条件&#xff0c;得到条件概率&#xff1a; 那么对于N个观察的独立数据&#xff0c;有&#xff1a; 因此&#xf…