文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Qt音视频开发之视频文件保存功能的实现

2022-12-08 20:55

关注

一、前言

和音频存储类似,视频的存储也对应三种格式,视频最原始的数据是yuv(音频对应pcm),视频压缩后的数据是h264(音频对应aac),由于很多播放器或者早期的播放器不支持直接播放h264文件,所以需要用编码器编码成mp4格式,这块就需要用到ffmpeg里面一整套的编码流程,对yuv数据进行编码成MP4格式存储。

在经过对各种视频文件或者视频流保存的过程中,发现rtsp这类的视频流可以直接编码打包存储,不需要经过 avcodec_send_frame avcodec_receive_packet 这两个步骤对每个包编码,这样可以极大的降低CPU占用,猜测可能是rtsp视频流收到的数据包packet就已经是标准的h264裸流带了各种pps啥的。所以视频的监控领域如果要同时存储16路32路视频,采用这个策略是最稳妥的,相当于一直写文件。很多人会觉得编码流程繁琐,其实只要静下心来,挨个测试,把流程搞懂,基本上都是水到渠成的事情。包括之前遇到的保存的文件鼠标右键属性中看不到分辨率等参数信息,原来是调用写入文件头 avformat_write_header 写入的时机不对,一定要在打开打开视频编码器 avcodec_open2 以及打开输出文件 avio_open 以后再写入。

编码保存的大致流程:

二、效果图

三、体验地址

国内站点:https://gitee.com/feiyangqingyun

国际站点:https://github.com/feiyangqingyun

体验地址:https://pan.baidu.com/s/1bNGaPFZc7UovQI57UIQSbw 提取码:xk9a 文件名:bin_video_demo/bin_linux_video。

四、相关代码

