当前位置: 首页 > news >正文

C++实现视频截图功能

大半夜的聊聊C++视频截图,顺便祭奠一下我那逝去的发际线

现在是凌晨3点15分,我那个破烂项目的代码还在编译,进度条卡在99%不动了,我心态崩了真的...趁着这会儿功夫,看到论坛里有个小白在问怎么用C++做视频截图。哎,本来不想说话的,但看到有人推荐用OpenCV我就坐不住了。兄弟,OpenCV那是给搞算法的人玩的玩具,真到了生产环境,还得是FFmpeg这个让人头秃的玩意儿。

说实话,每次碰FFmpeg的API,我都感觉自己在排雷。文档写得跟天书一样,甚至有时候我都怀疑写文档的人是不是故意不想让我们看懂...

FFmpeg:这玩意儿就是个黑洞

在C++里搞视频处理,那种感觉就像...在早高峰的八通线上试图优雅地吃完一碗刚出锅的热汤面。不仅烫嘴,还容易洒一身,周围全是挤你的人(并发线程)。你以为截个图很简单?打开文件、解封装、找流、找解码器、解码、颜色空间转换、保存... 这一套流程下来,你不掉两根头发都对不起祖师爷。

别跟我提什么“优雅的封装”,在视频流处理面前,优雅就是个伪命题。为了性能,我们甚至得写出一些自己都恶心的代码。

来,给你们看点“传家宝”代码

这段代码是我从一个三年前烂尾的项目里扒出来的。当时为了赶上线,那是昏天黑地地写,变量名有点随意,你们凑合看。别问我为什么有些地方逻辑很怪,问就是为了过编译,问就是为了不Crash!!!

