全球快消息!【架构图】FFmpeg的库函数源代码分析
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()。上篇文章已经分析了avformat_write_header(),这篇文章继续分析av_write_frame()。
av_write_frame()用于输出一帧视音频数据,它的声明位于libavformat\avformat.h,如下所示。
/** * Write a packet to an output media file. * * This function passes the packet directly to the muxer, without any buffering * or reordering. The caller is responsible for correctly interleaving the * packets if the format requires it. Callers that want libavformat to handle * the interleaving should call av_interleaved_write_frame() instead of this * function. * * @param s media file handle * @param pkt The packet containing the data to be written. Note that unlike * av_interleaved_write_frame(), this function does not take * ownership of the packet passed to it (though some muxers may make * an internal reference to the input packet). * * This parameter can be NULL (at any time, not just at the end), in * order to immediately flush data buffered within the muxer, for * muxers that buffer up data internally before writing it to the * output. * * Packet"s @ref AVPacket.stream_index "stream_index" field must be * set to the index of the corresponding stream in @ref * AVFormatContext.streams "s->streams". It is very strongly * recommended that timing information (@ref AVPacket.pts "pts", @ref * AVPacket.dts "dts", @ref AVPacket.duration "duration") is set to * correct values. * @return < 0 on error, = 0 if OK, 1 if flushed and there is no more data to flush * * @see av_interleaved_write_frame() */int av_write_frame(AVFormatContext *s, AVPacket *pkt);
简单解释一下它的参数的含义: 函数正常执行后返回值等于0。
这个函数最典型的例子可以参考:
最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
函数调用关系图
av_write_frame()的调用关系如下图所示。
av_write_frame()
av_write_frame()的定义位于libavformat\mux.c,如下所示。
int av_write_frame(AVFormatContext *s, AVPacket *pkt){ int ret; ret = check_packet(s, pkt); if (ret < 0) return ret; //Packet为NULL,Flush Encoder if (!pkt) { if (s->oformat->flags & AVFMT_ALLOW_FLUSH) { ret = s->oformat->write_packet(s, NULL); if (s->flush_packets && s->pb && s->pb->error >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS) avio_flush(s->pb); if (ret >= 0 && s->pb && s->pb->error < 0) ret = s->pb->error; return ret; } return 1; } ret = compute_pkt_fields2(s, s->streams[pkt->stream_index], pkt); if (ret < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS)) return ret; //写入 ret = write_packet(s, pkt); if (ret >= 0 && s->pb && s->pb->error < 0) ret = s->pb->error; if (ret >= 0) s->streams[pkt->stream_index]->nb_frames++; return ret;}
从源代码可以看出,av_write_frame()主要完成了以下几步工作: (1)调用check_packet()做一些简单的检测 (2)调用compute_pkt_fields2()设置AVPacket的一些属性值 (3)调用write_packet()写入数据
下面分别看一下这几个函数功能。
check_packet()
check_packet()定义位于libavformat\mux.c,如下所示。
static int check_packet(AVFormatContext *s, AVPacket *pkt){ if (!pkt) return 0; if (pkt->stream_index < 0 || pkt->stream_index >= s->nb_streams) { av_log(s, AV_LOG_ERROR, "Invalid packet stream index: %d\n", pkt->stream_index); return AVERROR(EINVAL); } if (s->streams[pkt->stream_index]->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT) { av_log(s, AV_LOG_ERROR, "Received a packet for an attachment stream.\n"); return AVERROR(EINVAL); } return 0;}
从代码中可以看出,check_packet()的功能比较简单:首先检查一下输入的AVPacket是否为空,如果为空,则是直接返回;然后检查一下AVPacket的stream_index(标记了该AVPacket所属的AVStream)设置是否正常,如果为负数或者大于AVStream的个数,则返回错误信息;最后检查AVPacket所属的AVStream是否属于attachment stream,这个地方没见过,目前还没有研究。
compute_pkt_fields2()
compute_pkt_fields2()函数的定义位于libavformat\mux.c,如下所示。
//FIXME merge with compute_pkt_fieldsstatic int compute_pkt_fields2(AVFormatContext *s, AVStream *st, AVPacket *pkt){ int delay = FFMAX(st->codec->has_b_frames, st->codec->max_b_frames > 0); int num, den, i; int frame_size; av_dlog(s, "compute_pkt_fields2: pts:%s dts:%s cur_dts:%s b:%d size:%d st:%d\n", av_ts2str(pkt->pts), av_ts2str(pkt->dts), av_ts2str(st->cur_dts), delay, pkt->size, pkt->stream_index); if (pkt->duration < 0 && st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) { av_log(s, AV_LOG_WARNING, "Packet with invalid duration %d in stream %d\n", pkt->duration, pkt->stream_index); pkt->duration = 0; } /* duration field */ if (pkt->duration == 0) { ff_compute_frame_duration(s, &num, &den, st, NULL, pkt); if (den && num) { pkt->duration = av_rescale(1, num * (int64_t)st->time_base.den * st->codec->ticks_per_frame, den * (int64_t)st->time_base.num); } } if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && delay == 0) pkt->pts = pkt->dts; //XXX/FIXME this is a temporary hack until all encoders output pts if ((pkt->pts == 0 || pkt->pts == AV_NOPTS_VALUE) && pkt->dts == AV_NOPTS_VALUE && !delay) { static int warned; if (!warned) { av_log(s, AV_LOG_WARNING, "Encoder did not produce proper pts, making some up.\n"); warned = 1; } pkt->dts =// pkt->pts= st->cur_dts; pkt->pts = st->pts.val; } //calculate dts from pts if (pkt->pts != AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE && delay <= st-="">pts_buffer[0] = pkt->pts; for (i = 1; i < delay + 1 && st->pts_buffer[i] == AV_NOPTS_VALUE; i++) st->pts_buffer[i] = pkt->pts + (i - delay - 1) * pkt->duration; for (i = 0; ipts_buffer[i] > st->pts_buffer[i + 1]; i++) FFSWAP(int64_t, st->pts_buffer[i], st->pts_buffer[i + 1]); pkt->dts = st->pts_buffer[0]; } if (st->cur_dts && st->cur_dts != AV_NOPTS_VALUE && ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) || st->cur_dts > pkt->dts)) { av_log(s, AV_LOG_ERROR, "Application provided invalid, non monotonically increasing dts to muxer in stream %d: %s >= %s\n", st->index, av_ts2str(st->cur_dts), av_ts2str(pkt->dts)); return AVERROR(EINVAL); } if (pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE && pkt->pts < pkt->dts) { av_log(s, AV_LOG_ERROR, "pts (%s) < dts (%s) in stream %d\n", av_ts2str(pkt->pts), av_ts2str(pkt->dts), st->index); return AVERROR(EINVAL); } av_dlog(s, "av_write_frame: pts2:%s dts2:%s\n", av_ts2str(pkt->pts), av_ts2str(pkt->dts)); st->cur_dts = pkt->dts; st->pts.val = pkt->dts; /* update pts */ switch (st->codec->codec_type) { case AVMEDIA_TYPE_AUDIO: frame_size = (pkt->flags & AV_PKT_FLAG_UNCODED_FRAME) ? ((AVFrame *)pkt->data)->nb_samples : av_get_audio_frame_duration(st->codec, pkt->size); /* HACK/FIXME, we skip the initial 0 size packets as they are most * likely equal to the encoder delay, but it would be better if we * had the real timestamps from the encoder */ if (frame_size >= 0 && (pkt->size || st->pts.num != st->pts.den >> 1 || st->pts.val)) { frac_add(&st->pts, (int64_t)st->time_base.den * frame_size); } break; case AVMEDIA_TYPE_VIDEO: frac_add(&st->pts, (int64_t)st->time_base.den * st->codec->time_base.num); break; } return 0;}
从代码中可以看出,compute_pkt_fields2()主要有两方面的功能:一方面用于计算AVPacket的duration, dts等信息;另一方面用于检查pts、dts这些参数的合理性(例如PTS是否一定大于DTS)。具体的代码还没有细看,以后有时间再进行分析。
AVOutputFormat->write_packet()
write_packet()函数的定义位于libavformat\mux.c,如下所示。
/** * Make timestamps non negative, move side data from payload to internal struct, call muxer, and restore * sidedata. * * FIXME: this function should NEVER get undefined pts/dts beside when the * AVFMT_NOTIMESTAMPS is set. * Those additional safety checks should be dropped once the correct checks * are set in the callers. */static int write_packet(AVFormatContext *s, AVPacket *pkt){ int ret, did_split; if (s->output_ts_offset) { AVStream *st = s->streams[pkt->stream_index]; int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base); if (pkt->dts != AV_NOPTS_VALUE) pkt->dts += offset; if (pkt->pts != AV_NOPTS_VALUE) pkt->pts += offset; } if (s->avoid_negative_ts > 0) { AVStream *st = s->streams[pkt->stream_index]; int64_t offset = st->mux_ts_offset; if (s->offset == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE && (pkt->dts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) { s->offset = -pkt->dts; s->offset_timebase = st->time_base; } if (s->offset != AV_NOPTS_VALUE && !offset) { offset = st->mux_ts_offset = av_rescale_q_rnd(s->offset, s->offset_timebase, st->time_base, AV_ROUND_UP); } if (pkt->dts != AV_NOPTS_VALUE) pkt->dts += offset; if (pkt->pts != AV_NOPTS_VALUE) pkt->pts += offset; av_assert2(pkt->dts == AV_NOPTS_VALUE || pkt->dts >= 0 || s->max_interleave_delta > 0); if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) { av_log(s, AV_LOG_WARNING, "Packets poorly interleaved, failed to avoid negative " "timestamp %s in stream %d.\n" "Try -max_interleave_delta 0 as a possible workaround.\n", av_ts2str(pkt->dts), pkt->stream_index ); } } did_split = av_packet_split_side_data(pkt); if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) { AVFrame *frame = (AVFrame *)pkt->data; av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE); ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0); av_frame_free(&frame); } else { //写入 ret = s->oformat->write_packet(s, pkt); } if (s->flush_packets && s->pb && ret >= 0 && s->flags & AVFMT_FLAG_FLUSH_PACKETS) avio_flush(s->pb); if (did_split) av_packet_merge_side_data(pkt); return ret;}
write_packet()函数最关键的地方就是调用了AVOutputFormat中写入数据的方法。如果AVPacket中的flag标记中包含AV_PKT_FLAG_UNCODED_FRAME,就会调用AVOutputFormat的write_uncoded_frame()函数;如果不包含那个标记,就会调用write_packet()函数。write_packet()实际上是一个函数指针,指向特定的AVOutputFormat中的实现函数。例如,我们看一下FLV对应的AVOutputFormat,位于libavformat\flvenc.c,如下所示。
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,};
从ff_flv_muxer的定义可以看出,write_packet()指向的是flv_write_packet()函数。在看flv_write_packet()函数的定义之前,我们先回顾一下FLV封装格式的结构。
FLV封装格式
FLV封装格式如下图所示。
PS:原图是网上找的,感觉画的很清晰,比官方的Video File Format Specification更加通俗易懂。但是图中有一个错误,就是TagHeader中的StreamID字段的长度写错了(查看了一下官方标准,应该是3字节,现在已经改过来了)。
从FLV的封装格式结构可以看出,它的文件数据是一个一个的Tag连接起来的,中间间隔包含着Previous Tag Size。因此,flv_write_packet()函数的任务就是写入一个Tag和Previous Tag Size。下面简单记录一下Tag Data的格式。Tag Data根据Tag的Type不同而不同:可以分为音频Tag Data,视频Tag Data以及Script Tag Data。下面简述一下音频Tag Data和视频Tag Data。
Audio Tag Data
Audio Tag在官方标准中定义如下。 Audio Tag开始的第1个字节包含了音频数据的参数信息,从第2个字节开始为音频流数据。 第1个字节的前4位的数值表示了音频数据格式: 第1个字节的第5-6位的数值表示采样率:0 = 5.5kHz,1 = 11KHz,2 = 22 kHz,3 = 44 kHz。 第1个字节的第7位表示采样精度:0 = 8bits,1 = 16bits。 第1个字节的第8位表示音频类型:0 = sndMono,1 = sndStereo。 其中,当音频编码为AAC的时候,第一个字节后面存储的是AACAUDIODATA,格式如下所示。
Video Tag Data
Video Tag在官方标准中的定义如下。 Video Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据。 第1个字节的前4位的数值表示帧类型(FrameType): 第1个字节的后4位的数值表示视频编码ID(CodecID): 其中,当音频编码为AVC(H.264)的时候,第一个字节后面存储的是AVCVIDEOPACKET,格式如下所示。
flv_write_packet()
下面我们看一下FLV格式中write_packet()对应的实现函数flv_write_packet()的定义,位于libavformat\flvenc.c,如下所示。
static int flv_write_packet(AVFormatContext *s, AVPacket *pkt){ AVIOContext *pb = s->pb; AVCodecContext *enc = s->streams[pkt->stream_index]->codec; FLVContext *flv = s->priv_data; FLVStreamContext *sc = s->streams[pkt->stream_index]->priv_data; unsigned ts; int size = pkt->size; uint8_t *data = NULL; int flags = -1, flags_size, ret; if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A || enc->codec_id == AV_CODEC_ID_VP6 || enc->codec_id == AV_CODEC_ID_AAC) flags_size = 2; else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) flags_size = 5; else flags_size = 1; if (flv->delay == AV_NOPTS_VALUE) flv->delay = -pkt->dts; if (pkt->dts < -flv->delay) { av_log(s, AV_LOG_WARNING, "Packets are not in the proper order with respect to DTS\n"); return AVERROR(EINVAL); } ts = pkt->dts + flv->delay; // add delay to force positive dts if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) { write_metadata(s, ts); s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED; } //Tag Header switch (enc->codec_type) { case AVMEDIA_TYPE_VIDEO: //Type avio_w8(pb, FLV_TAG_TYPE_VIDEO); flags = enc->codec_tag; if (flags == 0) { av_log(s, AV_LOG_ERROR, "Video codec "%s" is not compatible with FLV\n", avcodec_get_name(enc->codec_id)); return AVERROR(EINVAL); } //Key Frame? flags |= pkt->flags & AV_PKT_FLAG_KEY ? FLV_FRAME_KEY : FLV_FRAME_INTER; break; case AVMEDIA_TYPE_AUDIO: flags = get_audio_flags(s, enc); av_assert0(size); //Type avio_w8(pb, FLV_TAG_TYPE_AUDIO); break; case AVMEDIA_TYPE_DATA: //Type avio_w8(pb, FLV_TAG_TYPE_META); break; default: return AVERROR(EINVAL); } if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { /* check if extradata looks like mp4 formated */ if (enc->extradata_size > 0 && *(uint8_t*)enc->extradata != 1) if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0) return ret; } else if (enc->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 && (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) { if (!s->streams[pkt->stream_index]->nb_frames) { av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: " "use the audio bitstream filter "aac_adtstoasc" to fix it " "("-bsf:a aac_adtstoasc" option with ffmpeg)\n"); return AVERROR_INVALIDDATA; } av_log(s, AV_LOG_WARNING, "aac bitstream error\n"); } /* check Speex packet duration */ if (enc->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160) av_log(s, AV_LOG_WARNING, "Warning: Speex stream has more than " "8 frames per packet. Adobe Flash " "Player cannot handle this!\n"); if (sc->last_ts < ts) sc->last_ts = ts; if (size + flags_size >= 1<<24) { av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n", size + flags_size, 1<<24); return="" tag="" header="" -="" datasize="" size="" timestamp="" ts="">> 24) & 0x7F); // timestamps are 32 bits _signed_ //StreamID avio_wb24(pb, flv->reserved); if (enc->codec_type == AVMEDIA_TYPE_DATA) { int data_size; int64_t metadata_size_pos = avio_tell(pb); if (enc->codec_id == AV_CODEC_ID_TEXT) { // legacy FFmpeg magic? avio_w8(pb, AMF_DATA_TYPE_STRING); put_amf_string(pb, "onTextData"); avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY); avio_wb32(pb, 2); put_amf_string(pb, "type"); avio_w8(pb, AMF_DATA_TYPE_STRING); put_amf_string(pb, "Text"); put_amf_string(pb, "text"); avio_w8(pb, AMF_DATA_TYPE_STRING); put_amf_string(pb, pkt->data); put_amf_string(pb, ""); avio_w8(pb, AMF_END_OF_OBJECT); } else { // just pass the metadata through avio_write(pb, data ? data : pkt->data, size); } /* write total size of tag */ data_size = avio_tell(pb) - metadata_size_pos; avio_seek(pb, metadata_size_pos - 10, SEEK_SET); avio_wb24(pb, data_size); avio_seek(pb, data_size + 10 - 3, SEEK_CUR); avio_wb32(pb, data_size + 11); } else { av_assert1(flags>=0); //First Byte of Tag Data avio_w8(pb,flags); if (enc->codec_id == AV_CODEC_ID_VP6) avio_w8(pb,0); if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A) { if (enc->extradata_size) avio_w8(pb, enc->extradata[0]); else avio_w8(pb, ((FFALIGN(enc->width, 16) - enc->width) << 4) | (FFALIGN(enc->height, 16) - enc->height)); } else if (enc->codec_id == AV_CODEC_ID_AAC) avio_w8(pb, 1); // AAC raw else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { //AVCVIDEOPACKET-AVCPacketType avio_w8(pb, 1); // AVC NALU //AVCVIDEOPACKET-CompositionTime avio_wb24(pb, pkt->pts - pkt->dts); } //Data avio_write(pb, data ? data : pkt->data, size); avio_wb32(pb, size + flags_size + 11); // previous tag size flv->duration = FFMAX(flv->duration, pkt->pts + flv->delay + pkt->duration); } av_free(data); return pb->error;}
我们通过源代码简单梳理一下flv_write_packet()在写入H.264/AAC时候的流程: (1)写入Tag Header的Type,如果是视频,代码如下:
avio_w8(pb, FLV_TAG_TYPE_VIDEO);
如果是音频,代码如下:
avio_w8(pb, FLV_TAG_TYPE_AUDIO);
(2)写入Tag Header的Datasize,Timestamp和StreamID(至此完成Tag Header):
//Tag Header - Datasize avio_wb24(pb, size + flags_size); //Tag Header - Timestamp avio_wb24(pb, ts & 0xFFFFFF); avio_w8(pb, (ts >> 24) & 0x7F); // timestamps are 32 bits _signed_ //StreamID avio_wb24(pb, flv->reserved);
(3)写入Tag Data的第一字节(其中flag已经在前面的代码中设置完毕):
//First Byte of Tag Data avio_w8(pb,flags);
(4)如果编码格式VP6作相应的处理(不研究);编码格式为AAC,写入AACAUDIODATA;编码格式为H.264,写入AVCVIDEOPACKET:
if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A) { if (enc->extradata_size) avio_w8(pb, enc->extradata[0]); else avio_w8(pb, ((FFALIGN(enc->width, 16) - enc->width) << 4) | (FFALIGN(enc->height, 16) - enc->height)); } else if (enc->codec_id == AV_CODEC_ID_AAC) avio_w8(pb, 1); // AAC raw else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) { //AVCVIDEOPACKET-AVCPacketType avio_w8(pb, 1); // AVC NALU //AVCVIDEOPACKET-CompositionTime avio_wb24(pb, pkt->pts - pkt->dts); }
(5)写入数据:
//Data avio_write(pb, data ? data : pkt->data, size);
(6) 写入previous tag size:
avio_wb32(pb, size + flags_size + 11); // previous tag size
至此,flv_write_packet()就完成了一个Tag的写入。
标签:
相关推荐:
精彩放送:
- []全球今头条!构建开发环境——JDK6.0安装配置
- []环球今日报丨年轻男生掉头发是什么原因?年轻男性脱发的原因有哪些?
- []世界简讯:宁夏地方专项计划招生学校名单及2022年录取分数线
- []焦点热文:手工狗窝怎么制作?纯手工狗窝制作方法
- []曝光20家高风险运输企业
- []世界视讯!fact是什么意思?fact的用法有哪些?
- []热文:bar是什么意思?bar的用法有哪些?
- []自制冷饮怎么做?自制冷饮做法大全简单
- []《爱的奉献》原唱歌词有哪些?爱的奉献歌曲简介
- []看点:降息利好哪些领域?降息利好哪些板块?
- []世界热资讯!false是什么意思?false的用法有哪些?
- []crowd是什么意思?crowd的用法你知道吗?
- []环球看热讯:Alien是什么意思?Alien的中文翻译
- []全球热点!如何制作手机上网和多台电脑上WIFI?制作虚拟路由器免费WIFI方法
- []世界热讯:创见u盘怎么样?创见U盘价格及快速修复方法
- []【时快讯】东风谷早苗最新钢达姆模型(附代码)
- []世界即时看!帝尔复读机怎样?帝尔复读机价格性能介绍
- []UML类图画法全程解析 UML类图画法的介绍
- []天天快资讯丨电脑屏幕录像应该怎么操作?录制屏幕方法
- []网络层-数据包的包格式里面的字段是什么?详情介绍
- []当前观点:xbox游戏机怎么样?微软Xbox电视游戏机评测
- []【全球新要闻】使用adb操作android手机的idb工具有哪些?详情介绍
- []什么叫电子邮箱?注册电子邮箱的方法有哪些?
- []焦点热门:努比亚Z17畅享版和Z17有什么区别?性能与双摄区别在哪?
- []2月23日重点数据和大事件前瞻
- []焦点速读:中国主要山脉及山峰分布 中国地形地貌模型研究进展
- []世界讯息:标准差计算器怎样安装使用?标准差计算器使用教程介绍
- []百度域名收录情况怎么看?百度域名收录查看方法
- []不可预料的压缩文件末端解决办法是什么?解决方法步骤
- []今日聚焦!中国现在有多少家苹果零售店?苹果零售店在中国的发展历程
- []演化策略(Evolutionary Strategies)
- []天天实时:如何使用Markdown编辑器写博客?Markdown编辑器的基本语法知识
- []光载毫米波无线电通信技术现状介绍 光载毫米波无线电通信技术的发展
- []博士音响好不好?博士音响品牌介绍
- []当前视讯!大白菜系统怎么安装?大白菜安装系统两种方式
- []今日快看!lic是什么文件?lic文件怎么打开?
- []快看点丨【干货】运放放大器的负反馈和缓冲器
- []重点聚焦!海顿燃气壁挂炉好不好?海顿燃气壁挂炉优点介绍
- []世界观察:实例检查视频是否已暂停:myVid=document
- []全球时讯:什么是IP转换器?手机IP地址怎么代理?
- []天天头条:苹果作为市场上的主流手机有哪些维修点?苹果手机维修点查询
- []服务器遭遇DDoS攻击时会有哪些症状?DDos攻击的基本原理及症状
- []每日看点!PDF转CAD格式图纸怎么做?教你两种方法
- []环球热推荐:ubuntu16.04怎么安装搜狗输入法?安装搜狗输入法流程步骤
- []TD早报 | 韩国决定3月1日起取消自中国入境人员核酸检测;国台办呼吁:尽快恢复两岸空中客运直航正常化
- []今日热搜:招商信用卡解绑还款银行卡,boc信用卡网银绑定后解绑
- []世界观察:信用卡周六日可以刷出来吗,这些信用卡怎么办?
- []每日讯息!保险公司倒闭万能账户安全吗,如何投资万能理财?
- []每日短讯:黑户哪里能办信用卡,信用卡申请流程:填写申请表及附件
- []手机银行信用卡能提额度吗,商-3信用卡临时改善额度
- []今日聚焦!沪惠保在哪里看保单
- []今日热门!第三方责任险200万与300万的区别,有以下两点
- []住房公积金缴费基数是怎么计算的,公积金断缴要注意什么
- []天天要闻:支付宝买车险便宜还是保险公司买便宜,有以下两种情况
- []交强险过期多久还能正常上路,24小时
- []建筑设备供货商郝氏控股再度向港交所递表 2022年4-9月收益5160万港元
- []当前播报:战无不胜是指什么生肖动物?_战无不胜是指什么生肖
- []每日观察!关于兔的歇后语大全_老鼠钻进风箱里歇后语下一句是什么意思_歇后语大全老鼠钻进风箱里
- []房子贷款湖北银行怎么样
- []快播:失地保险在哪里查询,如何查询社保信息?
- []华夏银行信用卡好下卡吗,华夏信用卡申请条件已经敲定
- []世界时讯:社保补贴怎么查询,如何查询社保补贴?可从网站查询
- []农村合作社能贷多少款,农村credit合作社贷款条件一览
- []保利发展向特定对象发行A股相关议案获董事会通过
- []债市风云|融创房地产集团被纳入失信被执行人 东方金诚终止国美信用评级
- []【环球新视野】美亚柏科:公司2017年成立AI研发中心,深度开展人工智能技术研究
- []全球快看点丨纬思迈财经,出示农行信用卡有风险吗?专家为你解答
- []天天简讯:少儿超能宝交十年能取出来吗
- []全球快看点丨平安银行有可能倒,平安银行第三章:破产可能性微乎其微
- []泰康人寿千人培训怎么样,招聘业务员的程序和你说的一样
- []全球观速讯丨拼多多出小额贷了,拼多多0元下单说明支付小额借款
- []环球微资讯!瀚川智能2022年度净利1.27亿同比增长109.36% 订单有序交付
- []全球视讯!奕东电子:截止2023年2月20日,公司股东户数为25,860户
- []快消息!值得买:2月21日公司高管刘峰减持公司股份合计5100股
- []环球简讯:友讯达:2月21日公司高管崔涛减持公司股份合计5100股
- []世界观热点:东莞医保报销比例三甲医院,医保定点医院异地就医挂号流程
- []众安保险指定医院,关于保管好医保的详细内容请看第一篇
- []要闻速递:工行信用卡申请延期还款,信用卡月单逾期怎么办?
- []环球快资讯丨换牙医保能报销吗,补牙医保卡报销须耐心等待
- []民生信用卡怎么提前还款,民生信用卡提前还款服务今日开放
- []全球短讯!珠江股份回复上交所:本次重大资产置换交易定价具备公允性
- []环球今头条!四环生物股价又涨了“一分” 索赔案诉讼时效仅剩60多天
- []世界视讯!龙软科技2022年度净利8085.93万同比增长28.19% 业务持续稳定增长
- []重点聚焦!中泰化学:如有重大进展公司将及时履行信息披露义务
- []珠江股份回复上交所:提前还款符合上市公司未来经营规划
- []普门科技:2月22日王铮减持公司股份合计3000股
- []山东玻纤:2月20日至2月21日宋忠玲减持公司股份合计18.5万股
- []焦点要闻:珠江股份更新重大资产置换及出售方案 修订现价对价用途等
- []东原仁知服务H股全流通申请获中国证监会受理
- []全球热消息:无所不能?无所不骗!捞人出狱寻回名画皆是一场空
- []环球快播:内蒙古当兵属于艰苦地区吗,在乌鲁木齐当兵不算艰苦地区?
- []生育险报销是打到社保卡上吗,生育保险制度沿革
- []环球视讯!民生信用卡2张卡通用的吗,民生bank信用卡开卡发两张卡
- []当前热点-信用卡怎么在app上注销,信用卡onappon怎么办?
- []浦发信用卡怎么提前还款,如何申请信用卡提前还款?方法如下
- []硅宝科技:2月21日公司高管方丽减持公司股份合计8000股
- []东野圭吾出道35周年荣耀新作 京东2月21日0时独家纸电齐发《白鸟与蝙蝠》
- []当前快报:杭州拟向二孩三孩家庭发放育儿补助 总金额预计约1.4亿
- []建业新生活财务负责人由周大鹏变更为郭立圆
- []* 一线城市增速放缓,元气森林唐彬森称“互联网思维是毒药”
- 套信用卡利息怎么算,信用卡透支消费免息期三个月内提现免费
- 银行贷款到期还不上可以申请延期吗,如何申请延期贷款?
- 世界关注:怎么知道自己有没有二次报销,医保二次报销有何不同?
- 热点!个人交社保有医保存折吗,医保give存折意味着什么?
- 环球快讯:建行信用卡分期怎么提前还款,如何申请分期付款?
- 全球视讯!物业丨建业新生活:周大鹏卸任财务负责人 郭立圆接任
- 环球简讯:中一科技:公司不生产覆铜板,但公司生产的标准铜箔产品是覆铜板的主要原材料之一
- 环球要闻:财面儿|正荣集团退出上海荣顾100%股权 正荣旗下荣邦达通接盘
- 华帝股份:该项目目前按照既定计划推进中
- 环球今日讯!家居丨恒尚股份递表上交所主板 拟募资5.77亿元
- 财面儿丨中海宏洋地产拟发行10亿元公司债券 票面利率询价区间3.5%-4.8%
- 环球讯息:又一地楼市放大招!取消新房限售,三孩家庭公积金最高可贷120万…
- 全球热头条丨隆利科技:截至2023年2月20日,公司的股东人数为12,375,谢谢您的关注与支持!
- 世界视讯!什么是原创歌手?
- 今日热闻!赣粤高速:公司目前未投资高速公路光伏项目
- 天天新动态:学平险可以报销狂犬疫苗吗,国产注射液疫苗学平险可报销吗?
- 淘宝信用卡在哪里申请,信用卡怎么开?
- 少儿平安福报销范围,少儿平安福保险好但不一定对孩子最好!
- 百尚贷款多久下款,申请贷款侯多久银行会下款
- 维权群被挤爆了!深交所火速发函,股民懵了,到底谁在撒谎
- 天天播报:中国人寿正式员工,中国人寿保险公司有正式员工吗?
- 天天视讯!成都远洋太古里项目交易全部完成交割 太古地产拥有100%权益
- 焦点讯息:宋都股份两名董事高管拟减持合计不超29.25万股
- 每日精选:横店东磁:公司主要生产铁氧体磁性材料产品,主要原材料是铁红,故没有涉足稀土开采
- 全通教育:截至2022年12月20日,公司股东人数为42,740人
- 世界要闻:变局下的回归与生长——联合资信2023年度中国债券市场风险展望论坛顺利召开
- 环球信息:郑州:2023年经济发展预期GDP增速为7% 促进房地产业良性循环
- 环球快播:雄韬股份:公司钠电中试线建设正在有序推进当中,计划将于2023年投产,同时可启动一期量产线建设
- 环球热讯:保利地产投资顾问公司被罚 代理销售不符合条件商品房
- 每日精选:宇新股份:截止2月20日,公司股东人数为10847
- 浙江舟山普陀区:最高400万元扶持光伏、新型储能、氢能示范应用等项目建设
- 每日播报!信用卡每月限额,信用卡可贷多少取决于个人收入和房产记录
- 【环球聚看点】电芯成本呈下降态势,2023年储能产业继续高增可期
- 每日消息!光能杯储能主题演讲第三弹:储能行业趋势十大预测(含PPT)
- 环球微头条丨1580万起拍,60多亿成交!又现天价锂矿,买家何方神圣?背后藏着多家A股公司
- 全球观天下!一节钠离子电池是如何诞生的?
- 多晶硅周评-订单签订结束 价格持稳运行
- 简讯:人保报案号查询,国保车险查询四大重点:电话查询
- 当前播报:协鑫能科发力换电赛道 首批12座换电站将投运
- 【环球报资讯】上学为什么要交社保,没交社保可上学吗?一般情况下都可以
- 全球焦点!交通银行改账单日,交通银行信用卡账单日后第25天为还款日
- 天天新消息丨关于奥联电子“钙钛矿大神”往事,交易所又发来关注函……
- 广发银行最低还款影响征信吗,最低还款金额是多少?
- 世界热点!“打假门”再发酵!众能光电针对奥联电子发布澄清声明
- 金地集团20亿元公司债将付息 票面利率3.93%
- 天天快资讯:NYMEX原油料下探75美元关口
- 环球新动态:豫园股份:有关公司经营情况以公司在上海证券交易所网站及指定信息披露媒体上披露的临时公告及定期报告为准
- 当前速讯:关于日本历史文化风土人情的书有哪些?关于日本历史文化风土人情的书汇总?
- 世界要闻:剑网3目前有哪些副本?剑网3副本介绍?
- 京都薇薇“商业+”孵化平台 盛大起航!
- 极路由hiwifi手机怎么设置?设置步骤有哪些?
- 21金地01将于3月1日付息 发行金额20亿元
- 全球今日讯!微信支付密码怎么改?微信密码改方法
- 【热闻】win7运行在哪?电脑Win7系统运行方式有哪几种?
- 当前快播:台风红色预警信号标准是什么?超强台风是指什么?
- 全球报道:因达到退休年龄 刘铁林、孙东樊辞任北辰实业副总经理
- 全球热资讯!黑芝麻:公司参与了南宁市五象养老中心PPP项目的投资
- 环球百事通!高力指调整从价印花税税阶实际对香港楼价影响不大
- 世界视点!国信期货日评:俄油出口增加,油价维持区间震荡
- 世界头条:湖北民族学院怎么样?湖北民族学院资料介绍?
- 现在那种播放器最好用?视频播放器推荐?
- wrf文件怎么打开?wrf文件打开的方法
- 焦点播报:吸顶灯有哪些品牌?吸顶灯品牌推荐介绍
- 安徽发行5至10年期棚改专项地方债 规模达45亿元
- 世界快消息!2月22日中国卫星涨停分析:卫星互联网,北斗导航,航天概念热股
- 视点!2月22日首航高科涨停分析:光热发电,碳中和,风电概念热股
- 联想b40-30笔记本怎么样?联想b40之联想b40-30测评及报价
- 北京商场有哪些?北京有几个大悦城?
- 2月22日坤泰股份涨停分析
- 环球关注:西红柿怎么种植?西红柿种植方法是什么?
- 读书文摘卡的格式是怎样的?读书文摘卡怎么制作?
- 阴阳师帚神哪里多?阴阳师帚神哪里多速刷攻略大全
- 医保卡买过的药能查到吗,如何查询医保卡消息?
- 全球关注:湖南安仁金紫仙抽水蓄能电站“三大专题”审查会议召开
- 全球消息!i78700和i78700k区别是什么? i7 8700和i7 8700k 参数对比差多少?
- 龙抬头公益理发,杭州德佑回馈社区
- 今日热文:Ecobat将新建锂离子电池回收设施
- 全球视讯!当日快讯:发改委等部门印发第29批新认定及全部国家企业技术中心名单
- 环球微速讯:【自控笔记】自动控制系统的基本原理及分类原则
- 当前讯息:Panels教程:向panel页面中添加节点的创建流程
- 学生保险查询平台,学生人寿保险查询方法如下
- 中汽协:1月新能源汽车产销同比分别下降6.9%和6.3%
- 携程:“五一”出境机票均价3022元,同比去年腰斩
- 世界热门:计算机组成原理知识点总结——第七章输入/输出系统
- 今日要闻!10 个储能系统设计的重要考虑因素
- 天天快资讯:医保预注金额,如何计算当年账户和个人账户资金?
- 天天看点:mysql常用函数返回值样例汇总 Oracle函数大全
- 上海社保个人补贴8年,缴纳社会保险费期限补贴可延至退休
- 每日消息!国家卫健委:2023年将创建1000个全国示范性老年友好型社区
- 国际金价或无法坚守1828美元
- 外媒:日本2022年新建公寓均价创新高
- 天天报道:国际金价仍看跌,美国经济强势添新证,FED鹰派必须死磕
- 提质升级,创新服务,2023 IEAE广州电子展描绘电子电器行业新蓝图
- 【全球新视野】美原油交易策略:油价短线下行风险仍存,关注72.24附近支撑
- 常州将出台10条房产新政 拟取消新建商品住房2年的限售期限
- 环球即时看!现货黄金交易策略:金价低位震荡,美联储会议纪要或助力空头
- 陕西韩城爱琴海置业因违规施工被罚
- 前沿资讯!上海浦东新区挂牌一宗商服用地 起价约为4.5亿元
- 神火股份:截止到2月20日收盘,公司股东户数是6.6万
- 华泰车险如何,华泰车险平安车险哪个好