FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频

FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频

文章目录

  • FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频
  • 1、前言
    • 1.1 目标
    • 1.2 一些说明
  • 2、效果
  • 3、代码
    • 3.1 思路
    • 3.2 工程目录
    • 3.3 核心代码
  • 4、全部代码获取


1、前言

  本文通过FFMPEG(7.0.2)与Qt(5.13.2)实现在windows10系统下,实时预览以及录制1080p视频。
  本文程序只对视频数据进行处理,不考虑音频数据。

1.1 目标

  在win10平台,用FFMPEG和Qt实现实时显示USB摄像头画面以及同步录制mp4视频。

1.2 一些说明

  本程序实现USB摄像头数据视频流获取及显示,录制、显示的视频质量与USB相机有关,本人摄像头为1920*1080@30Hz。
  USB相机默认视频流格式会有差别,如果需要特定格式及分辨率的时候,需要手动设置。
  大部分USB摄像头有MJPG和YUV两种格式,为了追求高分辨率,可以将摄像头参数设置为MJPG输入。需要通过以下代码实现。即配置AVDictionary。需要同时配置相机的帧率、分辨率和格式,不能只设置输入格式!要不然不成功。

AVDictionary* options = NULL;
av_dict_set(&options, "input_format", "mjpeg", 0);
av_dict_set(&options, "framerate", "30", 0);
av_dict_set(&options, "video_size", "1920x1080", 0);

avformat_open_input(&pFormatCtx_, in_file.c_str(), ifmt, &options);

2、效果

  先看演示效果视频:

FFMPEG+Qt win10 实时预览录制1080p视频

3、代码

  本项目全部代码请到此处获取:https://download.csdn.net/download/wang_chao118/89921908

3.1 思路

  通过Qt进行画面可视化,FFMPEG对USB视频流进行解码、编码以及存储。将FFMPEG的循环操作放到一个子线程中,与现实线程隔离。
  通过Qt的信号槽机制,将FFMPEG循环操作过程中解码出的单帧图像转化成QImage*通过信号传递至主线程进行图像绘制。

thread_ = new std::thread(&CameraThread::Run, this);
QObject::connect(camera_thread_, &CameraThread::frameReady, &w, &Widget::updatePic);

3.2 工程目录

  本项目中将FFMPEG相关上下文的初始化、解码、编码、记录循环功能集成在一个CameraThread类中。
  CameraThread类继承Thread类。Thread类中实现子线程的初始化、开始、停止、运行等基础功能。
在这里插入图片描述

3.3 核心代码

  CameraThread::Start()函数用于初始化FFMPEG的各类上下文,设置解码器、编码器参数等,在Qt界面中只要点击“开始录制”按钮就会调用该函数,点击“停止录制”按钮调用CameraThread::Stop()函数,对上下文进行清理。

