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_ptsif (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***********************************************/// 转换解码后的帧为 QImageif (!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
相关文章:
FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频
FFMPEGQt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频 文章目录 FFMPEGQt 实时显示本机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.…...
微信小程序中关闭默认的 `navigationBar`,并使用自定义的 `nav-bar` 组件
要在微信小程序中关闭默认的 navigationBar,并使用自定义的 nav-bar 组件,你可以按照以下步骤操作: 1. 关闭默认的 navigationBar 在你的页面的配置文件 *.json 中设置 navigationBar 为 false。你需要在页面的 JSON 配置文件中添加以下代码…...
FPGA 小鸟避障游戏
FPGA实现效果: FPGA 小鸟避障游戏 FPGA(Field Programmable Gate Array)即现场可编程门阵列,是一种可以编程的数字逻辑器件。基于FPGA的小鸟避障游戏是一种结合了硬件加速和算法优化来运行的实时交互游戏。这种游戏一般利用FPGA的…...
Claude Financial Data Analyst:基于Claude的金融数据分析工具!免费开源!
大家好,我是木易,一个持续关注AI领域的互联网技术产品经理,国内Top2本科,美国Top10 CS研究生,MBA。我坚信AI是普通人变强的“外挂”,专注于分享AI全维度知识,包括但不限于AI科普,AI工…...
django5入门【03】新建一个hello界面
文章目录 1、前提条件⭐2、操作步骤总结3、实际操作示例 1、前提条件⭐ 将上一节创建的 Django 项目导入到 PyCharm 中。 2、操作步骤总结 (1)在 HelloDjango/HelloDjango 目录下,新建一个 views.py 文件。 (2)在 H…...
【Unity】Unity中调用手机的震动功能 包括安卓和IOS
直接上代码 #if UNITY_IOS[DllImport("__Internal")]private static extern void EX_C_CallVibrateE(int eID); #endif public static void CallVibrate(int eID){ #if UNITY_EDITOR#elif UNITY_ANDROIDlong miSec 30;if(eID 1520){miSec 60;}//通过报名获取ja…...
【软件工程】软件工程入门
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀软件开发必练内功_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前…...
命名空间std, using namespace std
命名空间std,using namespace std 在标准C以前,都是用#include<iostream.h>这样的写法的,因为要包含进来的头文件名就是iostream.h。标准C引入了名字空间的概念,并把iostream等标准库中的东东封装到了std名字空间中&#x…...
人工智能:未来生活与工作的变革者
随着人工智能(AI)技术的迅猛发展,越来越多的领域开始受益于AI的强大功能。从医疗、企业管理到日常生活,人工智能正在改变我们的世界。本文将深入探讨人工智能技术的应用前景,并分析它如何从根本上改变我们的生活和工作…...
SEO基础:什么是LSI关键词?【百度SEO优化专家】
SEO基础:什么是LSI关键词? 大家好,我是林汉文(百度SEO优化专家),在SEO(搜索引擎优化)中,LSI关键词是一个重要的概念,有助于提升网页的相关性和内容质量。那么…...
将理论付诸实践:如何通过实际项目有效学习和应用新技术
文章目录 摘要引言选择合适实践对象理论知识的转化遇到的挑战及解决方法挑战1:组件状态管理的复杂性挑战2:Node.js异步编程的复杂性 实践过程中的经验和心得将理论知识转化为操作能力QA环节总结未来展望参考资料 摘要 在技术的不断进步和变化的环境中&a…...
【R + Python】iNaturalist 网站图片下载 inat api
文章目录 一、iNaturalist 简介二、R语言API:rinat三、示例3.1 获取观测数据3.2 绘制可视化图像函数用法 3.4 在区域网格中搜索3.5 下载图片3.51 提取图片 url3.52 下载图片: R语言3.53 下载图片: python 四、获取详细rinat包的文档 一、iNaturalist 简介 …...
C#与Sqlite数据库
1,一般的访问方式。 1.1,连接语句。 //sqlite 连接,支持相对位置,也支持绝对位置 Data Source../../Database/cater.db// 连接数据库,FailIfMissingfalse时若文件不存在会自动创建 string connStr "DataSourcetest.db;Vers…...
2019年计算机网络408真题解析
第一题: 解析:OSI参考模型第5层完成的功能 首先,我们需要对OSI参考模型很熟悉:从下到上依次是:物理层-数据链路层-网络层- 运输层-会话层-表示层-应用层,由此可知,题目要问的是会话层的主要功能…...
江协科技STM32学习- P21 ADC模数转换器
🚀write in front🚀 🔎大家好,我是黄桃罐头,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝…...
[RK3566-Android11] 使用SPI方式点LED灯带-JE2815/WS2812,实现呼吸/渐变/随音量变化等效果
问题描述 之前写了一篇使用GPIO方式点亮LED灯带的文章 https://blog.csdn.net/jay547063443/article/details/134688745?fromshareblogdetail&sharetypeblogdetail&sharerId134688745&sharereferPC&sharesourcejay547063443&sharefromfrom_link 使用GPIO…...
PostgreSQL用load语句加载插件
文章目录 1. LOAD 语法2. 用途3. 示例4. 注意事项5. 其他相关命令6. 总结 在 PostgreSQL 中,LOAD 主要用于加载共享库,通常用于扩展功能或性能优化。以下是一些有关 LOAD 语句和 PostgreSQL 中的加载操作的关键信息: 1. LOAD 语法 LOAD 语句…...
一文了解:增强图像搜索之图像嵌入
图像嵌入在现代计算机视觉领域扮演着明星角色,它使得计算机能够像人类一样识别出各种各样的图像。由于计算机只能处理数字信息,我们需要将图像转换成数字向量,并存储在向量数据库中,这样就能迅速地检索到它们。 谈到嵌入技术&…...
yolov9目标检测/分割预测报错AttributeError: ‘list‘ object has no attribute ‘device‘常见汇总
这篇文章主要是对yolov9目标检测和目标分割预测测试时的报错,进行解决方案。 在说明解决方案前,严重投诉、吐槽一些博主发的一些文章,压根没用的解决方法,也不知道他们从哪里抄的,误人子弟、浪费时间。 我在解决前&…...
格姗知识圈博客网站开源了!
格姗知识圈博客 一个基于 Spring Boot、Spring Security、Vue3、Element Plus 的前后端分离的博客网站!本项目基本上是小格子一个人开发,由于工作和个人能力原因,部分技术都是边学习边开发,特别是前端(工作中是后端开…...
【C++】深入理解C++中的类型推导:从auto到decltype的应用与实践
C11引入了类型推导特性,旨在简化代码并提升开发效率。类型推导使开发者无需显式指定变量的类型,从而让代码更具可读性和灵活性。本文深入探讨了C11引入的auto、decltype和decltype(auto)等关键特性,通过分析其背后的设计理念、实际应用场景&a…...
使用Prometheus对微服务性能自定义指标监控
背景 随着云计算和容器化技术的不断发展,微服务架构逐渐成为现代软件开发的主流趋势。微服务架构将大型应用程序拆分成多个小型、独立的服务,每个服务都可以独立开发、部署和扩展。这种架构模式提高了系统的可伸缩性、灵活性和可靠性,但同时…...
深入解析 Lombok 的实现原理:以 @Builder 为例的实战演示(三)
文章目录 Lombok 的实现原理概述以 Builder 为例:解析 Lombok 如何生成 Builder 模式示例代码:没有 Lombok 的 Builder 模式使用 Lombok 的 Builder 简化代码 Lombok 如何实现 Builder:源码解析案例演示:自定义构造逻辑Lombok 的代…...
SEO基础:什么是SERP?【百度SEO专家】
SEO基础:什么是SERP? 大家好,我是林汉文(百度SEO专家),在进行SEO(搜索引擎优化)时,理解SERP是一个非常重要的基础概念。那么,究竟什么是SERP呢?本…...
HTML5教程(一)- 网页与开发工具
1. 什么是网页 网页 基于浏览器阅读的应用程序,是数据(文本、图像、视频、声音、链接等)展示的载体常见的是以 .html 或 .htm 结尾的文件 网站 使用 HTML 等制作的用于展示特定内容相关的网页集合。 2. 网页的组成 浏览器 代替用户向服务…...
Java进阶篇设计模式之二 ----- 工厂模式
前言 在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法。本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式、工厂方法和抽象工厂模式。 简单工厂模式 简单工厂模式是属于创建型模式,又叫做静态工厂方法模式。…...
考研篇——数据结构王道3.2.2_队列的顺序实现
目录 1.实现方式说明2.代码实现2.12.1.1 代码12.1.2 代码22.1.3 代码3 2.22.2.1 代码42.2.5 代码52.2.6 代码6 总结 1.实现方式说明 多在选择题中考察 队尾指针(rear)有两种指向方式: 队尾指针指向队尾元素的位置,队尾指针指向…...
从零开始理解 Trie 树:高效字符串存储与查找的利器【自动补全、拼写检查】
题目分析 这道题让我们实现一个 Trie 类(也称为前缀树),以便高效地插入和查询字符串。前缀树是一种特殊的树形数据结构,适用于快速存储和检索字符串数据集中的键,比如实现 自动补全 和 拼写检查。 题目要求 Trie 类…...
关于sse、websocket与流式渲染
一、SSE是什么? 网络中的 SSE (Server-Sent Events) 是一种服务器向浏览器单向推送数据的机制,常用于需要实时更新的数据传输,如新闻推送、股票行情、聊天应用等。 SSE 的特点: 单向通信:服务器向客户端推送数据&…...
Python 语法与数据类型详解
Python 语法与数据类型详解 Python 以其简洁易读的语法和丰富多样的数据类型在编程领域占据重要地位。深入理解 Python 的语法和数据类型是掌握这门语言的关键。 一、Python 语法概述 (一)缩进规则 Python 独特的缩进规则是其语法的重要特征之一。与…...
java python 做网站/江苏seo和网络推广
3. 运行数据库部署工具 Windows中启动部署工具的三种方式: 选择『开始』|『程序』|『KingbaseES V8 』|『Client Tools』|『数据库部署工具』,启动数据库部署工具。 在Windows资源管理器中进入到数据库安装目录下$InstallDir/DeployTools,双…...
做品管圈网站/优化网站技术
使用的引入注解不同 (引入controller的时候有时候会拿不到,那是因为controller类的最上面,没有引入Controller注解:) RunWith(SpringJUnit4ClassRunner.class)ContextConfiguration(locations { "classpath:spri…...
昭通做网站/腾讯云域名购买
from http://www.guokr.com/blog/763017/ http://blog.csdn.net/stilling2006/article/details/4129700 下载一个文档,一打开发现是乱码,不抓狂才怪…… 你们都知道,这都是字符编码闯的祸。ASCII、ANSI、GB18030、Unicode、UTF-8、UTF-8 with…...
WordPress discuz 仿站/seo搜索优化 指数
文章目录1. 条件搜索1) 等值比较2) 不等于比较3) 完整案例2. 模糊匹配1. 条件搜索 使用条件搜索时,可以直接使用filter链来过滤掉满足条件的记录,因为每次filter()的结果仍然是一个QuerySet,因此可以根据参数有无,来添加filter。 先不添加任何…...
商用高端网站设计新感觉建站/中国的搜索引擎有哪些
有些命令在执行之后将会返回一定的错误值(errorlevel),可以通过errorlevel的值判断命令执行的状况。这点类似于C语言里面的exit(num),num就是错误代码。 获取返回值errorlevel的方法就是,在执行命令后,立马调用返回值errorleve…...
品牌营销全案策划/广告优化师发展前景
进入设备:enable 进入特权模式配置:config terminal 设置telnet用户名和密码 enable service telnet-serverenable password 0 111111 username NAME password 111111line vty 0 4login local exit exit 退出config模式 write 写入修改 设置consol…...