目录
- 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