简介这份资源是面向C#开发者的FFmpeg.AutoGen实战学习示例适合希望在.NET环境中处理音视频、又不想直接编写C/C代码的中级开发者。压缩包内共174个文件以111个C/C头文件、16个动态链接库、8个C#源码文件及若干工程配置、示例资源为主整体约55.53MB结构上兼顾了原生库依赖与托管代码示例。核心示例CSharpVideoDemo演示了如何通过NuGet引入FFmpeg.AutoGen绑定完成多媒体文件的打开与流信息读取、音视频解码、AVFrame帧处理与色彩空间转换以及编码封装、过滤器图构建和内存资源释放等关键环节并涉及异步处理思路。目前已有732人学习下载读者可据此理解FFmpeg API在C#中的调用方式掌握从解码到输出的完整链路并在此基础上扩展自定义滤镜或实时流媒体应用。1. FFmpeg.AutoGen 到底解决什么问题从一行 P/Invoke 到完整转码链路如果你用 C# 调过 FFmpeg大概率经历过这个场景avcodec_open2返回负数你翻遍文档也说不清是参数没填对还是结构体字段偏移错了。FFmpeg.AutoGen 就是冲着这个痛点来的——它把 FFmpeg 的 C 头文件自动翻译成 C# 的unsafe结构体、枚举和函数签名让你在 .NET 里直接以接近原生 C 的写法调用libavcodec、libavformat、libswscale。标题里的「代码例子.rar」本质上是一份可运行的调用样板集合覆盖解封装、解码、缩放、编码、封装这条主链路。它适合两类人一是需要在 Windows 服务或桌面端做媒体处理的 .NET 工程师二是想从 C 示例迁移到 C# 但不想手写几百行互操作声明的人。核心价值不是「封装得好用」而是「映射得准确」让你能对着 FFmpeg 官方示例逐行对照。2. 把 FFmpeg.AutoGen 跑起来环境、绑定与第一个可执行例子2.1 为什么选 AutoGen 而不是手写 P/Invoke手写 P/Invoke 的问题不在于难而在于结构体布局会随 FFmpeg 版本漂移。AVFrame在 FFmpeg 4.x 和 6.x 之间字段顺序变过AVCodecContext更是重灾区。你手写的[StructLayout]一旦对不上运行时不会报错只会读到垃圾内存表现为花屏、崩溃或者玄学般的随机失败。FFmpeg.AutoGen 的做法是用 ClangSharp 解析 FFmpeg 的公共头文件生成与头文件严格对应的 C# 代码。你用的 FFmpeg 动态库是哪个版本就生成哪个版本的绑定版本对齐这件事从「靠记忆」变成「靠工具」。选型上还有一条现实理由FFmpeg.AutoGen 生成的函数签名保留了AVCodecContext*这类指针参数意味着网上大量 C 语言示例可以近乎直译。你不需要在脑子里做一层「C# 封装 API」的翻译减少了出错面。2.2 最小可运行环境的三步搭建第一步是拿到与动态库匹配的 FFmpeg 二进制。Windows 上常见做法是用 gyan.dev 或 BtbN 的构建解压后把bin目录加入PATH或者复制到输出目录。注意 shared 版本和 static 版本的区别AutoGen 需要的是 shared 动态库因为它是运行时通过dlopen/LoadLibrary解析符号的。第二步是安装 NuGet 包。FFmpeg.AutoGen 的包名就是FFmpeg.AutoGen但它不包含 FFmpeg 本体只包含绑定代码。这一点新手最容易翻车以为装了包就能跑。第三步是注册动态库路径。FFmpeg.AutoGen 提供DynamicallyLoadedBindings.LibrariesPath和FFmpegLoader相关入口不同大版本 API 略有差异常见写法如下using FFmpeg.AutoGen; // 指向存放 avcodec-*.dll / libavcodec.so 的目录 ffmpeg.RootPath C:\ffmpeg\bin; // 触发一次符号解析确认绑定可用 Console.WriteLine($avcodec version: {ffmpeg.avcodec_version()});逻辑说明ffmpeg.RootPath是 AutoGen 用来拼接动态库文件名的根路径设置后它会在首次调用函数时加载对应库。avcodec_version()返回一个整数高 16 位是主版本低 16 位是次版本。参数说明路径必须是目录而不是具体 dll 文件且目录下文件名要符合 FFmpeg 的命名规范如avcodec-60.dll否则加载会抛DllNotFoundException。2.3 一个完整的解封装加解码骨架下面这段代码演示从打开文件到取出第一帧AVFrame的主流程是「代码例子.rar」里最该先吃透的部分using FFmpeg.AutoGen; unsafe { AVFormatContext* fmtCtx null; // 打开输入最后一个参数是 options这里传 null int ret ffmpeg.avformat_open_input(fmtCtx, input.mp4, null, null); if (ret 0) throw new Exception($open_input failed: {ret}); // 读取流信息必须调用否则 codecpar 是空的 ret ffmpeg.avformat_find_stream_info(fmtCtx, null); if (ret 0) throw new Exception($find_stream_info failed: {ret}); // 找第一条视频流 int vIdx ffmpeg.av_find_best_stream(fmtCtx, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, null, 0); if (vIdx 0) throw new Exception(no video stream); AVCodecParameters* par fmtCtx-streams[vIdx]-codecpar; AVCodec* codec ffmpeg.avcodec_find_decoder(par-codec_id); AVCodecContext* decCtx ffmpeg.avcodec_alloc_context3(codec); ffmpeg.avcodec_parameters_to_context(decCtx, par); ffmpeg.avcodec_open2(decCtx, codec, null); AVPacket* pkt ffmpeg.av_packet_alloc(); AVFrame* frame ffmpeg.av_frame_alloc(); while (ffmpeg.av_read_frame(fmtCtx, pkt) 0) { if (pkt-stream_index vIdx) { ffmpeg.avcodec_send_packet(decCtx, pkt); while (ffmpeg.avcodec_receive_frame(decCtx, frame) 0) { Console.WriteLine($frame {decCtx-frame_num}, {frame-width}x{frame-height}); ffmpeg.av_frame_unref(frame); } } ffmpeg.av_packet_unref(pkt); } }逻辑说明avformat_open_input负责探测容器格式avformat_find_stream_info会实际读一段数据来填充编码参数跳过它会导致codecpar里width/height为 0。解码采用 send/receive 模型一个 packet 可能产出多帧所以receive_frame要循环到返回负值为止。参数说明av_find_best_stream的第三个参数-1表示不指定期望的解码器让 FFmpeg 自己选avcodec_open2的第三个参数是AVDictionary**传null表示用默认配置需要开多线程时在这里传threads。3. 缩放、编码与封装把解码帧写回成新文件3.1 swscale 的像素格式转换与尺寸缩放解码出来的AVFrame通常是YUV420P而编码器或显示层可能要求NV12或RGB24。libswscale就是干这个的。核心是sws_getContext加sws_scaleSwsContext* sws ffmpeg.sws_getContext( srcW, srcH, srcFormat, // 输入宽高与像素格式 dstW, dstH, dstFormat, // 输出宽高与像素格式 (int)SwsFlags.SWS_BILINEAR, // 缩放算法 null, null, null); // 为输出帧分配缓冲区 AVFrame* dstFrame ffmpeg.av_frame_alloc(); dstFrame-format (int)dstFormat; dstFrame-width dstW; dstFrame-height dstH; ffmpeg.av_frame_get_buffer(dstFrame, 0); // 执行转换srcData/srcLinesize 来自源帧 ffmpeg.sws_scale(sws, srcFrame-data, srcFrame-linesize, 0, srcH, dstFrame-data, dstFrame-linesize);逻辑说明sws_getContext返回的上下文可以复用不要每帧都创建否则性能会断崖式下跌。av_frame_get_buffer的第二个参数是对齐要求传 0 让 FFmpeg 自动选择。参数说明SWS_BILINEAR是速度与质量的折中追求质量可用SWS_LANCZOS但 CPU 开销明显上升sws_scale的第四个参数是起始行第五个是行数做局部缩放时才需要改。3.2 编码器初始化与时间基处理编码比解码多两个坑一是AVCodecContext的time_base必须手动设置二是framerate和pix_fmt要在avcodec_open2之前填好。AVCodec* enc ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264); AVCodecContext* encCtx ffmpeg.avcodec_alloc_context3(enc); encCtx-width dstW; encCtx-height dstH; encCtx-pix_fmt AVPixelFormat.AV_PIX_FMT_YUV420P; encCtx-time_base new AVRational { num 1, den 25 }; // 25fps encCtx-bit_rate 2_000_000; encCtx-gop_size 50; ffmpeg.avcodec_open2(encCtx, enc, null);逻辑说明time_base是编码器内部计时单位1/25表示每帧间隔 1/25 秒。参数说明bit_rate单位是 bit/sgop_size控制关键帧间隔太小文件大太大 seek 不精确。编码时把dstFrame-pts按帧序号递增设置再avcodec_send_frame循环avcodec_receive_packet取包。3.3 用 libavformat 写出 MP4封装阶段要新建AVFormatContext添加流写头再逐包写出AVFormatContext* outCtx null; ffmpeg.avformat_alloc_output_context2(outCtx, null, null, output.mp4); AVStream* outStream ffmpeg.avformat_new_stream(outCtx, null); ffmpeg.avcodec_parameters_from_context(outStream-codecpar, encCtx); outStream-time_base encCtx-time_base; ffmpeg.avio_open(outCtx-pb, output.mp4, AvioFlags.AVIO_FLAG_WRITE); ffmpeg.avformat_write_header(outCtx, null); // 对每个编码包av_interleaved_write_frame(outCtx, pkt) ffmpeg.av_write_trailer(outCtx); ffmpeg.avio_closep(outCtx-pb);逻辑说明avformat_alloc_output_context2根据文件名推断封装格式avformat_write_header会写文件头av_write_trailer负责收尾。参数说明av_interleaved_write_frame会自动处理音视频交错单视频轨时也可以用av_write_frame。注意pkt-pts和pkt-dts要按输出流的time_base重新缩放否则播放器会显示错误时长。4. 避坑与排查FFmpeg.AutoGen 最常见的五类翻车4.1 现象DllNotFoundException或找不到入口点原因ffmpeg.RootPath没设或者目录里的 dll 文件名与 AutoGen 期望的不一致。AutoGen 按avcodec-{major}.dll这种模式拼接如果你手动改名成avcodec.dll就会失败。解决保持官方构建的原始文件名用ffmpeg.avcodec_version()先验证加载链路。4.2 现象解码第一帧就崩溃无异常堆栈原因AVFrame或AVPacket没有用av_frame_alloc/av_packet_alloc分配而是用了stackalloc或default。AutoGen 的结构体是指针语义必须走 FFmpeg 的分配函数。解决所有传给 FFmpeg 的结构体指针都从对应的_alloc函数来。4.3 现象画面颜色发绿或上下颠倒原因像素格式判断错误或者linesize为负。某些解码器输出YUVJ420P你按YUV420P处理就会偏色。解决打印frame-format和frame-linesize[0]用sws_getContext时传入实际格式不要硬编码。4.4 现象内存持续增长几小时后 OOM原因av_read_frame之后忘记av_packet_unref或者receive_frame之后忘记av_frame_unref。这两个函数负责释放包和帧内部的引用计数缓冲区。解决把 unref 放在循环末尾用try/finally保证执行。4.5 现象编码输出文件能播放但时长不对原因pts没有按输出流time_base做av_rescale_q。解码帧的 pts 基于解码器 time_base编码包基于编码器 time_base封装流又是另一个。解决在写包前统一做ffmpeg.av_rescale_q(pkt-pts, encCtx-time_base, outStream-time_base)。5. 进阶技巧用条件编译和版本判断让绑定跨版本存活FFmpeg.AutoGen 生成的代码是版本绑定的你针对 FFmpeg 6.1 生成的绑定换到 7.0 的动态库上可能因为符号缺失而崩溃。我一般会做两件事。第一件是用ffmpeg.avcodec_version()在启动时做一次版本断言把主版本号打出来和编译时用的头文件版本对比。如果主版本不一致直接抛异常并提示用户更换动态库而不是等到某个函数调用时才崩。第二件是把版本相关的 API 差异用条件编译包起来。比如avcodec_close在新版本里被标记为废弃推荐用avcodec_free_context。可以这样写#if FFMPEG_6_0_OR_GREATER ffmpeg.avcodec_free_context(decCtx); #else ffmpeg.avcodec_close(decCtx); ffmpeg.avcodec_free_context(decCtx); #endif逻辑说明FFMPEG_6_0_OR_GREATER是你在项目里自定义的编译符号根据引用的 AutoGen 包版本决定是否定义。参数说明avcodec_free_context接收二级指针调用后会把指针置空避免悬空引用。还有一个验证方法是写一个「冒烟测试」函数在程序启动时依次调用avcodec_version、avformat_version、avutil_version、swscale_version把四个版本号打印出来。这四个库的版本必须来自同一套构建混用不同来源的 dll 是血泪经验里最常见的翻车点——比如 avcodec 是 60avutil 是 58运行时会在某个结构体访问上直接段错误。最后说一个我自己的习惯每次升级 FFmpeg 动态库先跑一遍官方decode_video示例的 C# 移植版确认解封装、解码、缩放、编码、封装五步都通再动业务代码。这个习惯帮我省掉了至少三次「升级后线上花屏」的后悔药。希望帮到你。本文还有配套的精品资源点击获取
