• FFmpeg源代码简单分析-编码-av_write_frame()


    参考链接

    av_write_frame()

    • av_write_frame()用于输出一帧视音频数据,它的声明位于libavformat\avformat.h,如下所示。
    1. /**
    2. * Write a packet to an output media file.
    3. *
    4. * This function passes the packet directly to the muxer, without any buffering
    5. * or reordering. The caller is responsible for correctly interleaving the
    6. * packets if the format requires it. Callers that want libavformat to handle
    7. * the interleaving should call av_interleaved_write_frame() instead of this
    8. * function.
    9. *
    10. * @param s media file handle
    11. * @param pkt The packet containing the data to be written. Note that unlike
    12. * av_interleaved_write_frame(), this function does not take
    13. * ownership of the packet passed to it (though some muxers may make
    14. * an internal reference to the input packet).
    15. * <br>
    16. * This parameter can be NULL (at any time, not just at the end), in
    17. * order to immediately flush data buffered within the muxer, for
    18. * muxers that buffer up data internally before writing it to the
    19. * output.
    20. * <br>
    21. * Packet's @ref AVPacket.stream_index "stream_index" field must be
    22. * set to the index of the corresponding stream in @ref
    23. * AVFormatContext.streams "s->streams".
    24. * <br>
    25. * The timestamps (@ref AVPacket.pts "pts", @ref AVPacket.dts "dts")
    26. * must be set to correct values in the stream's timebase (unless the
    27. * output format is flagged with the AVFMT_NOTIMESTAMPS flag, then
    28. * they can be set to AV_NOPTS_VALUE).
    29. * The dts for subsequent packets passed to this function must be strictly
    30. * increasing when compared in their respective timebases (unless the
    31. * output format is flagged with the AVFMT_TS_NONSTRICT, then they
    32. * merely have to be nondecreasing). @ref AVPacket.duration
    33. * "duration") should also be set if known.
    34. * @return < 0 on error, = 0 if OK, 1 if flushed and there is no more data to flush
    35. *
    36. * @see av_interleaved_write_frame()
    37. */
    38. int av_write_frame(AVFormatContext *s, AVPacket *pkt);
    • 简单解释一下它的参数的含义:
      • s:用于输出的AVFormatContext。
      • pkt:等待输出的AVPacket。
    • 函数正常执行后返回值等于0。 
    • av_write_frame()的定义位于libavformat\mux.c,如下所示。
    1. int av_write_frame(AVFormatContext *s, AVPacket *in)
    2. {
    3. FFFormatContext *const si = ffformatcontext(s);
    4. AVPacket *pkt = si->parse_pkt;
    5. int ret;
    6. if (!in) {
    7. if (s->oformat->flags & AVFMT_ALLOW_FLUSH) {
    8. ret = s->oformat->write_packet(s, NULL);
    9. flush_if_needed(s);
    10. if (ret >= 0 && s->pb && s->pb->error < 0)
    11. ret = s->pb->error;
    12. return ret;
    13. }
    14. return 1;
    15. }
    16. if (in->flags & AV_PKT_FLAG_UNCODED_FRAME) {
    17. pkt = in;
    18. } else {
    19. /* We don't own in, so we have to make sure not to modify it.
    20. * (ff_write_chained() relies on this fact.)
    21. * The following avoids copying in's data unnecessarily.
    22. * Copying side data is unavoidable as a bitstream filter
    23. * may change it, e.g. free it on errors. */
    24. pkt->data = in->data;
    25. pkt->size = in->size;
    26. ret = av_packet_copy_props(pkt, in);
    27. if (ret < 0)
    28. return ret;
    29. if (in->buf) {
    30. pkt->buf = av_buffer_ref(in->buf);
    31. if (!pkt->buf) {
    32. ret = AVERROR(ENOMEM);
    33. goto fail;
    34. }
    35. }
    36. }
    37. ret = write_packets_common(s, pkt, 0/*non-interleaved*/);
    38. fail:
    39. // Uncoded frames using the noninterleaved codepath are also freed here
    40. av_packet_unref(pkt);
    41. return ret;
    42. }
    • 从源代码可以看出,av_write_frame()主要完成了以下几步工作:版本差异
    • (1)调用check_packet()做一些简单的检测,新版本目前将check_packet放进write_packets_common函数内
    • (2)调用compute_pkt_fields2()设置AVPacket的一些属性值   compute_pkt_fields2函数感觉已被弃用,其功能合并进其余函数
    • (3)调用write_packets_common()写入数据

    • 如果AVPacket中的flag标记中包含AV_PKT_FLAG_UNCODED_FRAME,就会调用AVOutputFormat的write_uncoded_frame()函数(对应上文代码,pkt=in);如果不包含那个标记,就会调用write_packet()函数(执行else里面的内容)。 

     av_write_frame

    1. static int write_packets_common(AVFormatContext *s, AVPacket *pkt, int interleaved)
    2. {
    3. AVStream *st;
    4. FFStream *sti;
    5. int ret = check_packet(s, pkt);
    6. if (ret < 0)
    7. return ret;
    8. st = s->streams[pkt->stream_index];
    9. sti = ffstream(st);
    10. ret = prepare_input_packet(s, st, pkt);
    11. if (ret < 0)
    12. return ret;
    13. ret = check_bitstream(s, sti, pkt);
    14. if (ret < 0)
    15. return ret;
    16. if (sti->bsfc) {
    17. return write_packets_from_bsfs(s, st, pkt, interleaved);
    18. } else {
    19. return write_packet_common(s, st, pkt, interleaved);
    20. }
    21. }

    check_packet()

    • check_packet()定义位于libavformat\mux.c,如下所示。
    • 从代码中可以看出,check_packet()的功能比较简单:
    • 然后检查一下AVPacket的stream_index(标记了该AVPacket所属的AVStream)设置是否正常,如果为负数或者大于AVStream的个数,则返回错误信息;
    • 最后检查AVPacket所属的AVStream是否属于attachment stream,这个地方没见过,目前还没有研究。
    1. static int check_packet(AVFormatContext *s, AVPacket *pkt)
    2. {
    3. if (pkt->stream_index < 0 || pkt->stream_index >= s->nb_streams) {
    4. av_log(s, AV_LOG_ERROR, "Invalid packet stream index: %d\n",
    5. pkt->stream_index);
    6. return AVERROR(EINVAL);
    7. }
    8. if (s->streams[pkt->stream_index]->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
    9. av_log(s, AV_LOG_ERROR, "Received a packet for an attachment stream.\n");
    10. return AVERROR(EINVAL);
    11. }
    12. return 0;
    13. }

    AVOutputFormat->write_packet()

    • write_packet()函数的定义位于libavformat\mux.c,如下所示。
    1. /**
    2. * Write a packet. If AVFMT_ALLOW_FLUSH is set in flags,
    3. * pkt can be NULL in order to flush data buffered in the muxer.
    4. * When flushing, return 0 if there still is more data to flush,
    5. * or 1 if everything was flushed and there is no more buffered
    6. * data.
    7. */
    8. int (*write_packet)(struct AVFormatContext *, AVPacket *pkt);
    1. static int write_packet(AVFormatContext *s1, AVPacket *pkt)
    2. {
    3. const V4L2Context *s = s1->priv_data;
    4. if (write(s->fd, pkt->data, pkt->size) == -1)
    5. return AVERROR(errno);
    6. return 0;
    7. }
    • write_packet()实际上是一个函数指针,指向特定的AVOutputFormat中的实现函数。
    • 例如,我们看一下FLV对应的AVOutputFormat,位于libavformat\flvenc.c,如下所示。
    1. const AVOutputFormat ff_flv_muxer = {
    2. .name = "flv",
    3. .long_name = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    4. .mime_type = "video/x-flv",
    5. .extensions = "flv",
    6. .priv_data_size = sizeof(FLVContext),
    7. .audio_codec = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    8. .video_codec = AV_CODEC_ID_FLV1,
    9. .init = flv_init,
    10. .write_header = flv_write_header,
    11. .write_packet = flv_write_packet,
    12. .write_trailer = flv_write_trailer,
    13. .check_bitstream= flv_check_bitstream,
    14. .codec_tag = (const AVCodecTag* const []) {
    15. flv_video_codec_ids, flv_audio_codec_ids, 0
    16. },
    17. .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
    18. AVFMT_TS_NONSTRICT,
    19. .priv_class = &flv_muxer_class,
    20. };
    • 从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位的数值表示了音频数据格式:
      • 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,格式如下所示。 

     Video Tag Data

    • Video Tag在官方标准中的定义如下

    •  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,格式如下所示

     flv_write_packet()

    • 下面我们看一下FLV格式中write_packet()对应的实现函数flv_write_packet()的定义,位于libavformat\flvenc.c,如下所示。
    1. static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
    2. {
    3. AVIOContext *pb = s->pb;
    4. AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
    5. FLVContext *flv = s->priv_data;
    6. FLVStreamContext *sc = s->streams[pkt->stream_index]->priv_data;
    7. unsigned ts;
    8. int size = pkt->size;
    9. uint8_t *data = NULL;
    10. int flags = -1, flags_size, ret = 0;
    11. int64_t cur_offset = avio_tell(pb);
    12. if (par->codec_type == AVMEDIA_TYPE_AUDIO && !pkt->size) {
    13. av_log(s, AV_LOG_WARNING, "Empty audio Packet\n");
    14. return AVERROR(EINVAL);
    15. }
    16. if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A ||
    17. par->codec_id == AV_CODEC_ID_VP6 || par->codec_id == AV_CODEC_ID_AAC)
    18. flags_size = 2;
    19. else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)
    20. flags_size = 5;
    21. else
    22. flags_size = 1;
    23. if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
    24. || par->codec_id == AV_CODEC_ID_MPEG4) {
    25. size_t side_size;
    26. uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
    27. if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) {
    28. ret = ff_alloc_extradata(par, side_size);
    29. if (ret < 0)
    30. return ret;
    31. memcpy(par->extradata, side, side_size);
    32. flv_write_codec_header(s, par, pkt->dts);
    33. }
    34. }
    35. if (flv->delay == AV_NOPTS_VALUE)
    36. flv->delay = -pkt->dts;
    37. if (pkt->dts < -flv->delay) {
    38. av_log(s, AV_LOG_WARNING,
    39. "Packets are not in the proper order with respect to DTS\n");
    40. return AVERROR(EINVAL);
    41. }
    42. if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    43. if (pkt->pts == AV_NOPTS_VALUE) {
    44. av_log(s, AV_LOG_ERROR, "Packet is missing PTS\n");
    45. return AVERROR(EINVAL);
    46. }
    47. }
    48. ts = pkt->dts;
    49. if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
    50. write_metadata(s, ts);
    51. s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
    52. }
    53. avio_write_marker(pb, av_rescale(ts, AV_TIME_BASE, 1000),
    54. pkt->flags & AV_PKT_FLAG_KEY && (flv->video_par ? par->codec_type == AVMEDIA_TYPE_VIDEO : 1) ? AVIO_DATA_MARKER_SYNC_POINT : AVIO_DATA_MARKER_BOUNDARY_POINT);
    55. switch (par->codec_type) {
    56. case AVMEDIA_TYPE_VIDEO:
    57. avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    58. flags = ff_codec_get_tag(flv_video_codec_ids, par->codec_id);
    59. flags |= pkt->flags & AV_PKT_FLAG_KEY ? FLV_FRAME_KEY : FLV_FRAME_INTER;
    60. break;
    61. case AVMEDIA_TYPE_AUDIO:
    62. flags = get_audio_flags(s, par);
    63. av_assert0(size);
    64. avio_w8(pb, FLV_TAG_TYPE_AUDIO);
    65. break;
    66. case AVMEDIA_TYPE_SUBTITLE:
    67. case AVMEDIA_TYPE_DATA:
    68. avio_w8(pb, FLV_TAG_TYPE_META);
    69. break;
    70. default:
    71. return AVERROR(EINVAL);
    72. }
    73. if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    74. /* check if extradata looks like mp4 formatted */
    75. if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
    76. if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0)
    77. return ret;
    78. } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 &&
    79. (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) {
    80. if (!s->streams[pkt->stream_index]->nb_frames) {
    81. av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: "
    82. "use the audio bitstream filter 'aac_adtstoasc' to fix it "
    83. "('-bsf:a aac_adtstoasc' option with ffmpeg)\n");
    84. return AVERROR_INVALIDDATA;
    85. }
    86. av_log(s, AV_LOG_WARNING, "aac bitstream error\n");
    87. }
    88. /* check Speex packet duration */
    89. if (par->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160)
    90. av_log(s, AV_LOG_WARNING, "Warning: Speex stream has more than "
    91. "8 frames per packet. Adobe Flash "
    92. "Player cannot handle this!\n");
    93. if (sc->last_ts < ts)
    94. sc->last_ts = ts;
    95. if (size + flags_size >= 1<<24) {
    96. av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n",
    97. size + flags_size, 1<<24);
    98. ret = AVERROR(EINVAL);
    99. goto fail;
    100. }
    101. avio_wb24(pb, size + flags_size);
    102. put_timestamp(pb, ts);
    103. avio_wb24(pb, flv->reserved);
    104. if (par->codec_type == AVMEDIA_TYPE_DATA ||
    105. par->codec_type == AVMEDIA_TYPE_SUBTITLE ) {
    106. int data_size;
    107. int64_t metadata_size_pos = avio_tell(pb);
    108. if (par->codec_id == AV_CODEC_ID_TEXT) {
    109. // legacy FFmpeg magic?
    110. avio_w8(pb, AMF_DATA_TYPE_STRING);
    111. put_amf_string(pb, "onTextData");
    112. avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY);
    113. avio_wb32(pb, 2);
    114. put_amf_string(pb, "type");
    115. avio_w8(pb, AMF_DATA_TYPE_STRING);
    116. put_amf_string(pb, "Text");
    117. put_amf_string(pb, "text");
    118. avio_w8(pb, AMF_DATA_TYPE_STRING);
    119. put_amf_string(pb, pkt->data);
    120. put_amf_string(pb, "");
    121. avio_w8(pb, AMF_END_OF_OBJECT);
    122. } else {
    123. // just pass the metadata through
    124. avio_write(pb, data ? data : pkt->data, size);
    125. }
    126. /* write total size of tag */
    127. data_size = avio_tell(pb) - metadata_size_pos;
    128. avio_seek(pb, metadata_size_pos - 10, SEEK_SET);
    129. avio_wb24(pb, data_size);
    130. avio_seek(pb, data_size + 10 - 3, SEEK_CUR);
    131. avio_wb32(pb, data_size + 11);
    132. } else {
    133. av_assert1(flags>=0);
    134. avio_w8(pb,flags);
    135. if (par->codec_id == AV_CODEC_ID_VP6)
    136. avio_w8(pb,0);
    137. if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A) {
    138. if (par->extradata_size)
    139. avio_w8(pb, par->extradata[0]);
    140. else
    141. avio_w8(pb, ((FFALIGN(par->width, 16) - par->width) << 4) |
    142. (FFALIGN(par->height, 16) - par->height));
    143. } else if (par->codec_id == AV_CODEC_ID_AAC)
    144. avio_w8(pb, 1); // AAC raw
    145. else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
    146. avio_w8(pb, 1); // AVC NALU
    147. avio_wb24(pb, pkt->pts - pkt->dts);
    148. }
    149. avio_write(pb, data ? data : pkt->data, size);
    150. avio_wb32(pb, size + flags_size + 11); // previous tag size
    151. flv->duration = FFMAX(flv->duration,
    152. pkt->pts + flv->delay + pkt->duration);
    153. }
    154. if (flv->flags & FLV_ADD_KEYFRAME_INDEX) {
    155. switch (par->codec_type) {
    156. case AVMEDIA_TYPE_VIDEO:
    157. flv->videosize += (avio_tell(pb) - cur_offset);
    158. flv->lasttimestamp = flv->acurframeindex / flv->framerate;
    159. flv->acurframeindex++;
    160. if (pkt->flags & AV_PKT_FLAG_KEY) {
    161. double ts = flv->lasttimestamp;
    162. int64_t pos = cur_offset;
    163. flv->lastkeyframetimestamp = ts;
    164. flv->lastkeyframelocation = pos;
    165. ret = flv_append_keyframe_info(s, flv, ts, pos);
    166. if (ret < 0)
    167. goto fail;
    168. }
    169. break;
    170. case AVMEDIA_TYPE_AUDIO:
    171. flv->audiosize += (avio_tell(pb) - cur_offset);
    172. break;
    173. default:
    174. av_log(s, AV_LOG_WARNING, "par->codec_type is type = [%d]\n", par->codec_type);
    175. break;
    176. }
    177. }
    178. fail:
    179. av_free(data);
    180. return ret;
    181. }
    • 我们通过源代码简单梳理一下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):
    1. //Tag Header - Datasize
    2. avio_wb24(pb, size + flags_size);
    3. //Tag Header - Timestamp 无定义
    4. avio_wb24(pb, ts & 0xFFFFFF);
    5. avio_w8(pb, (ts >> 24) & 0x7F); // timestamps are 32 bits _signed_
    6. //StreamID
    7. 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:
    1. if (enc->codec_id == AV_CODEC_ID_VP6F || enc->codec_id == AV_CODEC_ID_VP6A) {
    2. if (enc->extradata_size)
    3. avio_w8(pb, enc->extradata[0]);
    4. else
    5. avio_w8(pb, ((FFALIGN(enc->width, 16) - enc->width) << 4) |
    6. (FFALIGN(enc->height, 16) - enc->height));
    7. } else if (enc->codec_id == AV_CODEC_ID_AAC)
    8. avio_w8(pb, 1); // AAC raw
    9. else if (enc->codec_id == AV_CODEC_ID_H264 || enc->codec_id == AV_CODEC_ID_MPEG4) {
    10. //AVCVIDEOPACKET-AVCPacketType
    11. avio_w8(pb, 1); // AVC NALU
    12. //AVCVIDEOPACKET-CompositionTime
    13. avio_wb24(pb, pkt->pts - pkt->dts);
    14. }
    • (5)写入数据:avio_write(pb, data ? data : pkt->data, size);  //Data
    • (6)  写入previous tag size:avio_wb32(pb, size + flags_size + 11); // previous tag size
    • 至此,flv_write_packet()就完成了一个Tag的写入。
  • 相关阅读:
    Spring Boot 集成 Redis 配置 MyBatis 二级缓存
    【Java】面向对象程序设计 基础语法笔记
    Additional Features for Scripting
    Java工程师常见面试题集锦
    如何快速安装JDK 1.8 以及配置环境变量
    如何在 Ubuntu 上部署 ONLYOFFICE 协作空间社区版?
    【AWS系列】第七讲: AWS Serverless之API Gateway
    自动化安装脚本(Ansible+shell)
    final关键字
    框架之SpringBoot基础(一)
  • 原文地址:https://blog.csdn.net/CHYabc123456hh/article/details/125411155