图片合并成视频 img2video ffmpeg
1. 打开图片文件bool open_input_file(int seq, const QString filePath){int ret avformat_open_input(in_fmtCtx, filePath.toStdString().c_str(), nullptr, nullptr);if(ret 0){qDebug() 打开文件;return false;}ret avformat_find_stream_info(in_fmtCtx, nullptr);if(ret 0){qDebug() 信息获取失败;return false;}int vIdx av_find_best_stream(in_fmt, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);AVStream *vStream in_fmt-streams[vIdx];const AVCodec* codec avcodec_find_decoder(vStream-codecpar-codec_id);in_decodec avcodec_alloc_context3(codec);avcodec_parameters_to_context(in_decodec, vStream-codecpar);ret avcodec_open2(in_decodec, codec, nullptr);if(ret 0){qDebug() 打开解码器;return false;}return ture;}2. 打开输出文件bool open_output_file(const QString filePath){int ret avformat_alloc_output_context2(out_fmtCtx, nullptr, nullptr, filePath.toStdString().c_str());if(ret 0){qDebug() 打开输出文件失败;return false;}ret avio_open(out_fmtCtx-pb, filePath.toStdString().c_str(), AVIO_FLAG_WRITE);if(ret 0){qDebug() 打开输出流失败;return false;}AVStream *vStream out_fmtCtx-streams[vIdx];const AVCodec* codec avcodec_find_encoder(AV_CODEC_ID_H264);AVCodecContext *m_codecCtx avcodec_alloc_context3(codec);m_codecCtx-pix_fmt AV_PIX_FMT_YUV420P;m_codecCtx-width vStream-width;m_codecCtx-height vStream-height;m_codecCtx-time_best vStream-time_best;m_codecCtx-framereate AVRational{1.25};m_codecCtx-gop_size 12;m_codecCtx-max_b_frames 0; // 关闭B帧ret avcodec_open2(m_codecCtx, codec, nullptr);if(ret 0){qDebug() 编码器打开失败;return false;}dest_video avformat_new_stream(m_codecCtx, codec);dest_video-time_base m_codecCtx-time_base;avcodec_parameters_from_context(dest_video-codecpat, m_codecCtx);dest_video-codecpat-codec_tag 0;ret avformat_write_header(out_fmtCtx, nullptr);if(ret 0){qDebug() 写文件头;return false;}return true;}3. 初始化图片转换器bool init_sws_context(){enum AVPixelFormat target_format AV_PIX_FMT_YUV420P;swsContex sws_getContext(in decodecCtx-width, in_decodecCtx-height, m_codecCtx-pix_fmt,out_encodecCtx-width, in_decodecCtx-height, target_format,SWS_FAST_BILINEAR, nullptr, nullptr, nullptr);vframe av_frame_alloc();vframe-format target_format;vframe-width out_encodecCtx-width;vframe-height out_encodecCtx-height;ret av_image_alloc(vframe-data, vframe-linesizes,out_encodecCtx-width, out_encodecCtx-height,target_format, 1);if(ret 0){qDebug() 分配失败;return false;}return true;}//一次性分配图像原始数据缓冲区 int av_image_alloc(uint8_t *pointers[4], int linesizes[4], int w, int h, enum AVPixelFormat pix_fmt, int align);4. 图片重新编码输入输出文件bool decodec_video(int seq, AVPacket* packet, AVFrame* frame){int ret avcodec_send_packet(in_decodec, packet);if(ret 0){qDebug() 发送packet失败;return false;}while(true){ret avcodec_receive_frame(in_decodec, frame);if(ret 0){qDebug() receive frame error;}sws_scale(m_swsCtx, (const uint8_t*)frame-data, frame-linesize,0, frame-height,vframe-data, vframe-linesize);for(int i 0; i 100; i){vframe-pts packet_index;encodec_video(vframe);}}}bool encodec_video(AVFrame *frame){int ret avcodec_send_frame(out_encodecCtx, frame);if(ret 0){qDebug() 发送失败;return false;}while(1){AVPacket *packet av_packet_alloc();ret avcodec_receive_packet(out_encidecCtx, packet);if(ret 0){qDebug() 获取packet失败;return false;}av_packet_rescale_ts(packet, src_video-time_base, dest_video-time_base);packet-stream_index 0;ret av_write_frame(out_fmtCtx, packet);if(ret 0){qDebug() 写入失败;return false;}av_packet_unref(packet);}return true;}#include widget.h #include QDebug Widget::Widget(QWidget *parent) : QWidget(parent) { QString img1 3.jpg; QString img2 4.jpg; QString filePath 1.mp4; if(!open_input_file(0,img1)){ qDebug() 打开img1失败; return; } if(!open_input_file(1, img2)){ qDebug() 打开img2失败; return; } if(!open_output_file(filePath.toStdString().c_str())){ qDebug() 打开输出文件失败; return; } if(!init_sws_context(0)){ qDebug() 初始化失败; return; } if(!init_sws_context(1)){ qDebug() 初始化失败1; return; } AVPacket* packet av_packet_alloc(); AVFrame *frame av_frame_alloc(); while (av_read_frame(in_fmtCtx[0], packet) 0) { if(packet-stream_index video_index[0]){ decodec_video(0, packet, frame); } av_packet_unref(packet); } packet-data nullptr; packet-size 0; decodec_video(0, packet, frame); while (av_read_frame(in_fmtCtx[1],packet) 0) { if(packet-stream_index video_index[1]){ decodec_video(1,packet, frame); } av_packet_unref(packet); } packet-data nullptr; packet-size 0; decodec_video(0,packet, frame); encodec_video(nullptr); av_write_trailer(out_fmtCtx); av_frame_free(frame); av_frame_free(vframe[0]); av_frame_free(vframe[1]); av_packet_free(packet); avio_close(out_fmtCtx-pb); avcodec_free_context(in_decodecCtx[0]); avcodec_free_context(in_decodecCtx[1]); avcodec_free_context(out_encodecCtx); sws_freeContext(swsContex[0]); sws_freeContext(swsContex[1]); avformat_free_context(out_fmtCtx); avformat_close_input(in_fmtCtx[0]); avformat_close_input(in_fmtCtx[1]); } Widget::~Widget() { } bool Widget::open_input_file(int seq, const QString filePath) { // 1. 打开文件 int ret avformat_open_input(in_fmtCtx[seq], filePath.toStdString().c_str(), nullptr, nullptr); if( ret 0){ qDebug() 打开文件失败; return false; } // 2. 获取文件流信息 ret avformat_find_stream_info(in_fmtCtx[seq], nullptr); if( ret 0 ){ qDebug() 获取文件信息流失败; return false; } // 3. 获取视频流索引 video_index[seq] av_find_best_stream(in_fmtCtx[seq], AVMEDIA_TYPE_VIDEO, -1,-1,nullptr,0); if(video_index[seq] 0 ){ // 4. 获取视频流结构体 src_video in_fmtCtx[seq]-streams[video_index[seq]]; // 5. 获取解码器 const AVCodec *codec avcodec_find_decoder(src_video-codecpar-codec_id); // 6. 根据解码器获取解码器上下文结构体 in_decodecCtx[seq] avcodec_alloc_context3(codec); // 7. 复制解码参数到视频流结构体 ret avcodec_parameters_to_context(in_decodecCtx[seq], src_video-codecpar); if(ret 0){ qDebug() 复制失败; return false; } // 8. 打开解码器 ret avcodec_open2(in_decodecCtx[seq], codec, nullptr); if(ret 0){ qDebug() 打开解码器失败; return false; } } return true; } bool Widget::open_output_file(const QString filePath) { int ret avformat_alloc_output_context2(out_fmtCtx, nullptr, nullptr, filePath.toStdString().c_str()); if(ret 0){ qDebug() avformat_alloc_output_context2 fail; return false; } ret avio_open(out_fmtCtx-pb, filePath.toStdString().c_str(), AVIO_FLAG_WRITE); if(ret 0){ qDebug() avio_open fail; return false; } if(video_index[0] 0){ src_video in_fmtCtx[0]-streams[video_index[0]]; // ✅ 查找H264编码器 const AVCodec* codec avcodec_find_encoder(AV_CODEC_ID_H264); if(!codec){ qDebug() 找不到H264编码器请确认ffmpeg带libx264; return false; } out_encodecCtx avcodec_alloc_context3(codec); out_encodecCtx-pix_fmt AV_PIX_FMT_YUV420P; out_encodecCtx-width src_video-codecpar-width; out_encodecCtx-height src_video-codecpar-height; out_encodecCtx-time_base AVRational{1,25}; out_encodecCtx-framerate AVRational{25,1}; out_encodecCtx-gop_size 12; out_encodecCtx-max_b_frames 0; // 图片合成建议关闭B帧提升兼容性 ret avcodec_open2(out_encodecCtx,codec, nullptr); if(ret 0){ qDebug() 编码器打开失败; return false; } dest_video avformat_new_stream(out_fmtCtx, codec); dest_video-time_base out_encodecCtx-time_base; avcodec_parameters_from_context(dest_video-codecpar, out_encodecCtx); dest_video-codecpar-codec_tag 0; } ret avformat_write_header(out_fmtCtx, nullptr); if(ret 0){ qDebug() 写文件头失败; return false; } return true; } bool Widget::encodec_video(AVFrame *frame) { int ret avcodec_send_frame(out_encodecCtx,frame); if(ret 0){ qDebug() 发送失败; return false; } while (1) { AVPacket *packet av_packet_alloc(); ret avcodec_receive_packet(out_encodecCtx, packet); if(ret AVERROR(EAGAIN) || ret AVERROR_EOF){ qDebug() 获取pack 失败; return false; } av_packet_rescale_ts(packet, src_video-time_base, dest_video-time_base); packet-stream_index 0; ret av_write_frame(out_fmtCtx, packet); if(ret 0){ qDebug() 写入失败; return false; } av_packet_unref(packet); } return true; } bool Widget::decodec_video(int seq, AVPacket *packet, AVFrame *frame) { int ret avcodec_send_packet(in_decodecCtx[seq], packet); if(ret 0){ char err[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err, sizeof(err)); qDebug() send packet error: err; return false; } while (true) { ret avcodec_receive_frame(in_decodecCtx[seq], frame); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) { break; // 需要更多数据 / 解码结束正常退出循环 } if (ret 0) { char err[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err, sizeof(err)); qDebug() receive frame error: err; return false; } // 成功拿到 frame执行 sws_scale sws_scale(swsContex[seq], (const uint8_t **)frame-data, frame-linesize, 0, frame-height, vframe[seq]-data, vframe[seq]-linesize); // 编码 N 帧控制图片停留时长 for(int i 0; i 100; i) { vframe[seq]-pts packet_index; encodec_video(vframe[seq]); } } return true; } bool Widget::init_sws_context(int seq) { enum AVPixelFormat target_format AV_PIX_FMT_YUV420P; swsContex[seq] sws_getContext( in_decodecCtx[seq]-width, in_decodecCtx[seq]-height, in_decodecCtx[seq]-pix_fmt, out_encodecCtx-width, out_encodecCtx-height, target_format, SWS_FAST_BILINEAR, nullptr, nullptr, nullptr ); if(swsContex[seq] nullptr){ qDebug() 初始化swsContext 失败; return false; } vframe[seq] av_frame_alloc(); vframe[seq]-format target_format; vframe[seq]-width out_encodecCtx-width; vframe[seq]-height out_encodecCtx-height; av_image_alloc(vframe[seq]-data, vframe[seq]-linesize, out_encodecCtx-width, out_encodecCtx-height, target_format, 1); return true; }#ifndef WIDGET_H #define WIDGET_H #include QWidget extern C { #include libavcodec/avcodec.h #include libavformat/avformat.h #include libswscale/swscale.h #include libavdevice/avdevice.h #include libavutil/time.h #include libavutil/mathematics.h #include libavutil/imgutils.h } class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); bool open_input_file(int seq, const QString filePath); bool open_output_file(const QString filePath); bool encodec_video(AVFrame *frame); // 编码 bool decodec_video(int seq, AVPacket* packet, AVFrame *frame); // bool init_sws_context(int seq); private: AVFormatContext* in_fmtCtx[2] {nullptr, nullptr}; AVFormatContext* out_fmtCtx nullptr; AVCodecContext* in_decodecCtx[2] {nullptr, nullptr}; AVCodecContext* out_encodecCtx nullptr; AVStream *src_video nullptr; AVStream *dest_video nullptr; SwsContext *swsContex[2] {nullptr,nullptr}; AVFrame* vframe[2] {nullptr, nullptr}; int video_index[2] {-1,-1}; int packet_index 0; }; #endif // WIDGET_H

相关新闻

从欧拉函数到线性筛:高效计算互质数的算法实践

从欧拉函数到线性筛:高效计算互质数的算法实践

1. 从一道经典算法题说起:Relatives如果你刷过一些在线评测平台(OJ)的题目,或者正在准备算法竞赛,大概率见过这样一道题:给定一个正整数n,要求计算在1到n-1的范围内,有多少个整数与n…

2026/8/23 20:24:37 阅读更多 →
测试转大模型:Demo能跑通不算完,生产环境的权限和日志才是真考验

测试转大模型:Demo能跑通不算完,生产环境的权限和日志才是真考验

聊《测试转大模型,真正值钱的为什么不是会调 API?》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要很多测试同学转型做AI质量工程时,把大量时间花在学习Prompt编写、跑分指标…

2026/8/23 20:24:37 阅读更多 →
目标检测算法面试要点与工程实践解析

目标检测算法面试要点与工程实践解析

1. 目标检测算法面试核心要点解析作为计算机视觉领域最具挑战性的任务之一,目标检测在自动驾驶、工业质检、安防监控等场景中扮演着关键角色。过去五年间,我面试过上百位计算机视觉方向的候选人,发现80%的面试问题都集中在检测算法的演进脉络…

2026/8/23 20:24:37 阅读更多 →

最新新闻

论文AI率过高怎么办?2026年12款免费降AI率工具实测指南

论文AI率过高怎么办?2026年12款免费降AI率工具实测指南

现在毕业论文答辩前,“AI率超标”已经彻底取代“查重率过高”,成了同学们的头号难题!不少同学只是用AI润色了摘要和结论,AI率直接飙升到离谱,纯手动修改根本不管用——这说明AIGC检测系统抓的不是个别词语,…

2026/8/23 23:59:54 阅读更多 →
Marketch:从Sketch画板直接量取CSS

Marketch:从Sketch画板直接量取CSS

Marketch:从Sketch画板直接量取CSS 【免费下载链接】marketch Marketch is a Sketch 3 plug-in for automatically generating html page that can measure and get CSS styles on it. 项目地址: https://gitcode.com/gh_mirrors/ma/marketch 设计稿交付还在…

2026/8/23 23:59:54 阅读更多 →
如何在ThinkPad X390上安装macOS:OpenCore EFI完整指南

如何在ThinkPad X390上安装macOS:OpenCore EFI完整指南

如何在ThinkPad X390上安装macOS:OpenCore EFI完整指南 【免费下载链接】ThinkpadX390-Opencore-EFI macOS Catalina & Big Sur & Monterey on ThinkPad X390 (Hackintosh) 项目地址: https://gitcode.com/gh_mirrors/th/ThinkpadX390-Opencore-EFI …

2026/8/23 23:59:54 阅读更多 →
WechatHook 终极指南:5大核心能力详解,3分钟看懂微信自动化

WechatHook 终极指南:5大核心能力详解,3分钟看懂微信自动化

WechatHook 终极指南:5大核心能力详解,3分钟看懂微信自动化 【免费下载链接】WechatHook Enjoy hooking wechat by Xposed....Accessibility...and so on... 项目地址: https://gitcode.com/gh_mirrors/we/WechatHook WechatHook 是一个基于 Xpos…

2026/8/23 23:59:54 阅读更多 →
OpenModScan:免费跨平台 Modbus 主站调试工具,让现场通讯验证一键搞定

OpenModScan:免费跨平台 Modbus 主站调试工具,让现场通讯验证一键搞定

OpenModScan:免费跨平台 Modbus 主站调试工具,让现场通讯验证一键搞定 【免费下载链接】OpenModScan Open ModScan is a Free Modbus Master (Client) Utility 项目地址: https://gitcode.com/gh_mirrors/op/OpenModScan OpenModScan 是一款开源免…

2026/8/23 23:59:54 阅读更多 →
Gin-JWT认证与授权方案从Token到RBAC权限控制

Gin-JWT认证与授权方案从Token到RBAC权限控制

Gin-JWT认证与授权方案从Token到RBAC权限控制 文章导语 JWT(JSON Web Token)是现代Web服务的身份认证标准。在Gin框架中集成JWT看似简单,但涉及Token刷新、黑名单、多设备登录、RBAC权限控制等实际需求时,就需要更完善的设计。本文…

2026/8/23 23:57:54 阅读更多 →

日新闻

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/24 0:06:02 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/24 0:20:20 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/24 0:14:11 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/23 18:47:06 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/23 12:10:44 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/22 3:22:48 阅读更多 →