Qt + FFmpeg 视频工具:视频一键压缩功能实现
概述视频文件在日常使用中经常面临体积过大的问题手机录制的 4K 视频、屏幕录制的原始文件、从网上下载的高码率素材动辄几百 MB 甚至几个 GB。对于非专业用户来说打开 HandBrake 或 FFmpeg 命令行去调参数是一件有门槛的事情。一键压缩功能要解决的问题是让用户不需要理解 CRF、preset、码率、分辨率这些概念只需在三个预设模式中选择一个点一下按钮就能完成视频压缩。三档预设覆盖了三类典型场景模式CRF分辨率音频码率适用场景清晰优先18原始192 kbps存档、高画质需求平衡23原始128 kbps日常推荐小体积28720p (宽 1280)96 kbps手机分享、网盘存储核心思路底层是完整的 FFmpeg 转码管线解码 → 可选 sws_scale 缩放 → H.264 重编码 → AAC 音频 → MP4 封装上层通过VideoTranscoder纯工具类暴露 CRF、分辨率、音频码率等参数MediaAnalyzer负责异步调度和状态管理QML 页面只负责预设选择和结果展示。实测数据原始文件 298MB三种压缩模式输出文件分别是151MB、52.8MB、7.8MB功能流程用户选择预设模式清晰优先 / 平衡 / 小体积 │ ▼ QML 页面调用 mediaAnalyzer.compressCurrentVideo(filePath, crf, w, h, ...) │ ▼ MediaAnalyzer 入口校验 → 清空旧结果 → setBusy(true) → runAsync() │ ▼ 后台线程VideoTranscoder::transcode() │ ├─ 打开输入容器找到最佳视频/音频流 ├─ 创建输出容器配置 H.264 编码器CRF preset medium ├─ 配置音频编码器AAC 目标码率 ├─ 如目标分辨率 ≠ 源分辨率初始化 sws_scale 缩放上下文 ├─ 如音频采样格式/采样率不匹配初始化 SwrContext 重采样 ├─ 主循环av_read_frame → send_packet → receive_frame → 编码 → write_frame ├─ Flush 解码器 编码器内部缓存 ├─ 写 trailer关闭容器 └─ 组装 transcodeInfo编码器、分辨率、压缩率、耗时等 │ ▼ 主线程回调setCompressVideoInfo(info) → 状态栏 InfoPanel 更新核心算法CRF 恒定质量因子CRFConstant Rate Factor是 x264/x265 编码器最核心的质量控制参数范围 0~510无损编码体积甚至可能大于源文件18视觉无损人眼几乎看不出与源文件的差异23x264 默认值画质与体积的平衡点28明显降低体积画质有一定损失但可接受51最差质量基本不可用一键压缩通过暴露三个离散的 CRF 值让用户在画质和体积之间做简单选择而不是面对一个滑块不知所措。分辨率缩放与偶数对齐小体积模式会将宽度降到 1280720p高度按源视频宽高比自动计算。H.264 编码器要求宽高必须是偶数因此计算结果需要 ~1对齐// 计算目标分辨率宽高自动计算并对齐偶数voidcomputeTargetResolution(intsourceWidth,intsourceHeight,inttargetWidth,inttargetHeight){if(targetWidth0targetHeight0){// 两个维度都未指定保留原始尺寸targetWidthsourceWidth;targetHeightsourceHeight;return;}if(targetWidth0targetHeight0){// 两个维度都指定了直接使用对齐偶数targetWidthtargetWidth~1;targetHeighttargetHeight~1;return;}// 只指定了一个维度按原始宽高比计算另一个维度constdoubleaspect(sourceHeight0)?static_castdouble(sourceWidth)/sourceHeight:1.0;if(targetWidth0){targetWidthtargetWidth~1;targetHeightstatic_castint(targetWidth/aspect0.5);targetHeight(targetHeight0)?(targetHeight~1):2;}else{targetHeighttargetHeight~1;targetWidthstatic_castint(targetHeight*aspect0.5);targetWidth(targetWidth0)?(targetWidth~1):2;}}音频 AudioFIFO SwrContext解码出的音频帧采样格式和采样率往往与 AAC 编码器要求不一致例如源是 fltp/48kHzAAC 需要 fltp/44.1kHz。处理流程是用SwrContext做采样格式转换和采样率重采样转换后的样本写入AVAudioFifoFIFO 攒够一个frame_size的样本后取出并送入编码器尾部不足一帧的样本在 flush 阶段补齐编码这个模式保证了无论源音频参数如何编码器始终收到格式正确、大小一致的帧。后端工具类VideoTranscoderVideoTranscoder是纯工具类无状态、无 UI 依赖、无当前文件依赖。它只接收输入路径和编码参数输出压缩后的视频文件。类声明classVideoTranscoder{public:// 对整个视频执行转码压缩//// crf: 恒定质量因子 0~51值越小画质越高// targetWidth/targetHeight: 目标分辨率都为 0 时保留原始尺寸// audioBitrateKbps: 音频码率kbps0 表示编码器默认// keepAudio: 是否保留音频流// copyMetadata: 是否复制 metadata// transcodeInfo: 成功时返回结果摘要booltranscode(constQStringinputPath,constQStringoutputPath,intcrf,inttargetWidth,inttargetHeight,intaudioBitrateKbps,boolkeepAudio,boolcopyMetadata,QVariantMap*transcodeInfo,QString*errorText)const;};编码器参数配置视频编码器的参数配置是压缩质量的关键。preset medium保持编码速度和压缩率的平衡max_b_frames 0简化 PTS/DTS 顺序避免 B 帧重排问题gop_size按实际帧率动态计算videoEncCtx-widthencWidth;videoEncCtx-heightencHeight;videoEncCtx-pix_fmtchooseVideoPixelFormat(videoEncoder,videoDecCtx-pix_fmt);videoEncCtx-time_basevideoTimeBase;videoEncCtx-framerateframeRate;videoEncCtx-gop_sizeqMax(12,frameRate.num0frameRate.den0?frameRate.num/frameRate.den:12);videoEncCtx-max_b_frames0;// CRF 和 preset 只对 x264/x265 等支持私有选项的编码器生效av_opt_set(videoEncCtx-priv_data,preset,medium,0);av_opt_set_int(videoEncCtx-priv_data,crf,safeCrf,0);视频帧解码 → 缩放 → 编码每解码一帧后检查是否需要 sws_scale 转换。条件触发——只有当源分辨率或像素格式与编码器不一致时才初始化缩放上下文避免不必要的内存分配if(videoDecCtx-width!videoEncCtx-width||videoDecCtx-height!videoEncCtx-height||videoDecCtx-pix_fmt!videoEncCtx-pix_fmt){if(!swsCtx){swsCtxsws_getContext(videoDecCtx-width,videoDecCtx-height,videoDecCtx-pix_fmt,videoEncCtx-width,videoEncCtx-height,videoEncCtx-pix_fmt,SWS_BILINEAR,nullptr,nullptr,nullptr);}// 分配可写的编码帧缓冲av_frame_make_writable(videoEncodeFrame);sws_scale(swsCtx,decodedFrame-data,decodedFrame-linesize,0,videoDecCtx-height,videoEncodeFrame-data,videoEncodeFrame-linesize);frameForEncodervideoEncodeFrame;}时间戳处理方面整视频转码不需要裁剪偏移第一帧直接作为时间轴零点if(firstVideoPtsAV_NOPTS_VALUE)firstVideoPtsframePts!AV_NOPTS_VALUE?framePts:0;frameForEncoder-ptsframePts!AV_NOPTS_VALUE?av_rescale_q(framePts-firstVideoPts,inVideoStream-time_base,videoEncCtx-time_base):encodedVideoFrames;主读取循环标准的 send/receive 模型逐 packet 读取按流索引分发到对应解码器取出帧后立即编码。视频和音频交替处理保证 FIFO 中的数据及时被编码写出while(!reachedEof){retav_read_frame(inCtx,packet);if(retAVERROR_EOF){reachedEoftrue;break;}if(packet-stream_indexvideoStreamIndex){avcodec_send_packet(videoDecCtx,packet);receiveVideoFrames();// 解码 → 缩放 → 编码 → write_frame}elseif(audioDecCtxpacket-stream_indexaudioStreamIndex){avcodec_send_packet(audioDecCtx,packet);receiveAudioFrames();// 解码 → 重采样 → FIFO → 编码 → write_frame}av_packet_unref(packet);}// Flush 解码器和编码器内部缓存avcodec_send_packet(videoDecCtx,nullptr);receiveVideoFrames();avcodec_send_packet(audioDecCtx,nullptr);receiveAudioFrames();// Flush SwrContext 内部延迟样本 → FIFO → 编码导出封装MediaAnalyzer 异步调度MediaAnalyzer作为 QML 门面层负责入口校验、路径处理、异步调度和状态更新。转码是耗时操作必须放到后台线程避免阻塞 UIboolMediaAnalyzer::compressCurrentVideo(constQStringfilePath,intcrf,inttargetWidth,inttargetHeight,intaudioBitrateKbps,boolkeepAudio,boolcopyMetadata){if(m_currentFile.isEmpty()){setStatus(QStringLiteral(请先导入视频文件));setCompressVideoInfo(QVariantMap());returnfalse;}// 处理输出路径支持 file:// URL 和普通路径QString outputPathfilePath.trimmed();if(outputPath.startsWith(QStringLiteral(file:),Qt::CaseInsensitive))outputPathQUrl(outputPath).toLocalFile();if(outputPath.isEmpty())outputPathdefaultCompressedVideoPath();if(QFileInfo(outputPath).suffix().isEmpty())outputPathQStringLiteral(.mp4);setCompressVideoInfo(QVariantMap());setBusy(true);setStatus(QStringLiteral(正在压缩视频...));// 转码是耗时操作必须放到后台线程执行runAsync([]()-AsyncResult{AsyncResult r;QVariantMap result;r.oktranscoder.transcode(sourceFile,outputPath,crf,targetWidth,targetHeight,audioBitrateKbps,keepAudio,copyMetadata,result,r.errorText);if(r.ok){r.inforesult;r.outputPathoutputPath;}returnr;},[this](constAsyncResultr){if(r.ok){setCompressVideoInfo(r.info);setStatus(QStringLiteral(视频压缩完成%1).arg(r.outputPath));}else{setCompressVideoInfo(QVariantMap());setStatus(QStringLiteral(视频压缩失败%1).arg(r.errorText));}});returntrue;}QML 页面三档预设选择VideoCompressPage.qml用三个可点击的预设卡片横排展示用户点选后高亮当前预设。每个预设包含名称、CRF 标签、描述文本底部显示当前参数摘要// 压缩预设定义 property var compressPresets: [ { name: 清晰优先, crf: 18, targetWidth: 0, targetHeight: 0, audioBitrateKbps: 192, description: CRF 18保留原始分辨率画质接近无损。 }, { name: 平衡, crf: 23, targetWidth: 0, targetHeight: 0, audioBitrateKbps: 128, description: CRF 23保留原始分辨率画质与体积均衡。 }, { name: 小体积, crf: 28, targetWidth: 1280, targetHeight: 0, audioBitrateKbps: 96, description: CRF 28分辨率降至 720p适合手机分享。 } ] property int currentPresetIndex: 1 // 默认平衡 // 开始压缩按钮 ActionButton { text: 开始压缩 accent: true onClicked: mediaAnalyzer.compressCurrentVideo( outputPathField.text.trim(), currentPreset().crf, currentPreset().targetWidth, currentPreset().targetHeight, keepAudioBox.checked ? currentPreset().audioBitrateKbps : 0, keepAudioBox.checked, copyMetadataBox.checked) }结果面板通过InfoPanel展示压缩结果包含编码器、分辨率、压缩率、耗时等信息数据由MainWindow.qml的refreshVideoCompressRows()从compressVideoInfo映射生成。状态清理当前文件切换或重新导入时MediaAnalyzer::openFile()会调用setCompressVideoInfo(QVariantMap())清空旧的压缩结果避免 UI 显示过期数据。QML 页面通过Connections监听onCurrentFileChanged和onFileOpened信号同步刷新默认输出路径。小结视频一键压缩的实现要点分层架构VideoTranscoder纯算法→MediaAnalyzer状态 异步调度→VideoCompressPage.qml交互职责分离清晰。CRF 驱动不暴露码率参数用 CRF 控制质量让编码器自行分配码率对用户更友好。条件缩放只在目标分辨率与源不一致时初始化 sws_scale原始尺寸模式零额外开销。AudioFIFO 攒帧解决解码帧大小与编码器frame_size不匹配的问题尾部样本在 flush 阶段补齐。三档预设把复杂的编码参数收敛为三个直觉化选项降低使用门槛。

