【FFmpeg】avformat_write_header函數

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;// 初始化ptsif (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);elseavpriv_set_pts_info(st, 33, 1, 90000);}switch (par->codec_type) {// 檢查音頻的sample_rate和block_aligncase 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 priorityover 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
#endifdesc = 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_tagif (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 herepar->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;}} elsepar->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條流,分別是視頻、音頻、字幕和DATAif (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;} elses->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位表示是否存在視頻tagavio_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 typeavio_wb24(pb, 0);   // include flagsavio_wb24(pb, 0);   // time stampavio_wb32(pb, 0);   // reservedavio_wb32(pb, 11);  // sizeflv->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_typeavio_wb24(pb, 0); // size patched laterput_timestamp(pb, ts);avio_wb24(pb, 0); // streamidpos = 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 headerif (!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); //profileput_bits(&pbc, 4, samplerate_index); //sample rate indexput_bits(&pbc, 4, channels);put_bits(&pbc, 1, 0); //frame length - 1024 samplesput_bits(&pbc, 1, 0); //does not depend on core coderput_bits(&pbc, 1, 0); //is not extensionflush_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) { // hevcavio_w8(pb, FLV_IS_EX_HEADER | PacketTypeSequenceStart | FLV_FRAME_KEY); // ExVideoTagHeader mode with PacketTypeSequenceStartavio_write(pb, "hvc1", 4);} else if (par->codec_id == AV_CODEC_ID_AV1 || par->codec_id == AV_CODEC_ID_VP9) { // av1和vp9avio_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); // flagsavio_w8(pb, 0); // AVC sequence headeravio_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);elseff_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.pswp.cn/diannao/37491.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/37491.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/37491.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

GPT-4o首次引入!全新圖像自動評估基準發布!

目錄 01 什么是DreamBench&#xff1f; 02 與人類對齊的自動化評估 03 更全面的個性化數據集 04 實驗結果 面對層出不窮的個性化圖像生成技術&#xff0c;一個新問題擺在眼前&#xff1a;缺乏統一標準來衡量這些生成的圖片是否符合人們的喜好。 對此&#xff0c;來自清華大…

sql server啟動、連接 與 navicat連接sql server

一、sql server 啟動 1.搜索cmd->以管理員身份運行 2.輸入以下命令 net start mssqlserver 3.服務器啟動成功 二、sql server連接 1.打開ssms&#xff0c;輸入&#xff0c;連接 2.右鍵&#xff0c;屬性 3.連接&#xff0c;勾選允許遠程連接到此服務器 三、navicat連接sq…

java:aocache的單實例緩存(二)

之前一篇博客《java:aocache的單實例緩存》介紹了aoocache使用注解AoCacheable實現單實例緩存的方式&#xff0c;同時也指出了這種方式的使用限制&#xff0c;就是這個注解定義的構造方法&#xff0c;不能再創建出新實例。 為了更靈活方便的實現單實例。aocache最新版本0.4.0增…

人工智能筆記本選購標準

適合人工智能專業使用的筆記本電腦需要有高性能的CPU、大內存、快速的硬盤、高性能的GPU、支持虛擬化等特性。 一、硬件配置要求 要選擇適合人工智能專業使用的筆記本電腦&#xff0c;配置是非常重要的考慮因素。首先&#xff0c;它需要有高性能的CPU&#xff0c;如i7或i9處理器…

nodejs--nodejs內置模塊(上)

05 【nodejs內置模塊&#xff08;上&#xff09;】 1.nodejs 的官方API文檔 Node.js 的API文檔&#xff08;英文&#xff09;&#xff1a; https://nodejs.org/docs/latest-v8.x/api/index.htmlNode.js 的API文檔&#xff08;中文&#xff09;&#xff1a;http://nodejs.cn/a…

Python實現無頭瀏覽器采集應用的反爬蟲與反檢測功能解析與應對策略

Python實現無頭瀏覽器采集應用的反爬蟲與反檢測功能解析與應對策略 隨著網絡數據的快速增長&#xff0c;爬蟲技術在數據采集、信息分析和業務發展中扮演著重要的角色。然而&#xff0c;隨之而來的反爬蟲技術也在不斷升級&#xff0c;給爬蟲應用的開發和維護帶來了挑戰。為了應…

iCloud Drive使用指南:如何在云端創建和管理文件夾與文件

iCloud Drive是蘋果公司提供的一項云存儲服務&#xff0c;它允許用戶在所有Apple設備上存儲文件和文檔&#xff0c;并實現無縫同步。通過iCloud Drive&#xff0c;用戶可以輕松創建、訪問、編輯和共享文件&#xff0c;無論身處何地。本文將詳細介紹如何在iCloud Drive中創建文件…

Python 學習路線及技巧