int CameraThread::Start()
{
    start_pts = 0;
    int ret = 0;

    /******************************************打开摄像头设备********************************************/
    const AVInputFormat* m_inputFormat = av_find_input_format("dshow");
    inputContext = avformat_alloc_context();
    AVDictionary *options = nullptr;
    
    ret = avformat_open_input(&inputContext, url_.c_str(), m_inputFormat, &options);

    if (ret < 0){
        qDebug()<<"avformat_open_input failed, ret: "<<ret;
        return -1;
    }

    // 获取摄像头流信息
    ret = avformat_find_stream_info(inputContext, nullptr);
    if (ret < 0){
        qDebug()<<"Could not retrieve input stream information";
        return -1;
    }

    int videoStreamId = -1;
    videoStreamId = av_find_best_stream(inputContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, NULL);
    inputStream = inputContext->streams[videoStreamId];
    codecParameters = inputStream->codecpar;


    // 获取摄像头的实际分辨率和帧率
    int actual_width = codecParameters->width;
    int actual_height = codecParameters->height;
    AVRational actual_frame_rate = inputStream->r_frame_rate;

    qDebug()<< "Camera Resolution: " << actual_width << "x" << actual_height;
    qDebug()<< "Camera Frame Rate: " << actual_frame_rate.num << "/" << actual_frame_rate.den << " fps";

    /******************************************创建解码器********************************************/
    // 查找解码器
    auto codec_id = inputStream->codecpar->codec_id;
    const AVCodec* decoder = avcodec_find_decoder(codec_id);
    if (!decoder) {
        qDebug()<<"Unsupported codec !";
        return -1;
    }

    // 创建解码器上下文
    decoderContext = avcodec_alloc_context3(decoder);
    ret = avcodec_parameters_to_context(decoderContext, codecParameters);
    if (ret<0) {
        qDebug()<<"Failed to copy codec parameters to decoder context.";
        return -1;
    }
    qDebug() << " output pix_fmt=" << av_get_pix_fmt_name((AVPixelFormat)codecParameters->format) <<" "<< codecParameters->format;
    ret = avcodec_open2(decoderContext, decoder, nullptr);
    if (ret<0) {
        qDebug()<<"Failed to open decoder.";
        return -1;
    }

    /******************************************创建编码器********************************************/
    // 查找 H.264 编码器
    const AVCodec* pEncoderH264 = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (pEncoderH264 == NULL) {
        qDebug() << "Unsupported encodec.";
        return -1;
    }

    //视频编码器上下文
    encoderContext = avcodec_alloc_context3(pEncoderH264);
    encoderContext->time_base.num = inputStream->time_base.num;
    encoderContext->time_base.den = inputStream->time_base.den;
    encoderContext->has_b_frames = 0;
//    encoderContext->gop_size = 50;
    encoderContext->codec_id = pEncoderH264->id;
    encoderContext->pix_fmt = (AVPixelFormat)inputStream->codecpar->format;
    qDebug()<<"111111111111111111111111111111: "<<encoderContext->pix_fmt;
    encoderContext->width = inputStream->codecpar->width;
    encoderContext->height = inputStream->codecpar->height;
//    encoderContext->bit_rate = 0;
    encoderContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    encoderContext->framerate = inputStream->avg_frame_rate;//编码帧率按照采集帧率来
//    encoderContext->bit_rate = 5000000;               //数值越小文件越小
    //编码器不用等待缓冲区填满,接收到数据即开始编码
    av_opt_set(encoderContext->priv_data, "tune", "zerolatency", 0);
//    av_opt_set(encoderContext->priv_data, "crf", "23", 0);               //数值越大越模糊,存储文件越小

    qDebug() << "encoder h246 information:";
    qDebug() << " encode fps=" << (encoderContext->framerate.num / encoderContext->framerate.den) ;
    qDebug() << " w=" << encoderContext->width << ", h=" << encoderContext->height;
    qDebug() << " input pix format=" << av_get_pix_fmt_name(encoderContext->pix_fmt) <<" "<< encoderContext->pix_fmt ;
    ret = avcodec_open2(encoderContext, pEncoderH264, NULL);
    if (ret < 0) {
        qDebug() <<"avcodec_open2";
        return -1;
    }

    /******************************************创建视频输出文件********************************************/
    const AVOutputFormat* outputFormat = av_guess_format("mp4", NULL, NULL);
    outputContext = avformat_alloc_context();
    outputContext->oformat = const_cast<AVOutputFormat*>(outputFormat);
    // 创建输出流
    outputStream = avformat_new_stream(outputContext, encoderContext->codec);
    if (!outputStream){
        qDebug() <<"Failed to create output stream";
        return -1;
    }

    avcodec_parameters_from_context(outputStream->codecpar, encoderContext);
    outputStream->time_base = av_inv_q(inputStream->r_frame_rate);
    // 创建输出 MP4 文件
    mp4_count++;
    m_mp4_file_name = m_mp4_file_name_header + std::to_string(mp4_count) + ".mp4";
    ret = avio_open(&outputContext->pb, m_mp4_file_name.c_str(), AVIO_FLAG_WRITE);
    if(ret <0)
    {
        qDebug() <<"Failed to create output file.";
        return -1;
    }
    ret = avformat_write_header(outputContext, NULL);
    if(ret <0)
    {
        qDebug() <<"Failed to write header";
        return -1;
    }

    /******************************************开线程****************************************************/
    thread_ = new std::thread(&CameraThread::Run, this);
    if (!thread_)
    {
        qDebug()<<"new std::thread(&CameraThread::Run, this) failed";
        return -1;
    }
    return 0;
}

  CameraThread::Run()函数是循环录制过程的执行函数,主要是从FFMPEG上下文中取出视频数据包AVPacket,解码至视频帧AVFrame,再按相应格式(H264)编码成AVPacket,然后再保存至文件中。

