FFmpeg源代码简单分析 av write frame

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.****.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

=====================================================

FFmpeg的库函数源代码分析文章列表:

【架构图】

FFmpeg源代码结构图 - 解码

FFmpeg源代码结构图 - 编码

【通用】

FFmpeg 源代码简单分析:av_register_all()

FFmpeg 源代码简单分析:avcodec_register_all()

FFmpeg 源代码简单分析:内存的分配和释放(av_malloc()av_free()等)

FFmpeg 源代码简单分析:常见结构体的初始化和销毁(AVFormatContextAVFrame等)

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源代码简单分析:libswscalesws_getContext()

FFmpeg源代码简单分析:libswscalesws_scale()

FFmpeg源代码简单分析:libavdeviceavdevice_register_all()

FFmpeg源代码简单分析:libavdevicegdigrab

【脚本】

FFmpeg源代码简单分析:makefile

FFmpeg源代码简单分析:configure

【H.264】

FFmpegH.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). *            <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()的调用关系如下图所示。
FFmpeg源代码简单分析 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 <= 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封装格式如下图所示。

PS:原图是网上找的,感觉画的很清晰,比官方的Video File Format Specification更加通俗易懂。但是图中有一个错误,就是TagHeader中的StreamID字段的长度写错了(查看了一下官方标准,应该是3字节,现在已经改过来了)。

FFmpeg源代码简单分析 av write frame

从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在官方标准中定义如下。
 FFmpeg源代码简单分析 av write frame
Audio Tag开始的第1个字节包含了音频数据的参数信息,从第2个字节开始为音频流数据。
第1个字节的前4位的数值表示了音频数据格式:
0 = Linear PCM, platform endian
1 = ADPCM
2 = MP3
3 = Linear PCM, little endian
4 = Nellymoser 16-kHz mono
5 = Nellymoser 8-kHz mono
6 = Nellymoser
7 = G.711 A-law logarithmic PCM
8 = G.711 mu-law logarithmic PCM
9 = reserved
10 = AAC
14 = MP3 8-Khz
15 = Device-specific sound
第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,格式如下所示。
 FFmpeg源代码简单分析 av write frame

Video Tag Data

Video Tag在官方标准中的定义如下。
FFmpeg源代码简单分析 av write frame
Video Tag也用开始的第1个字节包含视频数据的参数信息,从第2个字节为视频流数据。
第1个字节的前4位的数值表示帧类型(FrameType):
1: keyframe (for AVC, a seekableframe)(关键帧)
2: inter frame (for AVC, a nonseekableframe)
3: disposable inter frame (H.263only)
4: generated keyframe (reservedfor server use only)
5: video info/command frame
第1个字节的后4位的数值表示视频编码ID(CodecID):
1: JPEG (currently unused)
2: Sorenson H.263
3: Screen video
4: On2 VP6
5: On2 VP6 with alpha channel
6: Screen video version 2
7: AVC
其中,当音频编码为AVC(H.264)的时候,第一个字节后面存储的是AVCVIDEOPACKET,格式如下所示。
 FFmpeg源代码简单分析 av write frame

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 AVERROR(EINVAL);    }    //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);    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的写入。


雷霄骅
[email protected]
http://blog.****.net/leixiaohua1020

           

给我老师的人工智能教程打call!http://blog.****.net/jiangjunshow

FFmpeg源代码简单分析 av write frame
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: FFmpeg源代码简单分析 av write frame

带尺寸的图片: FFmpeg源代码简单分析 av write frame

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block var foo = 'bar'; 

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to-HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ(n)=(n1)!nN\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通过欧拉积分

Γ(z)=0tz1etdt&ThinSpace;. \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

gantt
        dateFormat  YYYY-MM-DD
        title Adding GANTT diagram functionality to mermaid
        section 现有任务
        已完成               :done,    des1, 2014-01-06,2014-01-08
        进行中               :active,  des2, 2014-01-09, 3d
        计划一               :         des3, after des2, 5d
        计划二               :         des4, after des3, 5d
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间,文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