一、學習路線 1. 基礎階段 ● 學習 Python 的語法基礎&#xff0c;如變量、數據類型、運算符、控制流等。 ● 掌握常用的 Python 標準庫&#xff0c;如 os、sys、re、datetime 等。 ● 通過編寫簡單的程序來鞏固基礎&#xff0c;如計算器、字符串處理等。 2. 進階階段 ● 深入…

媒體宣發套餐的概述及推廣方法-華媒舍

在今天的數字化時代&#xff0c;對于產品和服務的宣傳已經變得不可或缺。媒體宣發套餐作為一種高效的宣傳方式&#xff0c;在幫助企業塑造品牌形象、擴大影響力方面扮演著重要角色。本文將揭秘媒體宣發套餐&#xff0c;為您呈現一條通往成功的路。 1. 媒體宣發套餐的概述 媒體…

MySQL中的存儲引擎

介紹 存儲引擎就是存儲數據&#xff0c;建立索引&#xff0c;更新/查詢數據等技術的實現方式。存儲引擎是基于表的&#xff0c;而不是基于庫的&#xff0c;所以存儲引擎也可以稱為表類型&#xff08;即一個數據庫下的表可以選擇不同的存儲引擎&#xff09;。 1. 如何查看一個…

day57---面試專題(框架篇)

框架篇 1. Spring refresh 流程 要求 掌握 refresh 的 12 個步驟Spring refresh 概述 refresh 是 AbstractApplicationContext 中的一個方法,負責初始化 ApplicationContext 容器,容器必須調用 refresh 才能正常工作。它的內部主要會調用 12 個方法,我們把它們稱為 refre…

收銀系統源碼-千呼新零售【手機端收銀】

千呼新零售2.0系統是零售行業連鎖店一體化收銀系統&#xff0c;包括線下收銀線上商城連鎖店管理ERP管理商品管理供應商管理會員營銷等功能為一體&#xff0c;線上線下數據全部打通。 適用于商超、便利店、水果、生鮮、母嬰、服裝、零食、百貨、寵物等連鎖店使用。 詳細介紹請…

風風火火的新造車,或正在醞釀下一個樂視系,造車就是個大坑

隨著國內新能源汽車占新車市場的比例突破五成&#xff0c;燃油車發起了猛烈的反擊&#xff0c;5月份燃油車猛烈反彈&#xff0c;前五名之中就有5款是燃油車&#xff0c;燃油車到了背水一戰的時候&#xff0c;隨著電動汽車和燃油車的較量達到白熱化&#xff0c;新造車被淘汰一部…

deepE 定位系統卡頓問題實戰(一) ----------- 鎖造成的阻塞問題

deepE介紹 deepE是一個開源的用于端側(自動駕駛車,機器人)等環境的系統問題與性能分析工具。基于ebpf功能實現 deepE項目地址 歡迎star 測試程序 #include <iostream> #include <thread> #include <mutex>static std::mutex lock;void func1() {int l…

FastGPT本地手動部署(一)mongodb和pgvector的安裝

本文主要介紹 mongodb 和 pgvector 的安裝,為了更快安裝,通過 docker 的方式進行安裝,本文使用的環境是 ubuntu 20.04。 一、安裝 docker 直接通過 apt 進行安裝,執行如下命令。 sudo apt install docker.io docker-compose 二、安裝 mongodb 和 pgvector (1)docker…

出現 nested exception is java.sql.SQLException: 無效的列類型 的解決方法

目錄 前言1. 問題所示2. 原理分析3. 解決方法4. 彩蛋前言 以下主要以mybatisPLus或者JDBC的方式進行插入,導致數據庫出現類型不一致 如果是mybatis中的xml也差不多思路 1. 問題所示 代碼與數據庫交互的時候,出現如下問題 org.springframework.jdbc.UncategorizedSQLExcep…

無視OpenAI限制:智創聚合API的穩定服務承諾

近期OpenAI的一則消息——終止對中國提供API服務&#xff0c;無疑給許多依賴其技術的企業和開發者帶來了不小的困擾。但別擔心&#xff0c;智創聚合API平臺始終在這里&#xff0c;為您提供穩定、可靠且經濟的AI服務。 穩定服務&#xff0c;不受限制 智創聚合API平臺的服務器設在…

kafka(一)原理(2)組件

一、broker 1、介紹 kafka服務器的官方名字&#xff0c;一個集群由多個broker組成&#xff0c;一個broker可以容納多個topic。 2、工作流程 3、重要參數 參數名稱 描述 replica.lag.time.max.ms ISR中&#xff0c;如果Follower長時間未向Leader發送通信請求或同步數據&a…

MessageBox的作用與用法

在C# &#xff08; Windows Forms &#xff09;中&#xff0c;MessageBox 的所有常用用法如下&#xff1a; 1. 顯示一個簡單的消息框 MessageBox.Show("這是一個簡單的消息框。");2. 顯示帶標題的消息框 MessageBox.Show("這是一個帶標題的消息框。", &…