相关新闻

从代码到Storyboard:ComplimentaryGradientView的两种集成方式对比

从代码到Storyboard:ComplimentaryGradientView的两种集成方式对比

从代码到Storyboard:ComplimentaryGradientView的两种集成方式对比 【免费下载链接】ComplimentaryGradientView Create complementary gradients generated from dominant and prominent colors in supplied image. Inspired by Grade.js 项目地址: https://gitc…

2026/7/12 20:04:06 阅读更多 →
CVPR 2023开源项目IP_LAP:论文精读与代码实现对照解读

CVPR 2023开源项目IP_LAP:论文精读与代码实现对照解读

CVPR 2023开源项目IP_LAP:论文精读与代码实现对照解读 【免费下载链接】IP_LAP CVPR2023 talking face implementation for Identity-Preserving Talking Face Generation With Landmark and Appearance Priors 项目地址: https://gitcode.com/gh_mirrors/ip/IP_L…

2026/7/12 20:04:06 阅读更多 →
ChatGPT文件上传日志解密:如何从HTTP Payload中提取未公开的chunking策略与token截断阈值

ChatGPT文件上传日志解密:如何从HTTP Payload中提取未公开的chunking策略与token截断阈值

更多请点击: https://kaifayun.com 第一章:ChatGPT文件上传日志解密:如何从HTTP Payload中提取未公开的chunking策略与token截断阈值 ChatGPT Web 界面在处理用户上传的 PDF、TXT 或 DOCX 文件时,会触发一组隐蔽的前端分块&#…

