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


    参考链接:

    av_write_trailer()

    • av_write_trailer()用于输出文件尾,它的声明位于libavformat\avformat.h,如下所示
    1. /**
    2. * Write the stream trailer to an output media file and free the
    3. * file private data.
    4. *
    5. * May only be called after a successful call to avformat_write_header.
    6. *
    7. * @param s media file handle
    8. * @return 0 if OK, AVERROR_xxx on error
    9. */
    10. int av_write_trailer(AVFormatContext *s);
    • 它只需要指定一个参数,即用于输出的AVFormatContext。
    • 函数正常执行后返回值等于0。
    • av_write_trailer()的定义位于libavformat\mux.c,如下所示。
    1. int av_write_trailer(AVFormatContext *s)
    2. {
    3. FFFormatContext *const si = ffformatcontext(s);
    4. AVPacket *const pkt = si->parse_pkt;
    5. int ret1, ret = 0;
    6. for (unsigned i = 0; i < s->nb_streams; i++) {
    7. AVStream *const st = s->streams[i];
    8. FFStream *const sti = ffstream(st);
    9. if (sti->bsfc) {
    10. ret1 = write_packets_from_bsfs(s, st, pkt, 1/*interleaved*/);
    11. if (ret1 < 0)
    12. av_packet_unref(pkt);
    13. if (ret >= 0)
    14. ret = ret1;
    15. }
    16. }
    17. ret1 = interleaved_write_packet(s, pkt, 1, 0);
    18. if (ret >= 0)
    19. ret = ret1;
    20. if (s->oformat->write_trailer) {
    21. if (!(s->oformat->flags & AVFMT_NOFILE) && s->pb)
    22. avio_write_marker(s->pb, AV_NOPTS_VALUE, AVIO_DATA_MARKER_TRAILER);
    23. if (ret >= 0) {
    24. ret = s->oformat->write_trailer(s);
    25. } else {
    26. s->oformat->write_trailer(s);
    27. }
    28. }
    29. deinit_muxer(s);
    30. if (s->pb)
    31. avio_flush(s->pb);
    32. if (ret == 0)
    33. ret = s->pb ? s->pb->error : 0;
    34. for (unsigned i = 0; i < s->nb_streams; i++) {
    35. av_freep(&s->streams[i]->priv_data);
    36. av_freep(&ffstream(s->streams[i])->index_entries);
    37. }
    38. if (s->oformat->priv_class)
    39. av_opt_free(s->priv_data);
    40. av_freep(&s->priv_data);
    41. av_packet_unref(si->pkt);
    42. return ret;
    43. }
    • 从源代码可以看出av_write_trailer()主要完成了以下两步工作:
      • (1)循环调用输出将还未输出的AVPacket。
      • (2)调用AVOutputFormat的write_trailer(),输出文件尾。

    AVOutputFormat->write_trailer()

    • AVOutputFormat的write_trailer()是一个函数指针,指向特定的AVOutputFormat中的实现函数。
    • 我们以FLV对应的AVOutputFormat为例,看一下它的定义,如下所示。
    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. };
    • 从FLV对应的AVOutputFormat结构体的定义我们可以看出,write_trailer()指向了flv_write_trailer()函数

    flv_write_trailer()

    • flv_write_trailer()函数的定义位于libavformat\flvenc.c,如下所示。
    1. static int flv_write_trailer(AVFormatContext *s)
    2. {
    3. int64_t file_size;
    4. AVIOContext *pb = s->pb;
    5. FLVContext *flv = s->priv_data;
    6. int build_keyframes_idx = flv->flags & FLV_ADD_KEYFRAME_INDEX;
    7. int i, res;
    8. int64_t cur_pos = avio_tell(s->pb);
    9. if (build_keyframes_idx) {
    10. FLVFileposition *newflv_posinfo, *p;
    11. avio_seek(pb, flv->videosize_offset, SEEK_SET);
    12. put_amf_double(pb, flv->videosize);
    13. avio_seek(pb, flv->audiosize_offset, SEEK_SET);
    14. put_amf_double(pb, flv->audiosize);
    15. avio_seek(pb, flv->lasttimestamp_offset, SEEK_SET);
    16. put_amf_double(pb, flv->lasttimestamp);
    17. avio_seek(pb, flv->lastkeyframetimestamp_offset, SEEK_SET);
    18. put_amf_double(pb, flv->lastkeyframetimestamp);
    19. avio_seek(pb, flv->lastkeyframelocation_offset, SEEK_SET);
    20. put_amf_double(pb, flv->lastkeyframelocation + flv->keyframe_index_size);
    21. avio_seek(pb, cur_pos, SEEK_SET);
    22. res = shift_data(s);
    23. if (res < 0) {
    24. goto end;
    25. }
    26. avio_seek(pb, flv->keyframes_info_offset, SEEK_SET);
    27. put_amf_string(pb, "filepositions");
    28. put_amf_dword_array(pb, flv->filepositions_count);
    29. for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
    30. put_amf_double(pb, newflv_posinfo->keyframe_position + flv->keyframe_index_size);
    31. }
    32. put_amf_string(pb, "times");
    33. put_amf_dword_array(pb, flv->filepositions_count);
    34. for (newflv_posinfo = flv->head_filepositions; newflv_posinfo; newflv_posinfo = newflv_posinfo->next) {
    35. put_amf_double(pb, newflv_posinfo->keyframe_timestamp);
    36. }
    37. newflv_posinfo = flv->head_filepositions;
    38. while (newflv_posinfo) {
    39. p = newflv_posinfo->next;
    40. if (p) {
    41. newflv_posinfo->next = p->next;
    42. av_free(p);
    43. p = NULL;
    44. } else {
    45. av_free(newflv_posinfo);
    46. newflv_posinfo = NULL;
    47. }
    48. }
    49. put_amf_string(pb, "");
    50. avio_w8(pb, AMF_END_OF_OBJECT);
    51. avio_seek(pb, cur_pos + flv->keyframe_index_size, SEEK_SET);
    52. }
    53. end:
    54. if (flv->flags & FLV_NO_SEQUENCE_END) {
    55. av_log(s, AV_LOG_DEBUG, "FLV no sequence end mode open\n");
    56. } else {
    57. /* Add EOS tag */
    58. for (i = 0; i < s->nb_streams; i++) {
    59. AVCodecParameters *par = s->streams[i]->codecpar;
    60. FLVStreamContext *sc = s->streams[i]->priv_data;
    61. if (par->codec_type == AVMEDIA_TYPE_VIDEO &&
    62. (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4))
    63. put_avc_eos_tag(pb, sc->last_ts);
    64. }
    65. }
    66. file_size = avio_tell(pb);
    67. if (build_keyframes_idx) {
    68. flv->datasize = file_size - flv->datastart_offset;
    69. avio_seek(pb, flv->datasize_offset, SEEK_SET);
    70. put_amf_double(pb, flv->datasize);
    71. }
    72. if (!(flv->flags & FLV_NO_METADATA)) {
    73. if (!(flv->flags & FLV_NO_DURATION_FILESIZE)) {
    74. /* update information */
    75. if (avio_seek(pb, flv->duration_offset, SEEK_SET) < 0) {
    76. av_log(s, AV_LOG_WARNING, "Failed to update header with correct duration.\n");
    77. } else {
    78. put_amf_double(pb, flv->duration / (double)1000);
    79. }
    80. if (avio_seek(pb, flv->filesize_offset, SEEK_SET) < 0) {
    81. av_log(s, AV_LOG_WARNING, "Failed to update header with correct filesize.\n");
    82. } else {
    83. put_amf_double(pb, file_size);
    84. }
    85. }
    86. }
    87. return 0;
    88. }
    • 从flv_write_trailer()的源代码可以看出该函数做了以下两步工作:  位于end:之后的两段代码
      • (1)如果视频流是H.264,则添加包含EOS(End Of Stream) NALU的Tag。
      • (2)更新FLV的时长信息,以及文件大小信息。
    • 其中,put_avc_eos_tag()函数用于添加包含EOS NALU的Tag(包含结尾的一个PreviousTagSize),如下所示。
    1. static void put_avc_eos_tag(AVIOContext *pb, unsigned ts)
    2. {
    3. avio_w8(pb, FLV_TAG_TYPE_VIDEO);
    4. avio_wb24(pb, 5); /* Tag Data Size */
    5. put_timestamp(pb, ts);
    6. avio_wb24(pb, 0); /* StreamId = 0 */
    7. avio_w8(pb, 23); /* ub[4] FrameType = 1, ub[4] CodecId = 7 */
    8. avio_w8(pb, 2); /* AVC end of sequence */
    9. avio_wb24(pb, 0); /* Always 0 for AVC EOS. */
    10. avio_wb32(pb, 16); /* Size of FLV tag */
    11. }
    • 可以参考FLV封装格式理解上述函数。AVCVIDEOPACKET的格式,如下所示

    • 可以看出包含EOS NALU的AVCVIDEOPACKET的AVCPacketType为2。
    • 在这种情况下,AVCVIDEOPACKET的CompositionTime字段取0,并且无需包含Data字段。 
  • 相关阅读:
    java基础知识点总结篇一
    CSS特效013:背景色彩不停流动效果
    高数:第二章:一元函数微分学
    springboot项目部署 + vue项目部署
    数据结构——图
    海尔智家:“超预期”成为“新常态”
    教你在批量将视频逆时针旋转90度的同时添加马赛克
    三十一、《大数据项目实战之用户行为分析》Spark SQL与Hive整合
    前端如何实现精准的倒计时(排除误差、时间偏差)
    视频定格合璧,批量剪辑轻松插入图片
  • 原文地址:https://blog.csdn.net/CHYabc123456hh/article/details/125414721