void CameraThread::Run()
{
    qDebug() << "Run into CameraThread::Run()!";
    int ret = 0;
    SwsContext *sws_ctx = nullptr;

    while (!abort_)
    {
        // 读取视频帧
        ret = av_read_frame(inputContext, captured_packet);
        if (ret < 0)
        {
            qDebug() << "av_read_frame error";
            continue; // 如果读取失败,跳过到下一帧
        }
        // start capture pts from 0
        // 初始化 start_pts
        if (start_pts == 0)
        {
            start_pts = captured_packet->pts; // 仅在第一次读取时设置
        }

        captured_packet->pts -= start_pts;
        qDebug()<<"captured_packet->pts: "<<captured_packet->pts;

        av_log(nullptr, AV_LOG_INFO, "packet size is %d\n", captured_packet->size);

        // 解码 MJPEG 数据
        ret = avcodec_send_packet(decoderContext, captured_packet);
        if (ret < 0)
        {
            qDebug() << "Error sending packet for decoding.";
            av_packet_unref(captured_packet); // 释放 packet 内存
            continue; // 继续读取下一个包
        }

        auto decoded_frame = av_frame_alloc();
        ret = avcodec_receive_frame(decoderContext, decoded_frame); // decoded_frame 自带引用计数

        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        {
            av_frame_free(&decoded_frame); // 确保在这些情况下也释放内存
            av_packet_unref(captured_packet); // 释放 packet 内存
            continue;
        }
        else if (ret < 0)
        {
            qDebug() << "avcodec_receive_frame failed!";
            av_frame_free(&decoded_frame);
            av_packet_unref(captured_packet); // 释放 packet 内存
            break;
        }

        // 编码 H264 数据
        ret = avcodec_send_frame(encoderContext, decoded_frame);
        if (ret < 0)
        {
            qDebug() << "Error sending frame for encoding.";
            av_frame_free(&decoded_frame);
            av_packet_unref(captured_packet); // 释放 packet 内存
            continue;
        }


        /******************************************发送QImage***********************************************/

        // 转换解码后的帧为 QImage
        if (!sws_ctx)
        {
            sws_ctx = sws_getContext(decoded_frame->width, decoded_frame->height,
                                     (AVPixelFormat)decoded_frame->format,
                                     decoded_frame->width, decoded_frame->height,
                                     AV_PIX_FMT_RGB32, SWS_BILINEAR, nullptr, nullptr, nullptr);
        }

        QImage image(decoded_frame->width, decoded_frame->height, QImage::Format_RGB32);
        uint8_t *dst[4] = { image.bits(), nullptr, nullptr, nullptr };
        int dstStride[4] = { image.bytesPerLine(), 0, 0, 0 };

        sws_scale(sws_ctx, decoded_frame->data, decoded_frame->linesize, 0,
                  decoded_frame->height, dst, dstStride);

        // 发射信号
        emit frameReady(image);


        /******************************************存视频****************************************************/

        ret = avcodec_receive_packet(encoderContext, h264_pkt);
        if (ret < 0)
        {
            qDebug() << "avcodec_receive_packet error.";
            av_frame_free(&decoded_frame);
            av_packet_unref(captured_packet); // 释放 packet 内存
            continue;
        }

        ret = av_write_frame(outputContext, h264_pkt);
        if (ret < 0)
        {
            qDebug() << "write error.";
        }

        // 释放 decoded_frame 和 packet 内存
        av_frame_free(&decoded_frame);
        av_packet_unref(captured_packet); // 释放 packet 内存
    }
}