2026/7/12 20:02:06 阅读更多 →

最新新闻

SandboxFusion API使用教程:快速集成到AI代码生成系统

SandboxFusion API使用教程:快速集成到AI代码生成系统

SandboxFusion API使用教程:快速集成到AI代码生成系统 【免费下载链接】SandboxFusion 项目地址: https://gitcode.com/gh_mirrors/sa/SandboxFusion SandboxFusion是一个强大的代码沙盒系统,专门为AI代码生成和评估而设计。这个开源工具可以帮助…

2026/7/12 21:04:24 阅读更多 →
论坛(五)帖子编辑与话题

论坛(五)帖子编辑与话题

论坛(五)帖子编辑与话题 本文是「AtomCode 驱动全栈开发」系列的第 16 篇。我们将深入分析论坛项目的帖子编辑页面、话题选择联动、Markdown 编辑器集成,完整展示 PostEdit 视图、路由配置、Topic 模型及前后端联调的全过程。 目录 1. 引言2…

2026/7/12 21:04:24 阅读更多 →
如何快速开始使用OpenTelemetry-proto:从安装到第一个数据导出

如何快速开始使用OpenTelemetry-proto:从安装到第一个数据导出

如何快速开始使用OpenTelemetry-proto:从安装到第一个数据导出 【免费下载链接】opentelemetry-proto OpenTelemetry protocol (OTLP) specification and Protobuf definitions 项目地址: https://gitcode.com/gh_mirrors/op/opentelemetry-proto OpenTeleme…

