
简介这是一份面向32位Windows平台、可配合Qt与MinGW环境使用的FFmpeg 3.4轻量级开发包定位于帮助C/C开发者接入音视频编解码、转码、封装与解封装能力适用于播放器、转码工具和媒体处理软件的原型开发与课程实践。压缩包共170个文件大小约2.62MB包含107个头文件、21个C源码以及def、a、dll、lib、pc等链接库与配置类型另有ffpreset预设、说明文本和少量可执行文件便于完成编译链接与环境验证也兼顾开发接口与运行库适合对资源占用较敏感的场景。预览中的AAC转码、封装、解封装等示例源码可辅助理解FFmpeg接口调用流程并结合头文件和导入库用于Qt工程集成、功能裁剪与二次开发。已有229人学习适合需要快速搭建32位开发环境、查阅示例代码和排查编译链接问题的初中级开发者参考。1. 32 位 MinGW 版的 FFmpeg 3.4一个还没退役的组合接手一套工控上位机的维护时最先卡住的往往不是业务逻辑而是工具链。Qt 5.x 配 MinGW 32 位跑在只装得动 32 位系统的旧机器上现场要求把本地 MP4 转码、把摄像头流截帧存盘。这时候想用最新 FFmpeg先得自己搭一套 i686 交叉编译环境啃一整天编译脚本第三方预编译包又大多来自 MSVC 体系混进 MinGW 工程要额外一层麻烦。ffmpeg-3.4-mingw32这类资源解决的就是这段落差一份用 32 位 MinGW 工具链编好的 FFmpeg 3.4 库头文件、导入库、运行时 DLL 都在解压进工程就能写avformat_open_input。它适合两类人被 32 位环境绑死、又需要稳定音视频解码的桌面/Qt 开发者以及想先把链路跑通、再考虑升级方案的工程实践者。下面按拆包、接入、跑通、踩坑的顺序讲。2. 拆开压缩包目录结构、库依赖与上手前的验证拿到一个预编译库最忌讳的是直接往工程里一塞然后开始改代码。先花十分钟把包拆明白后面能省掉大量 为什么链接不上 的排查时间。这一章讲清 MinGW 发行版的库文件组织方式、怎么在命令行里确认这套库真的是 32 位、以及 FFmpeg 3.4 在 API 上有哪些和 4.x/5.x 不一样的地方需要提前知道。2.1 一个 MinGW 发行版的库文件是怎么组织的常见布局是三个目录include/、lib/、bin/。include/下是libavcodec/、libavformat/、libavutil/、libswscale/、libswresample/、libavfilter/、libavdevice/、libpostproc/八套头文件目录lib/下放导入库import libraryMinGW 版本通常同时给.a和.lib两套内容等价只是后缀方便不同构建系统识别bin/下是运行时 DLL文件名带主版本号比如avcodec-57.dll、avformat-57.dll、avutil-55.dll、swscale-4.dll、swresample-2.dll。FFmpeg 的模块之间有严格的依赖方向链接顺序必须自顶向下模块3.4 对应的 DLL依赖谁libavdeviceavdevice-57.dllavformat、avcodec、avutillibavfilteravfilter-6.dllavformat、avcodec、swscale、swresample、avutillibavformatavformat-57.dllavcodec、avutillibavcodecavcodec-57.dllswresample、avutillibswscaleswscale-4.dllavutillibswresampleswresample-2.dllavutillibavutilavutil-55.dll无这张表的用处很直接gcc/ld是单趟扫描符号的被依赖的库必须写在依赖者的右边。只做解码 像素格式转换的话-lavformat -lavcodec -lswresample -lswscale -lavutil这个顺序就够了如果顺手加了-lavfilter却放在-lavformat后面很容易冒出一串undefined reference to av_...而报错的符号其实来自 avformat。include/目录里还有一个容易忽略的点3.4 的头文件内部大量使用#include libavutil/xxx.h这种相对路径所以INCLUDEPATH要指到include/这一层不能指到include/libavcodec/。指错了会看到libavutil/common.h: No such file or directory。2.2 用命令行验证这套库是不是 32 位、能不能链接别信目录名用工具确认。MinGW 环境里objdump和gcc -print-multiarch都可用一条命令看架构# 查看导入库的目标架构输出里应出现 pe-i386 / i386 字样 objdump -f /path/to/ffmpeg/lib/libavcodec.a | head -n 5 # 查看 DLL 的机器类型32 位是 0x014c64 位是 0x8664 objdump -f /path/to/ffmpeg/bin/avcodec-57.dll | head -n 3如果输出是file format pe-i386或architecture: i386说明确实是 32 位出现pe-x86-64或x86-64就是拿错了包后面gcc -m32链接一定会报file format not recognized或符号找不到。第二步做一次最小链接验证别一上来就开 Qt Creator。写个只有main的空程序把头文件和库全带上/* check_link.c —— 只验证头文件和符号能不能对上不跑逻辑 */ #include stdio.h #include libavformat/avformat.h #include libavcodec/avcodec.h #include libavutil/avutil.h int main(void) { /* av_register_all 是 3.x 的初始化入口4.0 之后被移除 */ av_register_all(); printf(avformat %u.%u.%u\n, LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO); return 0; }# 编译 链接 运行一条命令验证整条链路 gcc -m32 check_link.c -o check_link.exe \ -I/path/to/ffmpeg/include \ -L/path/to/ffmpeg/lib \ -lavformat -lavcodec -lswresample -lswscale -lavutil # 把 bin 目录加进 PATH 再运行否则会弹 找不到 avformat-57.dll PATH/path/to/ffmpeg/bin:$PATH ./check_link.exe这段验证的价值在于把问题分层。编译报错说明INCLUDEPATH不对链接报错说明库路径、库名或顺序不对编译链接都过、一运行就闪退说明 DLL 没找到。三个阶段的错误信息完全不同分开验证比在 Qt 里对着collect2.exe: error: ld returned 1 exit status猜要快得多。参数上唯一需要留意的是-m32MinGW-w64 的 32 位发行版比如常见的 i686 前缀工具链默认就产出 32 位加不加都行如果是 x86_64 前缀的工具链-m32需要 multilib 支持多数发行版并不带这时候别硬扛换 32 位工具链更省事。2.3 版本 3.4 的 API 边界哪些函数还能用、哪些已经没了这不是一篇 API 历史考据但有几处差异必须在动手前记住否则从网上抄来的代码有一半编译不过。3.4 属于 3.x 分支末期处在老 API 和新 API 并存的阶段av_register_all()、avformat_network_init()仍然需要显式调用4.0 起前者被移除、后者变成可选。解码仍然可以用avcodec_decode_video2(ctx, frame, got, pkt)这个函数在 3.4 里正常工作4.0 标记废弃5.0 移除同时avcodec_send_packet/avcodec_receive_frame在 3.4 里也已经可用两者可以混着理解但不要在同一个解码上下文里混用。流参数从AVStream::codec旧的AVCodecContext*迁移到AVStream::codecparAVCodecParameters*这件事在 3.4 已经完成必须走avcodec_parameters_to_context(ctx, stream-codecpar)这条路直接读stream-codec会编译报错——这是从 2.x 时代老教程抄代码时最常见的一处断裂。AVPacket的内存管理也换了av_packet_alloc()/av_packet_free()配套使用读帧后要av_packet_unref()老的av_init_packet 手动av_free_packet虽然还能编译但容易漏掉buf引用计数在长时间运行的转码循环里表现为内存缓慢上涨。我一般在新代码里一律用 alloc/free 那套老接口只在维护存量代码时保留。提示如果你手上的第三方教程出现avcodec_encode_video、avcodec_decode_audio3、avcodec_alloc_frame那是 1.x 时代的接口3.4 里已经全部不存在照抄必然编译失败别浪费时间怀疑环境。3. 接到 Qt 工程里.pro 配置、链接顺序与编译宏Qt MinGW 的组合在 Windows 上有个绕不开的现实qmake 生成的是 MinGW 的 Makefile最终调用的是g和ld所以对库的要求跟命令行验证时一致。但.pro文件里的LIBS展开顺序、C 头文件在 C 里的编译宏、以及运行时 DLL 的部署位置这三件事各有各的坑。这一章把它们逐个拆开。3.1 .pro 文件的完整写法与链接顺序原理一个能用的.pro长这样# 工程基础配置 QT core gui CONFIG c11 console TARGET player_demo TEMPLATE app # 头文件搜索路径指到 include 这一层 INCLUDEPATH $$PWD/third_party/ffmpeg/include # 库搜索路径 链接顺序上层在前底层在后 LIBS -L$$PWD/third_party/ffmpeg/lib \ -lavformat \ -lavcodec \ -lswresample \ -lswscale \ -lavutil$$PWD是.pro文件所在目录用相对路径的好处是工程换机器不用改配置。三个参数层面要说清楚CONFIG console在 Windows 下会保留控制台窗口调试阶段非常有用——FFmpeg 的日志通过av_log_set_callback输出的内容默认打到 stderr没有控制台就只能靠写文件。发布时再去掉这一行。INCLUDEPATH只写一个include根目录不要在代码里写#include libavcodec/avcodec.h之外的路径前缀头文件内部的相对引用才能正常展开。LIBS里的-L和-l顺序不能颠倒也不可能靠多写几遍来糊弄。我见过有人在末尾追加一遍-lavcodec来修复未定义符号在ld的单趟扫描规则下这种补丁偶尔生效偶尔不生效取决于符号在库内的物理位置属于典型的玄学修复。正确做法是按 2.1 那张依赖表排好序一次到位。3.2 头文件引入方式与__STDC_CONSTANT_MACROSC 工程里引 C 头文件必须包extern C否则 C 的名字修饰规则会让链接器找不到av_register_all这类符号。标准写法是一个集中头文件/* ffmpeg_headers.h —— 全工程只在这里包一次 extern C */ #ifndef FFMPEG_HEADERS_H #define FFMPEG_HEADERS_H /* 3.x 在 C 下编译需要这个宏否则 UINT64_C 等宏未定义 */ #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS #endif extern C { #include libavformat/avformat.h #include libavcodec/avcodec.h #include libavutil/avutil.h #include libavutil/imgutils.h #include libavutil/opt.h #include libswscale/swscale.h #include libswresample/swresample.h } #endif /* FFMPEG_HEADERS_H */__STDC_CONSTANT_MACROS这个宏不是可选项。FFmpeg 的libavutil/common.h里有INT64_C、UINT64_C这类宏C 标准规定这些宏在 C 模式下默认不定义只有显式开启__STDC_CONSTANT_MACROSC11 之前或__STDC_LIMIT_MACROS才可见。不定义它报错信息是UINT64_C was not declared in this scope看起来跟 FFmpeg 毫无关系新手很容易在这里卡半天。另一种做法是在.pro里全局加DEFINES __STDC_CONSTANT_MACROS效果一样。我倾向于写在头文件里因为这样这个依赖跟着头文件走别人把源文件拷走也能编过。3.3 运行时 DLL 部署与调试器配置编译链接通过只是第一步运行阶段最常见的是进程一启动就退出、没有任何提示。原因基本只有一个avformat-57.dll这一串 DLL 不在进程的搜索路径里。Qt Creator 默认工作目录是构建目录PATH继承自 IDE 启动时的环境。三种部署方式按场景选开发阶段最省事的做法是在.pro里加拷贝规则每次构建把 DLL 同步到输出目录# 构建后自动拷贝 FFmpeg 运行库到可执行文件旁边 FFMPEG_BIN $$PWD/third_party/ffmpeg/bin win32 { DLLS $$files($$FFMPEG_BIN/*.dll) for(dll, DLLS) { QMAKE_POST_LINK $$quote(cmd /c copy /y \$$shell_path($$dll)\ \$$shell_path($$OUT_PWD/release)\ $$escape_expand(\\n\\t)) } }$$files()是 qmake 的通配函数QMAKE_POST_LINK在链接完成后执行$$OUT_PWD是构建输出目录。注意$$escape_expand(\\n\\t)的作用是让多条命令在生成的 Makefile 里分行不加会被拼成一行导致第二条失败。发布阶段则把所有 DLL 和 exe 放在同一目录或者用windeployqt把 Qt 的运行库一起收拢。这里有个细节windeployqt只处理 Qt 自己的依赖FFmpeg 的 DLL 要自己拷。调试器配置是另一件事。MinGW 的gdb在加载 FFmpeg 时偶尔会因为缺少调试信息而在某些栈帧上显示??这是正常的——预编译库不带.debug段。如果需要在崩溃时定位到 FFmpeg 内部可以临时把bin/下的avcodec-57.dll换成带调试符号的版本但工程里通常不需要。注意不要把ffmpeg/bin直接加进系统环境变量PATH来解决 DLL 问题。同一台机器上如果还有别的软件带着不同版本的 FFmpeg DLL会按 PATH 顺序加载先命中的那个版本可能就是错的表现为程序在别人机器上能跑、在原机器上崩。DLL 跟 exe 同目录是最稳的顺序。4. 跑通第一条解码链路从 MP4 到 QImage配置通了接下来验证这套库到底能不能干活。选解码是因为它最容易观察到结果——一帧画面出来了就是成了不出来就是哪里错了没有中间态。这一章按 3.4 的 API 走一遍完整解码序列然后处理sws_scale转 RGB32 时的行对齐问题最后反过来把一帧 QImage 推成 H.264验证编码方向也能用。4.1 3.4 版的解码调用序列一次完整的解码由固定的六步构成顺序错了任何一步都会返回负数avformat_open_input打开容器avformat_find_stream_info补齐流参数av_find_best_stream找到视频流的索引和对应的AVCodecavcodec_alloc_context3建解码上下文avcodec_parameters_to_context把codecpar里的参数灌进去avcodec_open2打开循环av_read_frame读包只处理视频流丢给avcodec_decode_video2got_picture为真时拿到AVFrame交给sws_scale转格式收尾阶段给解码器送一个空包 flush把 B 帧缓冲的帧取出来然后逐级释放。第 6 步很容易被漏掉。H.264 有 B 帧解码器内部会缓存若干帧等参考帧到齐文件读到 EOF 时缓冲区里还压着几帧。不 flush 的话一个 10 秒的片子可能只解出前 9 秒多最后几帧永远出不来。3.4 里的做法是把一个空的AVPacketdata NULL, size 0送进avcodec_decode_video2直到返回 0 且got_picture为假。4.2 完整代码封装一个最简解码器类把上面的流程封成类方便后面复用。这里给的是最简版本去掉了业务逻辑只保留解码和转 RGB/* simple_decoder.h */ #ifndef SIMPLE_DECODER_H #define SIMPLE_DECODER_H #include QImage #include QString #include ffmpeg_headers.h class SimpleDecoder { public: SimpleDecoder(); ~SimpleDecoder(); bool open(const QString filePath); // 打开文件成功返回 true bool nextFrame(QImage outImage); // 取下一帧并转 RGB32无帧返回 false void close(); private: bool decodePacket(AVPacket *pkt, QImage outImage); AVFormatContext *m_fmtCtx nullptr; AVCodecContext *m_codecCtx nullptr; int m_streamIdx -1; AVPacket *m_packet nullptr; AVFrame *m_frame nullptr; SwsContext *m_swsCtx nullptr; bool m_eof false; // 文件读完后进入 flush 阶段 }; #endif/* simple_decoder.cpp —— 核心解码逻辑 */ #include simple_decoder.h SimpleDecoder::SimpleDecoder() {} SimpleDecoder::~SimpleDecoder() { close(); } bool SimpleDecoder::open(const QString filePath) { /* 3.x 必须显式注册4.0 之后移除 */ av_register_all(); /* Windows 下路径要转本地编码直接传 QString 的 utf8 会失败 */ QByteArray path filePath.toLocal8Bit(); if (avformat_open_input(m_fmtCtx, path.constData(), nullptr, nullptr) ! 0) return false; if (avformat_find_stream_info(m_fmtCtx, nullptr) 0) return false; /* 找到最好的视频流同时拿到解码器 */ const AVCodec *codec nullptr; m_streamIdx av_find_best_stream(m_fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, codec, 0); if (m_streamIdx 0 || !codec) return false; m_codecCtx avcodec_alloc_context3(codec); /* 3.x 必须走 codecpar 拷贝stream-codec 已不可用 */ avcodec_parameters_to_context(m_codecCtx, m_fmtCtx-streams[m_streamIdx]-codecpar); /* 单线程解码线程数设 0 让 FFmpeg 自己决定调试时可固定为 1 便于定位 */ m_codecCtx-thread_count 1; if (avcodec_open2(m_codecCtx, codec, nullptr) 0) return false; m_packet av_packet_alloc(); m_frame av_frame_alloc(); return m_packet m_frame; } bool SimpleDecoder::decodePacket(AVPacket *pkt, QImage outImage) { int got 0; /* 3.4 里 avcodec_decode_video2 仍可用返回值为消耗的字节数 */ int ret avcodec_decode_video2(m_codecCtx, m_frame, got, pkt); if (ret 0 || !got) return false; /* 第一帧拿到后再建 sws 上下文避免拿不到 pix_fmt 时尺寸未知 */ if (!m_swsCtx) { m_swsCtx sws_getContext(m_frame-width, m_frame-height, static_castAVPixelFormat(m_frame-format), m_frame-width, m_frame-height, AV_PIX_FMT_BGRA, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!m_swsCtx) return false; } /* Format_RGB32 在小端机器上的内存布局就是 BGRA正好对应 AV_PIX_FMT_BGRA */ outImage QImage(m_frame-width, m_frame-height, QImage::Format_RGB32); uint8_t *dstData[4] { outImage.bits(), nullptr, nullptr, nullptr }; /* 关键用 bytesPerLine() 而不是 width*4Qt 会按 4 字节对齐补行尾 */ int dstLinesize[4] { static_castint(outImage.bytesPerLine()), 0, 0, 0 }; sws_scale(m_swsCtx, m_frame-data, m_frame-linesize, 0, m_frame-height, dstData, dstLinesize); return true; } bool SimpleDecoder::nextFrame(QImage outImage) { while (true) { if (!m_eof) { int ret av_read_frame(m_fmtCtx, m_packet); if (ret 0) { /* 读完了进入 flush 阶段送空包把缓冲的帧挤出来 */ m_eof true; av_packet_unref(m_packet); m_packet-data nullptr; m_packet-size 0; if (decodePacket(m_packet, outImage)) return true; return false; } if (m_packet-stream_index ! m_streamIdx) { av_packet_unref(m_packet); /* 音频包等直接丢不释放会漏内存 */ continue; } bool ok decodePacket(m_packet, outImage); av_packet_unref(m_packet); if (ok) return true; } else { /* flush 阶段持续送空包直到解码器不再吐帧 */ if (decodePacket(m_packet, outImage)) return true; return false; } } } void SimpleDecoder::close() { if (m_swsCtx) { sws_freeContext(m_swsCtx); m_swsCtx nullptr; } if (m_frame) { av_frame_free(m_frame); } if (m_packet) { av_packet_free(m_packet); } if (m_codecCtx) { avcodec_free_context(m_codecCtx); } if (m_fmtCtx) { avformat_close_input(m_fmtCtx); } }几处参数值得单独说。thread_count 1不是性能建议而是调试手段多线程解码下avcodec_decode_video2的返回时机和帧序在某些编码上会变定位花屏问题时先单线程复现确认是解码逻辑问题还是线程调度问题。toLocal8Bit()也不能省。avformat_open_input在 Windows 上走的是fopen系的窄字符接口接收的是本地 ANSI 编码。直接传QString::toUtf8()的字节路径里有中文时打开失败报的错是No such file or directory但文件明明存在——这个坑非常经典。m_packet-data nullptr这种写法利用了av_packet_unref之后data已被置空、size归零的特性实际上av_packet_unref会把整个包重置成一个合法的空包可以直接复用。不要在这里av_packet_free再av_packet_alloc循环里频繁分配释放会拖慢整体吞吐。4.3sws_scale转 RGB32 与行对齐sws_scale的签名里最关键的是 source/destination 的linesize数组。FFmpeg 的AVFrame每一行之间可能有 padding用于 SIMD 对齐所以frame-linesize[0]通常大于width * 4Qt 的QImage出于同样的原因bytesPerLine()也可能大于width * 4。两边都用各自的真实行宽sws_scale才知道每一行从哪开始。如果这里图省事写成int dstLinesize[4] { m_frame-width * 4, 0, 0, 0 }; // 错误示范当width * 4恰好是 4 的倍数但bytesPerLine()里带了额外对齐时输出图右侧会出现一条宽度不固定的彩色竖带——因为sws_scale按错误的行宽往右写把行尾的 padding 也当成了有效像素。4:3、16:9 这类常见比例大多凑巧没事换成 1:1 或者奇数宽度的素材就翻车所以从一开始就按正确写法来。像素格式的选择上AV_PIX_FMT_BGRA对应QImage::Format_RGB32小端机器AV_PIX_FMT_RGBA对应QImage::Format_ARGB32注意 Qt 的 ARGB32 在小端内存里也是 BGRA 顺序容易混淆。如果希望减少一次通道交换AV_PIX_FMT_RGB32这个宏在 FFmpeg 里就是本地字节序的 RGB32跟QImage::Format_RGB32的内存布局完全一致可以直接用。提示sws_scale返回的是输出行数正常应等于m_frame-height。如果返回值小于高度通常是目标缓冲区不够大或者dstLinesize设错了别直接当成功处理。4.4 编码方向把 QImage 推成 H.264解码跑通之后编码方向值得一起验证因为这套 3.4 的库如果只带解码器没带编码器有些精简发行版会裁要到这一步才会发现。检查方式是avcodec_find_encoder(AV_CODEC_ID_H264)返回非空。编码侧的关键在于像素格式。libx264只接受 YUV420P或 NV12 等QImage 是 RGB32所以要多一次sws_scale做 RGB → YUV420P 的转换而且 YUV420P 的 U/V 平面是width/2 × height/2宽高必须是偶数奇数尺寸的图要先裁到偶数。/* 编码一帧的骨架上下文创建部分省略重点看转换和送帧顺序 */ AVFrame *yuvFrame av_frame_alloc(); yuvFrame-format AV_PIX_FMT_YUV420P; yuvFrame-width srcImage.width() ~1; /* 向下取偶 */ yuvFrame-height srcImage.height() ~1; av_frame_get_buffer(yuvFrame, 32); /* 32 字节对齐x264 要求 */ SwsContext *toYuv sws_getContext( srcImage.width(), srcImage.height(), AV_PIX_FMT_BGRA, yuvFrame-width, yuvFrame-height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr); const uint8_t *srcData[4] { srcImage.bits(), nullptr, nullptr, nullptr }; int srcLinesize[4] { static_castint(srcImage.bytesPerLine()), 0, 0, 0 }; sws_scale(toYuv, srcData, srcLinesize, 0, yuvFrame-height, yuvFrame-data, yuvFrame-linesize); /* 3.4 编码用 avcodec_encode_video2和 avcodec_decode_video2 对称 */ AVPacket *pkt av_packet_alloc(); int got 0; avcodec_encode_video2(encCtx, pkt, yuvFrame, got); if (got) { /* 必须设置 pts否则 x264 报 non-strictly-monotonic PTS */ pkt-pts frameIndex; av_interleaved_write_frame(outFmtCtx, pkt); av_packet_unref(pkt); }av_frame_get_buffer的第二个参数是对齐字节数传 32 是给 SIMD 优化留空间传 1 也能跑但性能下降。pkt-pts必须单调递增3.4 里avcodec_encode_video2不帮你算自己维护一个帧计数器最省心。时间基换算在avcodec_parameters_to_context之后用encCtx-time_base {1, fps}显式设好别指望默认值——默认值不一定和容器时间基一致写出来的文件播放速度会不对。5. 避坑与排查32 位 MinGW 下最常翻车的五件事前面几章讲的是应该怎么做这一章讲做不成的时候怎么查。下面五条都是在实际工程里反复出现的问题按现象、原因、解决的顺序写可以当检查表用。5.1 链接期undefined reference的三种来源现象编译通过链接阶段冒出一屏undefined reference to avformat_open_input或者av_register_allcollect2.exe: error: ld returned 1 exit status。原因分三类按出现频率排。第一类是LIBS顺序反了-lavutil写在-lavformat前面ld扫描到 avformat 时 avutil 已经处理完符号没被引用就被丢弃。第二类是库路径压根没写对-L指到了lib/的上一级或者指到了一个空目录ld找不到libavformat.a就跳过不报库不存在而报符号未定义非常有误导性。第三类是真拿错了包库是 MSVC 编译的.libMinGW 的ld能识别 PE 格式但不认 MSVC 的名字修饰约定链接时报的符号名对不上。解决先按 2.2 的命令行验证法在终端里跑一遍把 Qt 排除在外。命令行能过就是.pro的问题命令行走不过就是库的问题。检查顺序固定为-L路径存在且有.a文件 → 库名拼写-lavformat对应libavformat.a→ 顺序按依赖表排 → 是否混入了 MSVC 的库。5.2 运行期缺 DLL、0xc000007b与路径中文现象编译链接全部通过双击 exe 或从 Qt Creator 启动进程毫无反应地退出或者弹一个0xc000007b的错误框。原因进程启动时加载器找不到avformat-57.dll不同 Windows 版本的表现不一样——有的静默退出有的报模块错误。0xc000007b是 32/64 位不匹配的典型码说明找到的 DLL 是 64 位的或者依赖链里某一环是 64 位。还有一个隐蔽来源exe 同目录下混进了另一套 FFmpeg 的 DLL比如别的软件留下的版本号相同但编译方式不同加载后在某些函数指针上崩溃。解决exe 目录下必须凑齐一套同源的 DLL不要靠系统PATH。用dumpbin /dependents或者 MinGW 的objdump -p xxx.exe | grep DLL Name看依赖列表逐个核对。0xc000007b时用objdump -f检查每个 DLL 的架构找出那个 x86-64 的。中文路径的问题在 4.2 已经提过统一转toLocal8Bit()。5.3 解码期花屏、绿边与 B 帧延迟现象画面能出来但右侧有彩色竖条或者整帧偏绿或者视频播到倒数几秒就没了。原因绿边是sws_scale的目标linesize用错了4.3 讲过QImage的bytesPerLine()大于width * 4时按width * 4写会把行尾的 padding 区域覆盖成相邻行的数据表现为右侧一条歪斜的彩带。整帧偏绿通常是像素格式对错了通道顺序把AV_PIX_FMT_BGRA和AV_PIX_FMT_RGBA搞混红蓝通道交换人脸发青、天空发红。最后几秒丢失是没做 flush解码器缓冲区里的帧没取出来。解决目标linesize一律用QImage::bytesPerLine()像素格式对照 4.3 那张小表确认nextFrame在av_read_frame返回小于 0 之后必须持续送空包直到不再出帧代码骨架见 4.2 的nextFrame。5.4 内存期32 位地址空间与 4K 素材现象处理小分辨率素材一切正常换到 1080p 以上的长视频跑几分钟后崩崩溃点随机有时在av_read_frame有时在 Qt 的图像绘制里。原因32 位进程的用户态地址空间只有 2GB 左右还要被 Qt、显卡驱动、系统 DLL 占掉一块。一帧 4K 的 BGRA 是3840 × 2160 × 4 ≈ 31.6MB加上 YUV 原始帧、sws_scale的内部缓冲、解码器的参考帧队列单帧开销能到 100MB 量级。如果每帧都 new 一个QImage又不及时释放堆碎片化之后即使总量没到 2GB 也会分配失败。解决三件事。第一sws_getContext只创建一次不要每帧建了再销毁——这个上下文内部有缓冲区反复创建销毁是内存碎片的头号来源。第二QImage复用同一个对象SimpleDecoder那种每次outImage QImage(...)的写法在 demo 里没问题正式工程应该让调用方传一个预先分配好的缓冲区或者用帧池管理。第三真的需要处理 4K 素材时先把分辨率降到 720p 解码sws_scale里目标宽高设小或者干脆评估 64 位工具链的可行性。5.5 编码期时间戳与 GOP 设置现象编码出来的 H.264 文件能播但播放器上拖动进度条位置不对或者画面卡顿、帧率明显不对。原因pkt-pts没设、设成 0、或者没有单调递增。avcodec_encode_video2在 3.4 里不做自动推算pts缺省是AV_NOPTS_VALUE落进文件后播放器只能按码率猜位置拖动就不准。另一种情况是time_base没跟容器对齐编码器用1/25、容器用1/1000写出去的时间戳差 40 倍播放速度直接错乱。解决encCtx-time_base AVRational{1, fps}显式设置pkt-pts frameIndex维护单调计数av_interleaved_write_frame之前不要手动改pts。GOP 相关的encCtx-gop_size一般设成fps * 2两秒一个关键帧设太大会导致拖动时定位慢设太小文件体积涨。另外encCtx-max_b_frames在需要低延迟的场合比如实时推流设成 0避免编码器内部再引入一层缓冲延迟。6. 进阶把这套老库用稳的几个习惯围绕 3.4 这个版本做工程最实用的习惯是给运行环境加一层自检。启动时用avcodec_configuration()打印一遍编译配置确认--enable-libx264、--enable-decoderh264这些关键开关真的开着——精简发行版经常裁掉编码器等到用户点了导出才报错就太晚了。再配一份avcodec_find_decoder_by_name(h264)/avcodec_find_encoder(AV_CODEC_ID_H264)的存在性检查缺哪个就在启动阶段提示而不是让程序跑到一半崩。另一个习惯是给日志分级。av_log_set_level(AV_LOG_WARNING)在发布版里避免日志刷屏调试时调到AV_LOG_DEBUG并且用av_log_set_callback把输出接到 Qt 的日志系统里这样 FFmpeg 内部报的Invalid NAL unit size、non-existing PPS这类信息才能和业务日志对上时间线排查现场问题时有据可查。帧池的做法值得单独提一句维护一个固定大小的QImage队列解码器复用队首对象写入消费端用完归还。在 32 位地址空间紧张的机器上这一招能把长时间运行的崩溃率压下来一大截。参数上池大小取 35 帧就够太多反而占内存。我最早接这套库的时候图省事跳过了 2.2 那个命令行验证直接在 Qt Creator 里改.pro结果在一个链接顺序上耗了整整一个下午——报错的符号指向 avcodec实际问题出在 avformat 的位置。从那以后我每次拿到预编译库都强制先在终端里用最小main跑一遍编译链接运行三连十分钟的事能把后面几小时的不确定性砍掉。希望帮到你。本文还有配套的精品资源点击获取