音视频开发集锦ijkplayer秘籍移动端音视频开发

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

2018-12-28  本文已影响270人  markfork

章节

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 封装库 实现了类似边下边播功能,视频可以实现快速预加载、拖拽访问无卡顿。

上一篇下一篇

猜你喜欢

热点阅读