extern "C" { #include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>}// 别问为什么用全局变量,当时脑子瓦特了,懒得改static AVFormatContext* g_fmt_ctx = NULL;void do_the_dirty_job(const char* filepath) { // 注册所有组件,新版本可能不需要了,但加上总没错,求个心理安慰 // av_register_all(); // 打开文件,要是这就失败了,建议直接砸键盘 if (avformat_open_input(&g_fmt_ctx, filepath, NULL, NULL) != 0) { printf("文件都打不开,玩个锤子!\n"); return; } // 找流信息,这步要是卡住,多半是网络IO的问题 if (avformat_find_stream_info(g_fmt_ctx, NULL) < 0) { printf("流信息呢?被狗吃了?\n"); return; } int video_stream_index = -1; // 遍历流,找到视频流。就像在一堆绿豆里找个红豆 for (unsigned int i = 0; i < g_fmt_ctx->nb_streams; i++) { if (g_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { video_stream_index = i; break; } } if (video_stream_index == -1) { printf("连视频流都没有,你给我个MP3让我截图?\n"); return; } AVCodecParameters* pLocalCodecParameters = g_fmt_ctx->streams[video_stream_index]->codecpar; const AVCodec* pLocalCodec = avcodec_find_decoder(pLocalCodecParameters->codec_id); // 就算是烂代码,也要检查空指针,这是底线 if (pLocalCodec == NULL) { printf("解码器找不到,散了吧...\n"); return; } AVCodecContext* pCodecContext = avcodec_alloc_context3(pLocalCodec); avcodec_parameters_to_context(pCodecContext, pLocalCodecParameters); avcodec_open2(pCodecContext, pLocalCodec, NULL); AVPacket* pPacket = av_packet_alloc(); AVFrame* pFrame = av_frame_alloc(); AVFrame* pFrameRGB = av_frame_alloc(); // 存转换后的数据 // 准备SwsContext,这玩意儿最耗性能,别在循环里甚至每一帧都创建销毁! // 以前有个实习生这么干,服务器CPU直接飙到100%,我想把他祭天 struct SwsContext* sws_ctx = sws_getContext( pCodecContext->width, pCodecContext->height, pCodecContext->pix_fmt, pCodecContext->width, pCodecContext->height, AV_PIX_FMT_RGB24, // 转成RGB24,方便保存 SWS_BILINEAR, NULL, NULL, NULL ); int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1); uint8_t* buffer_legacy_mess = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t)); av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer_legacy_mess, AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1); int frame_count_tmp = 0; // 开始死循环读帧 while (av_read_frame(g_fmt_ctx, pPacket) >= 0) { if (pPacket->stream_index == video_stream_index) { // 发送包到解码器 if (avcodec_send_packet(pCodecContext, pPacket) == 0) { // 从解码器接收帧 while (avcodec_receive_frame(pCodecContext, pFrame) == 0) { // 只要第一帧!截完赶紧跑! // 别问为什么不seek,seek不准,硬解才是王道(其实就是懒) if (frame_count_tmp == 50) { // 随便截第50帧,别太较真 sws_scale(sws_ctx, (uint8_t const* const*)pFrame->data, pFrame->linesize, 0, pCodecContext->height, pFrameRGB->data, pFrameRGB->linesize); // 这里应该写保存文件的代码,但我累了 // 你们自己用fwrite把 pFrameRGB->data 怼进文件里去吧 // 记得加PPM头或者用stb_image_write printf("截图成功!虽然不知道截了个啥...\n"); goto cleanup; // 没错,我用了goto,咬我啊 } frame_count_tmp++; } } } av_packet_unref(pPacket); }cleanup: // 内存泄漏?不存在的,系统回收也是回收嘛(开玩笑的,记得释放) av_free(buffer_legacy_mess); av_frame_free(&pFrameRGB); av_frame_free(&pFrame); av_packet_free(&pPacket); avcodec_close(pCodecContext); avformat_close_input(&g_fmt_ctx);}

最后唠叨两句性能的事儿

哎不对,刚才说岔了,回来回来。代码虽然能跑,但你要是直接把这玩意儿扔到主线程里,你的UI绝对会卡死,卡得像2005年的IE浏览器一样。到时候产品经理拿着刀来找你,别怪我没提醒你。

  • 必须要异步:搞个队列,丢到后台线程去跑。
  • Sws_scale是CPU杀手:如果能用GPU做颜色转换最好,不能的话,别指望它能跑多快。
  • 内存对齐:FFmpeg对内存对齐很敏感,要是搞错了,画面就是歪的,或者是绿屏,看着跟恐怖片似的。

行了,我那破代码终于编译完了,虽然全是Warning,但只要Error是0我就当看不见。看了这么多,你要是觉得这老油条说得还有点道理,赶紧点个关注,不然下次你遇到Bug查资料的时候,只有百度广告没人理你,哼。

http://www.jsqmd.com/news/525587/

相关文章:

  • 融合镜像视界 Pixel-to-Space × 多视角融合 × 动态三维重构 × 无感定位 × 轨迹建模 × 行为认知 的空间计算体系
  • 【开题答辩全过程】以 基于springboot的扶贫系统为例,包含答辩的问题和答案
  • LinkedIn多账号怎么运营更安全?从养号到曝光的实操指南
  • 南北阁Nanbeige 4.1-3B MATLAB科学计算辅助工具开发
  • 2026,我们倾尽所有,想为大家办一场万人AI大会丨AIFUT。
  • 如何借助TradingAgents-CN实现智能金融决策?——多智能体协作驱动的量化交易解决方案
  • 携程大模型二面真题:知识库文本切块策略全攻略(非常详细),吃透这一篇就够了!
  • 零基础玩转Guohua Diffusion:国风水墨画一键生成,保姆级新手入门教程
  • 2026出国劳务优质服务商推荐指南:出国务工公司派遣、出国务工正规劳务公司、出国劳务出国务工、出国劳务哪里工资高选择指南 - 优质品牌商家
  • 解决方案:大麦抢票自动化系统实现高效票务获取
  • 2026年比较好的秸秆回收机厂家推荐:拖拉机牵引秸秆回收机精选公司 - 品牌宣传支持者
  • 拒绝手动对齐!用Clang-format在VSCode实现C++代码完美排版(附自定义宏处理方案)
  • 如何系统读懂波特图
  • Comsol相场断裂模拟:探索材料断裂奥秘的利器
  • OptiScaler完整指南:3步让所有显卡享受DLSS级画质提升
  • MindSpore vs PyTorch:深度学习框架对比指南
  • 救命神器!开源免费AI论文软件,千笔·专业学术智能体 VS 云笔AI
  • AI头像生成器与Stable Diffusion搭配使用:完整头像制作流程
  • LLaMA Factory + AutoGPTQ + vllm 三件套安装避坑指南(附常见错误解决方案)
  • 2026模块化售楼处优质服务商推荐榜覆盖全场景需求:创意集装箱售楼处/可定制的售楼处/可拆卸售楼处/可移动售楼处/选择指南 - 优质品牌商家
  • 零基础玩转Qwen2.5-7B-Instruct:5分钟搞定vLLM离线推理与前端调用
  • 造相Z-Image模型v2在医疗可视化中的应用:解剖图谱生成
  • 我的上课日记
  • 单细胞数据分析避坑指南:10X数据文件命名规范与Seurat对象构建常见错误
  • 凡是能被摄像机捕捉的,AI就能学会生成;凡是能被屏幕呈现的,就难以避免被复制
  • 2026 热门知识付费平台盘点,个人创作者真实体验分享
  • 嵌入式工程师的成本控制方法
  • 手把手教你用Gnuradio和HackRF实现FSK文本传输(附Python脚本)
  • HUNYUAN-MT 7B翻译终端MySQL数据翻译实战:数据库内容国际化处理
  • 玩转含风光储并网的IEEE33节点配电系统Simulink模型