4、全部代码获取

  本项目全部代码请到此处获取:https://download.csdn.net/download/wang_chao118/89921908

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/901014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

多线程初阶(七):单例模式指令重排序

目录 1. 单例模式 1.1 饿汉模式 1.2 懒汉模式 2. 懒汉模式下的问题 2.1 线程安全问题 2.2 如何解决 --- 加锁 2.3 加锁引入的新问题 --- 性能问题 2.4 指令重排序问题 2.4.1 指令重排序 2.4.2 指令重排序引发的问题 1. 单例模式 单例模式, 是设计模式中最典型的一种模…

【ArcGIS微课1000例】0125:ArcGIS矢量化无法自动完成面解决方案

文章目录 一、坐标系统问题二、正确使用自动完成面工具一、坐标系统问题 1. 数据库坐标系 arcgis矢量化的过程中,无法自动完成面,可能是因为图层要素没有坐标系造成的。双击数据库打开数据库属性,可以查看当前数据框的坐标系。 2. 图层坐标系 双击图层,打开图层属性,切…

Safari 中 filter: blur() 高斯模糊引发的性能问题及解决方案

目录 引言问题背景&#xff1a;filter: blur() 引发的问题产生问题的原因分析解决方案&#xff1a;开启硬件加速实际应用示例性能优化建议常见的调试工具与分析方法 引言 在前端开发中&#xff0c;CSS滤镜&#xff08;如filter: blur()&#xff09;的广泛使用为页面带来了各种…

使用query-string库出现错误Module parse failed: Unexpected token

环境 node v12query-string 9.1.0 报错信息 Failed to compile../node_modules/query-string/base.js 350:14 Module parse failed: Unexpected token (350:14) File was processed with these loaders:* ./node_modules/babel-loader/lib/index.js You may need an additio…

正则表达式和通配符

文章目录 正则表达式和通配符的区别正则表达式&#xff08;Regex&#xff09;通配符&#xff08;Wildcards&#xff09;总结 正则表达式的概念正则表达式的由来为什么要使用正则表达式 正则表达式的语法组成修饰符元字符\f\b\B 在Linux中的基础正则和扩展正则基础正则(BRE)^$.*…

【南方科技大学】CS315 Computer Security 【Lab6 IoT Security and Wireless Exploitation】

目录 Introduction (Part 1: OS Security for IoT )Software RequirementsStarting the Lab 6 Virtual MachineSetting up the Zephyr Development EnvironmentDownload the Zephyr Source CodeInstalling Requirements and DependenciesSetting the Project’s Environment Va…

《a16z : 2024 年加密货币现状报告》解析

加密社 原文链接&#xff1a;State of Crypto 2024 - a16z crypto译者&#xff1a;AI翻译官&#xff0c;校对&#xff1a;翻译小组 当我们两年前第一次发布年度加密状态报告的时候&#xff0c;情况跟现在很不一样。那时候&#xff0c;加密货币还没成为政策制定者关心的大事。 比…

Ubuntu 安装 npm

1. 升级apt sudo apt-get update 2. 安装nodejs sudo apt install nodejs 3. 安装npm sudo apt-get install npm 4. 查看版本 node -v npm -v 完成安装&#xff01;

记一次AWS服务器扩容

1、首先通过下列命令列出设备详情&#xff0c;可以看到红色框起来的部分有160G&#xff0c;需要把新增的20G扩容到根目录(139.9)上 lsblk查看文件系统 df -h2.执行sudo growpart /dev/xvda 1即可把20G的空间扩容到根目录上 扩容成功 但是可以看到并未生效 3.列出文件系统格…

ue5实现数字滚动增长

方法1 https://www.bilibili.com/video/BV1h14y197D1/?spm_id_from333.999.0.0 b站教程 重写loop节点 方法二 写在eventtick里