2026/7/12 21:02:23 阅读更多 →
UE5蓝图5分钟实现3DTiles动态加载:数字孪生大场景轻量化方案

UE5蓝图5分钟实现3DTiles动态加载:数字孪生大场景轻量化方案

1. 项目概述:为什么要在UE5里动态加载3DTiles?如果你正在用UE5做数字孪生、智慧城市或者大场景仿真,手头有一大堆倾斜摄影、BIM或者点云数据,格式是OSGB或者别的什么,最后大概率都得转成3DTiles。数据量一大&#xff0…

2026/7/12 21:02:23 阅读更多 →
AI 一周大事盘点(2026 年 7 月 5 日~2026 年 7 月 11 日)

AI 一周大事盘点(2026 年 7 月 5 日~2026 年 7 月 11 日)

【摘要】本周全球 AI 行业迎来多重重磅动态:OpenAI 发布 GPT-5.6 系列模型,多智能体能力正式产品化;苹果起诉 OpenAI 窃取硬件机密,巨头竞争全面升级;AI 造福人类全球峰会推动治理从讨论转向务实落地。国内方面&#x…

2026/7/12 21:00:22 阅读更多 →
终极指南:如何用Bash Infinity框架彻底改变你的Shell脚本开发体验

终极指南:如何用Bash Infinity框架彻底改变你的Shell脚本开发体验

终极指南:如何用Bash Infinity框架彻底改变你的Shell脚本开发体验 【免费下载链接】bash-oo-framework Bash Infinity is a modern standard library / framework / boilerplate for Bash 项目地址: https://gitcode.com/gh_mirrors/ba/bash-oo-framework 厌…

2026/7/12 20:58:22 阅读更多 →

日新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/12 0:03:13 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/12 0:03:14 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/12 0:03:14 阅读更多 →

周新闻

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨

互联网大厂 Java 求职面试:燕双非的搞笑回答与技术探讨 在一个阳光明媚的上午,互联网大厂的面试官坐在桌前,准备迎接他的面试候选人——燕双非,一个以搞笑和幽默著称的程序员。第一轮提问 面试官:燕双非,作…

2026/7/12 0:03:13 阅读更多 →
车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估

车载以太网PMA测试设备选型:示波器、VNA、信号源3类仪器关键参数与预算评估在智能驾驶和车联网技术快速发展的今天,车载以太网作为新一代车载网络的核心传输技术,其物理层性能直接决定了数据传输的可靠性和稳定性。1000BASE-T1作为当前主流的…

2026/7/12 0:03:14 阅读更多 →
VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战,5步完成Keil工程转换

VSCode EIDE 插件 2.0:APM32/STM32 项目迁移实战指南嵌入式开发领域正经历一场工具链的静默革命。当传统Keil用户首次打开VSCode的扩展市场搜索EIDE时,往往会惊讶于这个看似简单的插件竟能重构十余年的开发习惯。本文将揭示如何用五个精准步骤&#xff0…

2026/7/12 0:03:14 阅读更多 →

月新闻