嵌入式Linux下基于FFmpeg实现MP4解码为JPG图片(SSD202D交叉编译实战)
若该文为原创文章转载请注明原文出处。一、背景与目标最近在基于SigmaStar SSD202DARM Cortex-A7的嵌入式开发板上做一个视频抓图功能需要将MP4视频文件解码成单帧图片JPG格式。板子跑的是Linux资源有限所以打算使用FFmpeg库来实现。然而直接在板子上编译FFmpeg不现实必须在Ubuntu主机上交叉编译生成ARM架构的动态库和可执行程序。本文记录了从下载FFmpeg源码、配置交叉编译、解决各种依赖问题到编写一个简单的C程序完成MP4解码并输出JPG的全过程希望对类似项目的朋友有所帮助。二、环境准备主机Ubuntu 20.04 x86_64目标芯片SigmaStar SSD202DARMv7-A双核交叉编译工具链gcc-arm-8.2-2018.08-x86_64-arm-linux-gnueabihfGCC 8.2.1FFmpeg源码经适配SSD202硬件解码的版本来自DongshanPI仓库SDKSigmaStar SSD202D完整SDK包含mi_vdec.h等头文件和libmi_*.so硬件解码库三、下载FFmpeg源码选用已针对SSD20x系列芯片做过适配的版本仓库地址https://github.com/aaron201912/ffmpeg.git克隆到本地git clone https://github.com/aaron201912/ffmpeg.git cd ffmpeg/ffmpeg-4.1.3该版本包含config_for_ssd20x.sh配置脚本方便我们修改。四、编写配置脚本我们需要启用必要的组件avcodec、avformat、swscale、swresample并且要启用MJPEG编码器为了输出JPG同时禁用不必要的模块以减小体积。最终脚本config_for_ssd20x.sh内容如下注意根据实际情况调整SDK路径参数#!/bin/bash if [ -e $1 ]; then alkaid_path$1 else alkaid_path./../.. fi echo alkaid_path ${alkaid_path} cflags -I${alkaid_path}/project/release/include cflags -I./../3rdparty/include cflags -I./../3rdparty/include/sstar cflags -DFFMPEG_LIBRARY_VERSION... echo cflags ${cflags} ldflags -L${alkaid_path}/project/release/nvr/i2m/common/glibc/8.2.1/mi_libs/dynamic ldflags -L./../3rdparty/lib/8.2.1 echo ldflags ${ldflags} ./configure \ --prefixhost \ --enable-ffmpeg \ --disable-ffprobe \ --disable-ffplay \ --disable-symver \ --disable-doc \ --disable-htmlpages \ --disable-manpages \ --disable-podpages \ --disable-txtpages \ --disable-avresample \ --disable-avfilter \ --disable-avdevice \ --disable-postproc \ --enable-swscale \ --enable-swresample \ --enable-avcodec \ --enable-avformat \ --enable-small \ --enable-cross-compile \ --archarm \ --target-oslinux \ --cross-prefixarm-linux-gnueabihf- \ --enable-shared \ --disable-gpl \ --enable-nonfree \ --disable-openssl \ # 禁用OpenSSL不需要HTTPS --disable-zlib \ # 禁用zlib不需要PNG输出 --enable-pthreads \ --enable-error-resilience \ --enable-debug \ --enable-encodermjpeg \ # 启用MJPEG编码器 --disable-hwaccels \ --disable-muxers \ --disable-indevs \ --disable-outdevs \ --disable-devices \ --disable-filters \ --disable-decoderssh264_ss268 \ --disable-decodersshevc_ss268 \ --shlibdirhost/dynamic \ --libdirhost/static \ --pkg-configpkg-config \ --extra-cflags${cflags} -g -rdynamic -funwind-tables -ffunction-sections \ --extra-ldflags${ldflags}关键点指定了交叉编译工具链前缀arm-linux-gnueabihf-。显式启用--enable-encodermjpeg因为默认--disable-encoders会关掉所有编码器。禁用了openssl和zlib避免因交叉编译环境中缺少这些库而导致配置失败。保留了硬解码相关路径mi_vdec等但此处我们只用软解所以实际上硬解解码器被禁用了--disable-decoderssh264_ss268。五、编译FFmpeg执行配置假定SDK根目录为/home/yifeng/dirik_ssd20x_sdkcd ffmpeg-4.1.3 make distclean # 清理之前残留 sh config_for_ssd20x.sh /home/yifeng/ssd20x_sdk检查配置摘要中的Programs:行确保包含ffmpeg可选grep Programs: config.log然后编译安装make -j4 make install编译完成后库文件位于host/dynamic/下头文件在host/include/。六、遇到的典型问题及解决1. OpenSSL依赖导致ffmpeg程序未被构建现象配置后Programs:为空且没有生成ffmpeg可执行文件。原因脚本中--enable-openssl但交叉编译环境中缺少OpenSSL库configure自动跳过程序构建。解决删除--enable-openssl改用--disable-openssl。2. zlib缺失同样导致程序跳过解决添加--disable-zlib。3. MJPEG编码器找不到现象测试程序报错MJPEG encoder not found然后段错误。原因配置中使用了--disable-encoders导致所有编码器被禁用。解决改为--enable-encodermjpeg同时去掉--disable-encoders。4. 链接时找不到libswresample和MI_*符号现象编译测试程序时提示undefined reference to swr_alloc和MI_VDEC_*。原因缺少-lswresample链接。由于FFmpeg编译时开启了硬解支持libavcodec依赖SigmaStar的MI库libmi_vdec、libmi_sys等。解决链接时加上-lswresample和MI库路径及库名同时添加-ldl因为MI库依赖dlopen等。最终编译命令见下文。七、编写解码程序MP4转JPG程序功能使用FFmpeg API打开MP4文件读取视频流解码每一帧然后通过MJPEG编码器将帧编码为JPG并保存为文件。完整代码mp4_to_jpg.c#include stdio.h #include libavcodec/avcodec.h #include libavformat/avformat.h #include libswscale/swscale.h #include libavutil/imgutils.h int main(int argc, char *argv[]) { if (argc 2) { fprintf(stderr, Usage: %s input.mp4\n, argv[0]); return -1; } const char *input_file argv[1]; AVFormatContext *fmt_ctx NULL; AVCodecContext *dec_ctx NULL, *enc_ctx NULL; AVCodec *dec_codec NULL, *enc_codec NULL; AVStream *video_stream NULL; int video_stream_index -1; AVPacket pkt, enc_pkt; AVFrame *frame NULL, *yuv_frame NULL; struct SwsContext *sws_ctx NULL; int ret; int frame_count 0; char out_filename[64]; // 1. 打开输入文件 if ((ret avformat_open_input(fmt_ctx, input_file, NULL, NULL)) 0) { fprintf(stderr, Cannot open input file\n); return -1; } // 2. 查找视频流信息 if ((ret avformat_find_stream_info(fmt_ctx, NULL)) 0) { fprintf(stderr, Cannot find stream info\n); goto end; } for (int i 0; i fmt_ctx-nb_streams; i) { if (fmt_ctx-streams[i]-codecpar-codec_type AVMEDIA_TYPE_VIDEO) { video_stream_index i; video_stream fmt_ctx-streams[i]; break; } } if (video_stream_index -1) { fprintf(stderr, No video stream found\n); goto end; } AVCodecParameters *codecpar video_stream-codecpar; dec_codec avcodec_find_decoder(codecpar-codec_id); if (!dec_codec) { fprintf(stderr, Decoder not found\n); goto end; } // 3. 解码器上下文 dec_ctx avcodec_alloc_context3(dec_codec); avcodec_parameters_to_context(dec_ctx, codecpar); if ((ret avcodec_open2(dec_ctx, dec_codec, NULL)) 0) { fprintf(stderr, Cannot open decoder\n); goto end; } int width dec_ctx-width; int height dec_ctx-height; AVRational time_base video_stream-time_base; // 4. 分配帧 frame av_frame_alloc(); yuv_frame av_frame_alloc(); if (!frame || !yuv_frame) { fprintf(stderr, Frame allocation failed\n); goto end; } // 5. 准备 JPEG 编码器 enc_codec avcodec_find_encoder(AV_CODEC_ID_MJPEG); if (!enc_codec) { fprintf(stderr, MJPEG encoder not found\n); goto end; } enc_ctx avcodec_alloc_context3(enc_codec); if (!enc_ctx) { fprintf(stderr, Cannot allocate encoder context\n); goto end; } // 设置编码参数 enc_ctx-width width; enc_ctx-height height; enc_ctx-time_base time_base; enc_ctx-pix_fmt AV_PIX_FMT_YUVJ420P; // MJPEG常用格式 enc_ctx-flags | AV_CODEC_FLAG_QSCALE; enc_ctx-global_quality 90; // 质量1~100 enc_ctx-codec_type AVMEDIA_TYPE_VIDEO; if ((ret avcodec_open2(enc_ctx, enc_codec, NULL)) 0) { fprintf(stderr, Cannot open encoder\n); goto end; } // 6. 分配 YUV 帧缓冲区 int num_bytes av_image_get_buffer_size(enc_ctx-pix_fmt, width, height, 1); uint8_t *buffer (uint8_t *)av_malloc(num_bytes); av_image_fill_arrays(yuv_frame-data, yuv_frame-linesize, buffer, enc_ctx-pix_fmt, width, height, 1); yuv_frame-width width; yuv_frame-height height; yuv_frame-format enc_ctx-pix_fmt; // 7. 创建色彩空间转换从解码器格式 → YUVJ420P sws_ctx sws_getContext(width, height, dec_ctx-pix_fmt, width, height, enc_ctx-pix_fmt, SWS_BILINEAR, NULL, NULL, NULL); if (!sws_ctx) { fprintf(stderr, Cannot create sws context\n); goto end; } av_init_packet(pkt); pkt.data NULL; pkt.size 0; av_init_packet(enc_pkt); enc_pkt.data NULL; enc_pkt.size 0; // 8. 读包解码编码保存 while (av_read_frame(fmt_ctx, pkt) 0) { if (pkt.stream_index video_stream_index) { ret avcodec_send_packet(dec_ctx, pkt); if (ret 0) { fprintf(stderr, Error sending packet\n); av_packet_unref(pkt); continue; } while (ret 0) { ret avcodec_receive_frame(dec_ctx, frame); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) break; if (ret 0) { fprintf(stderr, Error receiving frame\n); goto end; } // 转换色彩空间 sws_scale(sws_ctx, (const uint8_t * const *)frame-data, frame-linesize, 0, height, yuv_frame-data, yuv_frame-linesize); // 编码为 JPEG yuv_frame-pts frame-pts; ret avcodec_send_frame(enc_ctx, yuv_frame); if (ret 0) { fprintf(stderr, Error sending frame to encoder\n); continue; } while (ret 0) { ret avcodec_receive_packet(enc_ctx, enc_pkt); if (ret AVERROR(EAGAIN) || ret AVERROR_EOF) break; if (ret 0) { fprintf(stderr, Error receiving packet from encoder\n); goto end; } // 保存为 .jpg 文件 snprintf(out_filename, sizeof(out_filename), frame_%04d.jpg, frame_count); FILE *fp fopen(out_filename, wb); if (!fp) { fprintf(stderr, Cannot open output file\n); } else { fwrite(enc_pkt.data, 1, enc_pkt.size, fp); fclose(fp); printf(Saved %s\n, out_filename); } av_packet_unref(enc_pkt); } } } av_packet_unref(pkt); } // 9. 冲刷解码器可能还有缓存的帧 avcodec_send_packet(dec_ctx, NULL); while (avcodec_receive_frame(dec_ctx, frame) 0) { sws_scale(sws_ctx, (const uint8_t * const *)frame-data, frame-linesize, 0, height, yuv_frame-data, yuv_frame-linesize); yuv_frame-pts frame-pts; avcodec_send_frame(enc_ctx, yuv_frame); while (avcodec_receive_packet(enc_ctx, enc_pkt) 0) { snprintf(out_filename, sizeof(out_filename), frame_%04d.jpg, frame_count); FILE *fp fopen(out_filename, wb); if (fp) { fwrite(enc_pkt.data, 1, enc_pkt.size, fp); fclose(fp); printf(Saved %s (flush)\n, out_filename); } av_packet_unref(enc_pkt); } } // 冲刷编码器最后一帧 avcodec_send_frame(enc_ctx, NULL); while (avcodec_receive_packet(enc_ctx, enc_pkt) 0) { snprintf(out_filename, sizeof(out_filename), frame_%04d.jpg, frame_count); FILE *fp fopen(out_filename, wb); if (fp) { fwrite(enc_pkt.data, 1, enc_pkt.size, fp); fclose(fp); printf(Saved %s (encoder flush)\n, out_filename); } av_packet_unref(enc_pkt); } printf(Decoded and encoded %d frames\n, frame_count); end: // 释放资源 av_packet_unref(pkt); av_packet_unref(enc_pkt); av_frame_free(frame); av_frame_free(yuv_frame); if (buffer) av_free(buffer); if (sws_ctx) sws_freeContext(sws_ctx); if (dec_ctx) avcodec_free_context(dec_ctx); if (enc_ctx) avcodec_free_context(enc_ctx); if (fmt_ctx) avformat_close_input(fmt_ctx); return 0; }八、交叉编译测试程序在FFmpeg源码根目录下执行确保已安装交叉编译工具链arm-linux-gnueabihf-gcc -o mp4_to_jpg mp4_to_jpg.c \ -I host/include \ -L host/dynamic \ -L /home/yifeng/dirik_ssd20x_sdk/project/release/nvr/i2m/common/glibc/8.2.1/mi_libs/dynamic \ -Wl,-rpath-link,host/dynamic \ -Wl,-rpath-link,/home/yifeng/ssd20x_sdk/project/release/nvr/i2m/common/glibc/8.2.1/mi_libs/dynamic \ -lavformat -lavcodec -lavutil -lswscale -lswresample \ -lmi_vdec -lmi_sys -lmi_common -ldl -lpthread -lm链接选项解释-I指定FFmpeg头文件路径。-L指定库文件搜索路径。-Wl,-rpath-link链接时指定共享库的依赖库搜索路径解决间接依赖。-l...链接必要的FFmpeg库和MI硬件解码库即使不用硬解libavcodec也可能依赖。-ldl因为MI库用到了dlopen等动态加载函数。若编译通过会生成ARM可执行文件mp4_to_jpg。九、部署与运行将mp4_to_jpg以及host/dynamic/下的所有.so文件拷贝到开发板的某个目录例如/usr/lib或程序同目录。拷贝一个测试MP4文件到板子运行./mp4_to_jpg test.mp4程序会逐帧解码并输出frame_0001.jpg、frame_0002.jpg…… 直到视频结束。十、总结与反思成功要点正确配置FFmpeg交叉编译的关键在于选项的裁剪既要满足功能解码编码又要避免不必要的依赖OpenSSL、zlib。链接顺序与依赖嵌入式环境中共享库的间接依赖需要显式指定-rpath-link和所需的附加库如-ldl。编码器启用如果想输出JPG必须启用MJPEG编码器而非一揽子禁用编码器。走过的弯路最初想生成ffmpeg命令行工具来截图但因依赖问题多次失败最终决定自己写程序调用库更灵活可控。忽略了MI库的依赖导致链接错误不断直到把SigmaStar SDK中的MI库路径加入才解决。后续可优化点若需高性能可启用硬件解码-DSUPPORT_4K并启用对应解码器但需确保SDK中硬件解码库可用。可增加参数控制输出帧间隔或分辨率。如有侵权或需要完整代码请及时联系博主。

