av_write_frame()

打算写两篇文章简单分析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). *<br> *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. *<br> *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);简单解释一下它的参数的含义:s:用于输出的AVFormatContext。pkt:等待输出的AVPacket。函数正常执行后返回值等于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 Encoderif (!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 ptsif ((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 ptsif (pkt->pts != AV_NOPTS_VALUE && pkt->dts == AV_NOPTS_VALUE && delay <= MAX_REORDER_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; i<delay && st->pts_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封装格式如下图所示。

或者在河边放下一盏写着心愿的河灯,祝愿一切安好。

av_write_frame()

相关文章:

你感兴趣的文章:

标签云: