ffmpeg-实现视频 metadata(moov) 前置

作者: markfork | 来源:发表于2018-12-28 14:39 被阅读270次

    章节

    • 问题
    • 分析
    • 解决方案
    • 结果

    1、问题

    1.1 问题描述

    基于原生 ffmpeg api 封装而成的 libvideo_util 库最终经解码->帧上绘图->编码、生成的视频上传至 rgw 之后,出现线上拖拽访问卡顿的问题。

    如下图所示:


    image.png

    左边 为经过 libvideo_util 库处理生成的视频文件,可以看到加载非常慢,且没面加载数据量大约为 4KB左右。
    右边 为经过 ffmpeg 命令行处理过的视频文件,拖拽访问无问题,即可以实现快速预加载。
    命令行如下所示:

    ffmpeg -i bad.mp4 -c copy good.mp4
    

    如果左边的视频推送给用户观看,可想而知当用户拖拽浏览视频时,体验效果是非常差的。

    2、分析

    2.1 是什么导致了上述问题?

    我将问题描述给了导师,他说昨天正好去听了视频播放原理的分享,分享中有提到:视频的存储在文件中的逻辑存储方式是 box 形式存在,这些 box 分类如下(mp4info.exe 工具):

    image.png

    ftyp、free、mdat(视频实质内容)、moov(视频meta 信息)

    分享中阐述到:如果 视频的meta信息存储在视频文件尾部,那么浏览器通过网络传输协议加载远程资源服务器视频流时,浏览器不能迅速得到视频 meta信息(全局信息),这会导致浏览器不能很好的对视频进行预加载处理,所以就会出现拖拽访问卡顿的问题。

    解决办法是在生成目标视频时,将metadata信息(moov)提前至视频第一帧的前面。

    我在网上找到关于 短视频秒开 的优化原理,其中的一段话让我坚定 将 moov 数据提前至视频第一帧是可行的方案。如下图所示:


    image.png

    3、解决方案

    代码片段如下所示:

    3.1 VideoWriter->新增 AVDictionary *dict;

    /// 输出流 视频
    typedef struct {
        AVFormatContext *ofmt_ctx;
        AVPacket avpkt;
        CodecContext *arr_codec_ctx;
        int video_stream;          //视频流的流下标
        int audio_stream;
        uint8_t is_init;
        struct SwsContext *scxt;
        AVDictionary *dict;
        AVFrame *RGBFrame;
        AVFrame *YUVFrame;
        uint8_t *yuv_buff;
        uint8_t *out_buff;
        int outbuff_len;
        int error_code;
        char error_msg[128];
    } VideoWriter;
    

    3.2 打开输出流-> 设置 moov 信息前置

    /// 打开要写入的视频文件
    int VideoWriter_OpenVideo(void *writer, void *reader, const char *target_file,
                              int width, int height) {
        VideoWriter *w = (VideoWriter *) writer;
        VideoReader *r = (VideoReader *) reader;
        AVCodecContext *video_dec_ctx = r->arr_codec_ctx[r->video_stream].ctx;
        if (width <= 0 || height <= 0) {
            width = video_dec_ctx->width;
            height = video_dec_ctx->height;
        }
    
        w->error_code = avformat_alloc_output_context2(&(w->ofmt_ctx), NULL, NULL,
                                                       target_file);
        if (w->error_code < 0) {
            snprintf(w->error_msg, sizeof(w->error_msg),
                     "avformat_alloc_output_context2 fail");
            return -1;
        }
    
        if (_open_output_file(r, w, width, height) < 0) {
            return -1;
        }
    
        //Open output file
        if (!(w->ofmt_ctx->flags & AVFMT_NOFILE)) {
            w->error_code = avio_open(&(w->ofmt_ctx->pb), target_file,
                                      AVIO_FLAG_WRITE);
            if (w->error_code < 0) {
                snprintf(w->error_msg, sizeof(w->error_msg), "avio_open fail");
                return -7;
            }
        }
        // 移动 moov 至第一帧前面
        // _set_mov_moov_ahead(w);
        av_dict_set(&(w->dict),"moveflags","rtphint+faststart",0);
    
        //打印 dict 信息
    //    if (av_dict_count(w->dict) > 0) {
    //        printf("Using muxer settings:");
    //
    //        AVDictionaryEntry *entry = NULL;
    //        while ((entry = av_dict_get(w->dict, "", entry,
    //            AV_DICT_IGNORE_SUFFIX)))
    //            printf("\n\t%s=%s", entry->key, entry->value);
    //
    //        printf("\n");
    //    }
    
        w->error_code = avformat_write_header(w->ofmt_ctx, &(w->dict));
        if (w->error_code < 0) {
            snprintf(w->error_msg, sizeof(w->error_msg),
                     "avformat_write_header fail");
            return -7;
        }
        //AV_PIX_FMT_YUV420P
        w->scxt = sws_getContext(width, height, r->pix_fmt, width, height,
                                 video_dec_ctx->pix_fmt, SWS_POINT, NULL, NULL, NULL);
        if (NULL == w->scxt) {
            w->error_code = -1;
            snprintf(w->error_msg, sizeof(w->error_msg), "sws_getContext fail");
            return -1;
        }
        w->RGBFrame = av_frame_alloc();
        w->YUVFrame = av_frame_alloc();
        w->yuv_buff = (uint8_t *) malloc((width * height * 3));
        w->outbuff_len = (width * height * 3);
        w->out_buff = (uint8_t *) malloc(w->outbuff_len);
        w->is_init = 0;
        return 0;
    }
    
    

    注意:

      av_dict_set(&(w->dict),"moveflags","rtphint+faststart",0);
    

    要放置在 avformat_write_header() 之前

    3.3 调用 avformat_write_header() 函数:

        w->error_code = avformat_write_header(w->ofmt_ctx, &(w->dict));
        if (w->error_code < 0) {
            snprintf(w->error_msg, sizeof(w->error_msg),
                     "avformat_write_header fail");
            return -7;
        }
    

    4、结果

    image.png

    新版的libvideo_util ffmpeg 封装库 实现了类似边下边播功能,视频可以实现快速预加载、拖拽访问无卡顿。

    相关文章

      网友评论

        本文标题:ffmpeg-实现视频 metadata(moov) 前置

        本文链接:https://www.haomeiwen.com/subject/hdbslqtx.html