相关新闻

2026年AI重罪基准测试:安途普、OpenAI等模型非法活动频发,谁能保障安全?

2026年AI重罪基准测试:安途普、OpenAI等模型非法活动频发,谁能保障安全?

AI重罪测试:安途普与OpenAI攻击规模惊人在2026年进行的重罪基准测试中,安途普(Anthropic)和OpenAI的模型表现令人担忧。从7月21日到8月9日期间,这两家公司的模型多次引发安全事件。其中,OpenAI在多个日期出…

2026/8/23 23:16:34 阅读更多 →
[实战] 2026年制造业数字化:工程图纸GDT识别与检验计划自动化指南

[实战] 2026年制造业数字化:工程图纸GDT识别与检验计划自动化指南

2026 年,在制造业全面迈向数字化的进程中,寻找专业的Infra CONVERT 中国代理及相关技术支持,已成为质量工程师构建高效 FMEA、FAI 及 PPAP 流程的关键环节。本文将从技术实务角度,探讨如何通过数字化手段处理工程图纸,…

2026/8/23 23:15:33 阅读更多 →
RAG检索增强生成技术全解析:从原理到落地的完整指南

RAG检索增强生成技术全解析:从原理到落地的完整指南

这里写自定义目录标题欢迎使用Markdown编辑器引言:RAG 为什么成为企业落地的刚需一、RAG 的核心原理二、RAG 的三大范式2.1 Naive RAG:最基础的实现2.2 Advanced RAG:针对性的优化2.3 Modular RAG:灵活组合的模块化架构三、RAG 的核心技术环节3.1 文档切分3.2 向量化与索引3.3 …