NVR小程序接入平台/设备EasyNVR多品牌NVR管理工具/设备的多维拓展与灵活应用

在数字化安防时代&#xff0c;NVR批量管理软件/平台EasyNVR作为一种先进的视频监控系统设备&#xff0c;正逐步成为各个领域监控解决方案的首选。NVR批量管理软件/平台EasyNVR作为一款基于端-边-云一体化架构的国标视频融合云平台&#xff0c;凭借其部署简单轻量、功能多样、兼…

什么是DICOM文件?——认识DICOM:医学影像与信息管理的标准化利器

目录 引言 什么是DICOM&#xff1f; DICOM的组成 DICOM的功能 DICOM的应用 DICOM的种类 DICOM的生成过程 DICOM的发展 总结 引言 在现代医学中&#xff0c;影像处理和管理是不可或缺的一环。从MRI、CT、X射线到超声波&#xff0c;医学影像为诊断和治疗提供了丰富的信息…

iOS 本地存储地址(位置)

前言: UserDefaults 存在沙盒的 Library --> Preferences--> .plist文件 CoreData 存在沙盒的 Library --> Application Support--> xx.sqlite 一个小型数据库里 (注:Application Support 这个文件夹已开始是没有的,只有当你写了存储代码,运行之后,目录里才会出…

django个人博客管理系统-计算机毕业设计源码27633

目 录 1 绪论 1.1 研究背景和意义 1.2国内外研究现状 1.3论文结构与章节安排 2 系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2.3 系统用例分析 2.4 系统流程…

任务看板是什么?如何选择合适的任务看板工具?

一、任务看板是什么&#xff1f; 任务看板是一种可视化的项目管理工具&#xff0c;它通常以板状的形式呈现&#xff0c;将任务以卡片的形式展示在不同的列中&#xff0c;每一列代表任务的不同状态。例如&#xff0c;待办事项、进行中、已完成等。任务看板能够帮助团队成员清晰…

使用 Flask 实现简单的登录注册功能

目录 1. 引言 2. 环境准备 3. 数据库设置 4. Flask 应用基本配置 5. 实现用户注册 6. 实现用户登录 7. 路由配置 8. 创建前端页面 9. 结论 1. 引言 在这篇文章中&#xff0c;我们将使用 Flask 框架创建一个简单的登录和注册系统。Flask 是一个轻量级的 Python Web 框架…

合合信息亮相2024中国模式识别与计算机视觉大会,用AI构建图像内容安全防线

近日&#xff0c;第七届中国模式识别与计算机视觉大会&#xff08;简称“PRCV 2024”&#xff09;在乌鲁木齐举办。大会由中国自动化学会&#xff08;CAA&#xff09;、中国图象图形学学会&#xff08;CSIG&#xff09;、中国人工智能学会&#xff08;CAAI&#xff09;和中国计…

pytorh学习笔记——cifar10(六)MobileNet V1网络结构

基础知识储备&#xff1a; 一、深度可分离卷积&#xff08;Depthwise Separable Convolution&#xff09; MobileNet的核心是深度可分离卷积&#xff08;Depthwise Separable Convolution&#xff09;&#xff0c;深度可分离卷积是卷积神经网络&#xff08;CNN&#xf…

IDM下载器 (Internet Download Manager) v6.42.2 中文免激活绿色版

Internet Download Manager (IDM下载器) 是一款先进的下载工具,可以提升您的下载速度高达5倍,支持续传&#xff0c;IDM可以让用户自动下载某些类型的文件&#xff0c;它可将文件划分为多个下载点以更快速度下载&#xff0c;并列出最近的下载&#xff0c;方便访问文件。相对于其…

Web刷题日记1---清风

[GDOUCTF 2023]EZ WEB 题目网站在NSSCTF 这个题目有一个新的知识点&#xff0c;对于我来说比较的少见吧&#xff0c;第一次遇见。em...是什么呢?后面再说 进入靶场&#xff0c;比较突兀&#xff0c;点了这个button后&#xff0c;提示flag在附近 查看源码&#xff0c;有提示…