bool FFmpegSave::initVideoH264()
{
    //查找视频编码器(如果源头是H265则采用HEVC作为编码器)
    AVCodecID codecID = FFmpegHelper::getCodecID(videoStreamIn);
    if (codecID == AV_CODEC_ID_HEVC) {
        videoCodec = avcodec_find_encoder(AV_CODEC_ID_HEVC);
    } else {
        videoCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    }

    if (!videoCodec) {
        debug("编码失败", QString("错误: 查找视频编码器失败"));
        return false;
    }

    //创建视频编码器上下文
    videoCodecCtx = avcodec_alloc_context3(videoCodec);
    if (!videoCodecCtx) {
        debug("编码失败", QString("错误: 创建视频编码器上下文失败"));
        return false;
    }

    //为了兼容低版本的编译器推荐选择第一种方式
#if 1
    //放大系数是为了小数位能够正确放大到整型
    int ratio = 10000;
    videoCodecCtx->time_base.num = 1 * ratio;
    videoCodecCtx->time_base.den = frameRate * ratio;
    videoCodecCtx->framerate.num = frameRate * ratio;
    videoCodecCtx->framerate.den = 1 * ratio;
#elif 0
    videoCodecCtx->time_base = {1, frameRate};
    videoCodecCtx->framerate = {frameRate, 1};
#else
    videoCodecCtx->time_base = videoStreamIn->codec->time_base;
    videoCodecCtx->framerate = videoStreamIn->codec->framerate;
#endif

#if 0
    videoCodecCtx->qmin = 10;
    videoCodecCtx->qmax = 51;
    videoCodecCtx->me_range = 16;
    videoCodecCtx->max_qdiff = 4;
    videoCodecCtx->qcompress = 0.6;
#endif

    //初始化视频编码器参数(如果要文件体积小一些画质差一些可以调整码率)
    //参数说明 https://blog.csdn.net/qq_40179458/article/details/110449653
    videoCodecCtx->bit_rate = FFmpegHelper::getBitRate(videoWidth, videoHeight);
    videoCodecCtx->width = videoWidth;
    videoCodecCtx->height = videoHeight;
    videoCodecCtx->gop_size = 25;
    videoCodecCtx->max_b_frames = 3;
    videoCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    videoCodecCtx->level = 50;
    videoCodecCtx->profile = FF_PROFILE_H264_MAIN;

    //加上下面这个才能在文件属性中看到分辨率等信息 https://www.cnblogs.com/lidabo/p/15754031.html
    if (saveVideoType == SaveVideoType_Mp4) {
        videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    //加载预设 https://blog.csdn.net/JineD/article/details/125304570
    if (videoCodecCtx->codec_id == AV_CODEC_ID_H264) {
        av_opt_set(videoCodecCtx->priv_data, "preset", "slow", 0);
        //设置零延迟(本地摄像头视频流保存如果不设置则播放的时候会越来越模糊)
        av_opt_set(videoCodecCtx->priv_data, "tune", "zerolatency", 0);
    } else if (videoCodecCtx->codec_id == AV_CODEC_ID_HEVC) {
        av_opt_set(videoCodecCtx->priv_data, "x265-params", "qp=20", 0);
        av_opt_set(videoCodecCtx->priv_data, "preset", "ultrafast", 0);
        av_opt_set(videoCodecCtx->priv_data, "tune", "zero-latency", 0);
    }

    //打开视频编码器
    int result = avcodec_open2(videoCodecCtx, videoCodec, NULL);
    if (result < 0) {
        debug("打开编码", QString("错误: 打开视频编码器失败 %1").arg(FFmpegHelper::getError(result)));
        return false;
    }

    videoPacket = FFmpegHelper::creatPacket(NULL);
    return true;
}

bool FFmpegSave::initVideoMp4()
{
    //必须先设置过输入视频流
    if (!videoStreamIn || fileName.isEmpty()) {
        return false;
    }

    //有部分视频参数不正确保存不了 http://tv.netxt.cc:1998/live/y.flv
    if (videoStreamIn->time_base.num == 0) {
        return false;
    }

    QByteArray fileData = fileName.toUtf8();
    const char *filename = fileData.data();

    //开辟一个格式上下文用来处理视频流输出
    int result = avformat_alloc_output_context2(&formatCtx, NULL, "mp4", filename);
    if (result < 0) {
        debug("创建格式", QString("错误: %1").arg(FFmpegHelper::getError(result)));
        return false;
    }

    //创建视频流用来输出视频数据到文件
    videoStreamOut = avformat_new_stream(formatCtx, NULL);
    result = FFmpegHelper::copyContext(videoCodecCtx, videoStreamOut, true);
    if (result < 0) {
        debug("创建视频", QString("错误: %1").arg(FFmpegHelper::getError(result)));
        goto end;
    }

    //打开输出文件
    result = avio_open(&formatCtx->pb, filename, AVIO_FLAG_WRITE);
    if (result < 0) {
        debug("打开输出", QString("错误: %1").arg(FFmpegHelper::getError(result)));
        goto end;
    }

    //写入文件开始符
    result = avformat_write_header(formatCtx, NULL);
    if (result < 0) {
        debug("写入失败", QString("错误: %1").arg(FFmpegHelper::getError(result)));
        goto end;
    }

    return true;

end:
    //关闭释放并清理文件
    this->close();
    this->deleteFile(fileName);
    return false;
}

void FFmpegSave::writePacket(AVPacket *packet)
{
    packetCount++;
    if (saveVideoType == SaveVideoType_H264) {
        file.write((char *)packet->data, packet->size);
    } else if (saveVideoType == SaveVideoType_Mp4) {
        AVRational timeBaseIn = videoStreamIn->time_base;
        AVRational timeBaseOut = videoStreamOut->time_base;

        //没有下面这段判断在遇到不连续的帧的时候就会错位(相当于每次重新计算时间基准保证时间正确)
        //不连续帧的情况有暂停录制以及切换播放进度导致中间有些帧不需要录制
        double fps = frameRate;//av_q2d(videoStreamIn->r_frame_rate);
        double duration = AV_TIME_BASE / fps;
        packet->pts = (packetCount * duration) / (av_q2d(timeBaseIn) * AV_TIME_BASE);
        packet->dts = packet->pts;
        packet->duration = duration / (av_q2d(timeBaseIn) * AV_TIME_BASE);

        //重新调整时间基准
        packet->pts = av_rescale_q_rnd(packet->pts, timeBaseIn, timeBaseOut, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        packet->dts = packet->pts;
        //packet->dts = av_rescale_q_rnd(packet->dts, timeBaseIn, timeBaseOut, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        packet->duration = av_rescale_q(packet->duration, timeBaseIn, timeBaseOut);
        packet->pos = -1;

        //写入一帧数据
        int result = av_interleaved_write_frame(formatCtx, packet);
        if (result < 0) {
            debug("写入失败", QString("错误: %1").arg(FFmpegHelper::getError(result)));
        }
    }

    av_packet_unref(packet);
}

五、功能特点

5.1 基础功能

5.2 特色功能

5.3 视频控件

5.4 内核ffmpeg

到此这篇关于Qt音视频开发之视频文件保存功能的实现的文章就介绍到这了,更多相关Qt视频文件保存内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