2026/8/23 23:15:33 阅读更多 →

最新新闻

OnlyOffice 中文字体修复记录

OnlyOffice 中文字体修复记录

字体问题排查与解决记录 日期:2026-08-09 环境:Debian GNU/Linux 13 (trixie),OnlyOffice 9.4.0 一、问题现象 OnlyOffice 打开来自 Windows/WPS 的 Office 文档时,出现排版错乱、文字空白、字体显示异常。 二、根因分析 1. 缺少 …

2026/8/24 2:53:58 阅读更多 →
大模型面试实战:从Transformer到分布式训练的深度解析

大模型面试实战:从Transformer到分布式训练的深度解析

1. 大模型面试通关秘籍:半年N面大厂实战复盘去年下半年,我密集参加了阿里、腾讯等多家头部企业的大模型相关岗位面试,从最初的屡战屡败到最终斩获多个高薪offer。这段经历让我深刻认识到:大模型岗位的面试已经形成了一套独特的考察…

2026/8/24 2:53:58 阅读更多 →
全栈开发从原型到上线的完整闭环:性能数据到底该怎么看

全栈开发从原型到上线的完整闭环:性能数据到底该怎么看

全栈开发从原型到上线的完整闭环:性能数据到底该怎么看说明:本文以全栈交付示例梳理测试与性能链路。文中指标和门槛需要依据业务 SLO、设备条件和压测结果调整。很多全栈开发者(尤其是基于 Next.js、Node.js、Prisma 和 React SSR 栈&#x…

2026/8/24 2:53:58 阅读更多 →
前端工程化与微前端架构方案落地:从最小可用方案搭起

前端工程化与微前端架构方案落地:从最小可用方案搭起

前端工程化与微前端架构方案落地:从最小可用方案搭起说明:本文的协作与架构问题均为说明性场景。规则可作为起点,仍应通过实际依赖图、契约测试和评审确认。几年前,微前端(Micro-Frontends)方案在前端圈大火…

2026/8/24 2:53:58 阅读更多 →
React 底层原理与大型应用架构实践:版本升级最怕忽略什么

React 底层原理与大型应用架构实践:版本升级最怕忽略什么

React 底层原理与大型应用架构实践:版本升级最怕忽略什么说明:本文以可复现的失效模式讲解 React 诊断。代码是简化示例,不能替代内存快照、集成测试和发布前回归。React 跨大版本升级不只是替换依赖和入口 API。自动批处理、Strict Mode 和 …

2026/8/24 2:53:58 阅读更多 →
6GB显存也能跑4K AI视频生成:ComfyUI低显存优化工作流实战

6GB显存也能跑4K AI视频生成:ComfyUI低显存优化工作流实战

这次我们来看一个在低显存环境下实现高清AI视频生成的项目。对于很多只有6GB显存的显卡用户来说,运行大型AI视频模型往往意味着显存不足和崩溃。但这个基于ComfyUI的图生视频工作流,通过一系列优化策略,让6GB显存的显卡也能稳定生成4K画质的视…

2026/8/24 2:52:58 阅读更多 →

日新闻

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践 前端安全依赖分层防护。没有任何单一配置能替代输出编码、权限校验和依赖更新。 把不可信内容当作数据 默认使用框架的转义能力;确需渲染 HTML 时,先在服务端或可信的客户端库中进行白名单过滤。避免把用户输入直接赋给 inne…

2026/8/24 1:08:15 阅读更多 →
Windows登录密码存储机制全解析:从哈希算法到安全加固实战

Windows登录密码存储机制全解析:从哈希算法到安全加固实战

1. 项目概述:Windows登录密码的“黑匣子”每次你按下CtrlAltDel,输入密码,然后看到那个熟悉的桌面,这背后发生了一系列复杂而精密的操作。作为一名长期与Windows系统打交道的从业者,我经常被问到:“我的密码…

2026/8/24 1:08:15 阅读更多 →
AI面试系统安全挑战与解决方案

AI面试系统安全挑战与解决方案

1. 项目概述:AI面试系统的安全挑战去年参与某跨国企业AI面试系统部署时,遇到一个典型案例:候选人在视频面试中无意提到竞争对手产品名称,系统竟自动将该信息关联到企业知识库并生成竞品分析报告。这个看似"智能"的功能&…

2026/8/24 1:08:15 阅读更多 →

周新闻

[光学原理与应用-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 阅读更多 →