简介:本资源是一套面向C#开发者的学习实践包,聚焦FFmpeg.AutoGen原生绑定库在音视频处理中的工程化应用,适用于多媒体开发初学者及希望深入理解FFmpeg底层调用机制的中阶程序员。压缩包含174个文件,主体为111个C头文件(h)与8个核心C#源码(cs),辅以16个动态链接库(dll)、3个可执行示例(exe)及配套配置(App.config、packages.config)、窗体设计(frmPlayer.cs/.Designer.cs)、资源管理(Resources.Designer.cs)和项目构建文件(.sln、.csproj),完整呈现一个可运行的CSharpVideoDemo视频处理演示工程。资源大小55.53MB,结构清晰、模块分明,涵盖输入封装、解码器初始化、AVFrame帧处理、sws_scale色彩转换、过滤器图构建等关键流程的代码实现与注释。目前已有732人学习下载,读者可直接编译运行、逐层调试,掌握C#调用FFmpeg原生API进行音视频读取、解码、渲染及基础滤镜集成的全流程实践能力。
1. FFmpeg.AutoGen 不是“FFmpeg 的 C# 封装”,而是让你在 .NET 里安全调用原生 FFmpeg ABI 的最后一道可信桥梁
你手头有个.rar文件,名字叫FFmpeg.AutoGen代码例子.rar——它不是某个神秘组织发布的破解包,也不是某位博主打包的“免编译懒人版”,而极大概率是某位 C# 工程师在 Windows 上跑通 FFmpeg 原生能力后,把最小可运行示例(含预编译 DLL、C# 调用层、关键初始化逻辑和一个解码/转码/推流片段)压缩打包的实战快照。很多人第一次点开这个压缩包时会困惑:为什么不用更“高级”的封装如FFMpegCore或VideoLAN.LibVLC?因为那些库在底层仍绕不开 FFmpeg.AutoGen —— 它不提供业务逻辑,只做一件事:把 libavcodec.dll、libavformat.dll 这些二进制黑匣子,以类型安全、内存可控、P/Invoke 可追溯的方式,钉死在 .NET 运行时的 ABI 边界上。这意味着:你写AVCodecContext* ctx = ffmpeg.avcodec_alloc_context3(codec),编译器能校验指针类型,调试器能看到ctx->width的实时值,GC 不会误回收正在被解码器持有的 native buffer。这不是“教程”,而是生产级音视频集成的起点:当你的需求超出ffmpeg -i in.mp4 -c:v libx264 out.mp4的表达力——比如要注入自定义 AVPacket 时间戳、拦截解码中间帧做 AI 推理、或在 Unity 中低延迟渲染 RTSP 流——你就必须亲手穿过这道门。本文不讲“怎么安装 FFmpeg”,不教“ffmpeg 命令大全”,只聚焦于:如何从这个.rar包出发,让FFmpeg.AutoGen在你的 .NET 6+ 项目中真正活起来、稳住、不崩、可调试。
2. 从 .rar 解压到第一个成功调用 avcodec_version():三步建立可信调用链
这个.rar包的核心价值,不在于它塞了多少 demo,而在于它隐含了一套经过验证的ABI 对齐方案:Windows x64 下 FFmpeg 6.1.1 的 DLL、对应头文件生成的 C# binding、以及规避常见 P/Invoke 崩溃的初始化顺序。我们按最简路径复现——目标不是“跑起来”,而是“知道每一行为什么必须这么写”。
2.1 解压结构分析:识别真正的“不可替换”资产
先解压.rar,典型目录结构如下:
FFmpeg.AutoGen代码例子/ ├── binaries/ ← 关键!这里放的是预编译的 FFmpeg DLL │ ├── avcodec-60.dll │ ├── avformat-60.dll │ ├── avutil-58.dll │ └── swscale-7.dll ├── FFmpeg.AutoGen/ ← C# binding 项目(.csproj) │ ├── FFmpeg.AutoGen.csproj │ ├── NativeMethods.cs ← 自动生成的 P/Invoke 方法声明 │ └── FFmpegVersion.cs ← 版本号常量(必须与 binaries/ 中 DLL 版本一致) ├── Examples/ ← 实际调用示例(重点看这个) │ ├── DecodeVideoFrame.cs ← 解码单帧 BMP 输出 │ └── SimpleTranscode.cs ← H.264 转 H.265(无重采样) └── README.md提示:
binaries/下的 DLL 是整个方案的基石。不要试图用自己下载的ffmpeg.exe替换它们——.exe是命令行前端,不导出avcodec_open2等函数;也不要从官网下载“shared”版 DLL 后直接丢进来——版本号(如avcodec-60.dll中的60)必须与FFmpeg.AutoGen.csproj中<PackageReference Include="FFmpeg.AutoGen" Version="6.1.1" />严格匹配,否则avcodec_find_decoder返回 null 是常态,不是玄学。
2.2 创建最小验证项目:用avcodec_version()确认 ABI 连通性
新建一个 .NET 6 Console App(命名FFmpegAutoGenVerify),然后执行三步:
步骤 1:引用 NuGet 包并复制 native DLL
dotnet add package FFmpeg.AutoGen --version 6.1.1接着,将解压出的binaries/下全部.dll文件(avcodec-60.dll,avformat-60.dll,avutil-58.dll,swscale-7.dll)复制到项目根目录,并在.csproj中显式声明为Content并设CopyToOutputDirectory:
<ItemGroup> <Content Include="avcodec-60.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="avformat-60.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="avutil-58.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> <Content Include="swscale-7.dll"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> </ItemGroup>参数说明:
PreserveNewest确保每次构建时 DLL 被拷贝到bin/Debug/net6.0/,且不会因缓存导致旧版残留。这是比<None>更可靠的 native asset 管理方式。
步骤 2:编写 ABI 连通性测试代码
在Program.cs中写入:
using FFmpeg.AutoGen; // 必须在任何 FFmpeg 调用前执行! ffmpeg.avdevice_register_all(); // 注册设备(如 dshow, v4l2) ffmpeg.avcodec_register_all(); // 注册编解码器(H.264, AAC 等) ffmpeg.avformat_network_init(); // 初始化网络(RTMP/RTSP 需要) // 验证核心 ABI 是否就绪 int version = ffmpeg.avcodec_version(); Console.WriteLine($"FFmpeg codec version: {version} ({version.ToString("X")})"); Console.WriteLine($"FFmpeg build configuration: {ffmpeg.avcodec_configuration()}"); // 尝试找一个解码器(验证符号解析) var h264Decoder = ffmpeg.avcodec_find_decoder(AVCodecID.AV_CODEC_ID_H264); if (h264Decoder == null) { Console.WriteLine("❌ ERROR: H.264 decoder not found! Check DLL version match."); return; } Console.WriteLine($"✅ H.264 decoder found: {Marshal.PtrToStringAnsi(h264Decoder->name)}");步骤 3:运行并观察输出
执行dotnet run,预期输出:
FFmpeg codec version: 3934208 (3C0800) FFmpeg build configuration: --enable-shared --enable-gpl --enable-libx264 ... ✅ H.264 decoder found: h264逻辑说明:
avcodec_version()返回的是AV_VERSION_INT宏计算值(如3934208对应6.1.1),它由libavcodec的version.h编译进 DLL。如果此处报System.DllNotFoundException,说明 DLL 未被加载(路径错/位数错/依赖缺失);如果返回0或乱码,说明FFmpeg.AutoGen绑定的函数签名与 DLL 导出符号不匹配(版本错)。这一步通过,证明你的 .NET 进程已能安全触达 FFmpeg 的原生 ABI 层——后续所有操作都建立在此之上。
3. 解码 MP4 到 RGB24:手写一个不依赖第三方图像库的完整流程
.rar包里的DecodeVideoFrame.cs示例往往只展示解码逻辑,却省略了最关键的内存生命周期管理。本节带你从零写出一个可稳定运行、内存不泄漏、支持任意分辨率输入的解码器,目标:读取input.mp4,逐帧解码为RGB24字节数组,保存为frame_0001.bmp。
3.1 初始化上下文:AVFormatContext + AVCodecContext 的协作关系
FFmpeg 解码是典型的“两阶段初始化”:先用avformat_open_input打开容器(MP4),再用avcodec_find_decoder+avcodec_open2打开其中的视频流编解码器。关键点在于:AVCodecContext必须从AVStream.codecpar复制参数,而非手动设置width/height/pix_fmt——否则avcodec_open2可能失败或解码出绿屏。
// 1. 打开输入文件 var inputPath = "input.mp4"; AVFormatContext* formatContext = null; int ret = ffmpeg.avformat_open_input(&formatContext, inputPath, null, null); if (ret < 0) throw new Exception($"avformat_open_input failed: {ret}"); // 2. 检索视频流索引 int videoStreamIndex = -1; for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) throw new Exception("No video stream found"); // 3. 获取视频流参数并查找解码器 AVCodecParameters* codecParams = formatContext->streams[videoStreamIndex]->codecpar; AVCodecID codecId = codecParams->codec_id; AVCodec* codec = ffmpeg.avcodec_find_decoder(codecId); if (codec == null) throw new Exception($"Decoder not found for {codecId}"); // 4. 分配并初始化编解码器上下文(关键:从 codecpar 复制!) AVCodecContext* codecContext = ffmpeg.avcodec_alloc_context3(codec); ret = ffmpeg.avcodec_parameters_to_context(codecContext, codecParams); if (ret < 0) throw new Exception($"avcodec_parameters_to_context failed: {ret}"); // 5. 打开解码器(此时 codecContext->width/height/pix_fmt 已由 parameters_to_context 填充) ret = ffmpeg.avcodec_open2(codecContext, codec, null); if (ret < 0) throw new Exception($"avcodec_open2 failed: {ret}");参数说明:
avcodec_parameters_to_context是 FFmpeg 4.0+ 强制要求的步骤,它将AVCodecParameters(轻量、只读的流参数)完整复制到AVCodecContext(可读写的运行时上下文)。跳过此步直接codecContext->width = codecParams->width是常见翻车点——某些编码器(如 HEVC)需要更多参数协同生效。
3.2 解码循环:AVPacket → AVFrame → RGB24 的三段式内存管理
解码核心是avcodec_send_packet+avcodec_receive_frame的 push-pull 模型。注意:AVFrame的内存由 FFmpeg 自动分配,但AVFrame.data[0]指向的像素数据需手动malloc用于 RGB 转换。
// 分配用于存储 RGB24 数据的 buffer(按最大可能帧尺寸) int width = (int)codecContext->width; int height = (int)codecContext->height; int rgbBufferSize = width * height * 3; // RGB24: 3 bytes per pixel byte* rgbBuffer = (byte*)ffmpeg.av_malloc((UIntPtr)rgbBufferSize); // 分配 SwsContext 用于 YUV420P → RGB24 转换 SwsContext* swsContext = ffmpeg.sws_getContext( width, height, codecContext->pix_fmt, width, height, AVPixelFormat.AV_PIX_FMT_RGB24, SwsFlags.SWS_BILINEAR, null, null, null); if (swsContext == null) throw new Exception("sws_getContext failed"); // 主解码循环 AVPacket* packet = ffmpeg.av_packet_alloc(); AVFrame* frame = ffmpeg.av_frame_alloc(); int frameCount = 0; while (ffmpeg.av_read_frame(formatContext, packet) >= 0) { if (packet->stream_index == videoStreamIndex) { // 发送压缩包到解码器 ret = ffmpeg.avcodec_send_packet(codecContext, packet); if (ret < 0 && ret != (int)AVError.EAGAIN && ret != (int)AVError.EINVAL) throw new Exception($"avcodec_send_packet failed: {ret}"); // 接收解码后的帧 while (ret >= 0) { ret = ffmpeg.avcodec_receive_frame(codecContext, frame); if (ret == (int)AVError.EAGAIN || ret == (int)AVError.EINVAL) break; if (ret < 0) throw new Exception($"avcodec_receive_frame failed: {ret}"); // 将 YUV 帧转换为 RGB24 byte** dstData = stackalloc byte*[4]; int* dstLinesize = stackalloc int[4]; dstData[0] = rgbBuffer; dstLinesize[0] = width * 3; ffmpeg.sws_scale(swsContext, frame->data, frame->linesize, 0, height, dstData, dstLinesize); // 保存为 BMP(简化版:仅支持 24-bit uncompressed) SaveRgb24AsBmp(rgbBuffer, width, height, $"frame_{frameCount:D4}.bmp"); frameCount++; } } ffmpeg.av_packet_unref(packet); } // 清理 ffmpeg.av_frame_free(&frame); ffmpeg.av_packet_free(&packet); ffmpeg.av_freep(&rgbBuffer); ffmpeg.sws_freeContext(swsContext); ffmpeg.avcodec_free_context(&codecContext); ffmpeg.avformat_close_input(&formatContext);逻辑说明:
sws_scale是唯一安全的像素格式转换入口。dstData[0]指向我们 malloc 的rgbBuffer,dstLinesize[0]必须是width * 3(RGB24 行字节数),不能传frame->linesize[0]——后者是 YUV 的行宽。SaveRgb24AsBmp是一个纯 C# 实现的 BMP 写入函数(不依赖System.Drawing),它按 BMP 文件头(54 字节)+ 像素数据(BGR 顺序,需翻转 R/B)写入,此处略去实现细节,但强调:所有av_malloc分配的内存必须用av_freep释放,av_frame_alloc的 frame 必须用av_frame_free,这是 FFmpeg.AutoGen 不帮你做的边界责任。
4. 避坑:五个让新手卡三天的真实问题与血泪解决方案
FFmpeg.AutoGen 的坑不在语法,而在 ABI 层的隐式契约。以下问题均来自真实项目日志,每一条都附带现象 → 原因 → 解决的闭环。
4.1 现象:avcodec_open2返回-22(EINVAL),但codecContext->width显示正确
原因:codecContext->pix_fmt为AV_PIX_FMT_NONE,而avcodec_parameters_to_context未被调用,导致解码器无法确定输入像素格式。
解决:强制在avcodec_open2前插入ffmpeg.avcodec_parameters_to_context(codecContext, codecParams),并检查返回值是否为0。
4.2 现象:解码出的帧全为绿色或紫色噪点,frame->width/height正确但frame->data[0]指向无效内存
原因:sws_getContext创建失败(返回 null),但代码未检查,后续sws_scale对 nullSwsContext*执行未定义行为。
解决:sws_getContext后必须判空,且确保codecContext->pix_fmt是sws_getContext支持的格式(如AV_PIX_FMT_YUV420P),可用ffmpeg.sws_isSupportedInput预检。
4.3 现象:程序运行几秒后崩溃,调试器显示AccessViolationException,堆栈指向avcodec_receive_frame
原因:AVFrame* frame未用av_frame_alloc()分配,而是stackalloc或new AVFrame()—— FFmpeg 内部会写入frame->buf[0]等指针字段,托管内存布局不兼容。
解决:所有AVFrame*必须用ffmpeg.av_frame_alloc()分配,用ffmpeg.av_frame_free()释放;禁止任何new或stackalloc。
4.4 现象:avformat_open_input返回-1094995529(AVERROR_UNKNOWN),但文件路径确认无误
原因:Windows 下路径含中文或空格,avformat_open_input的 C 字符串转换失败(Marshal.StringToHGlobalAnsi遇到 Unicode 字符截断)。
解决:改用ffmpeg.avformat_open_input的宽字符变体(需自行绑定avformat_open_input_w)或先将路径转为短路径(GetShortPathNameW),推荐后者:
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern uint GetShortPathNameW(string lpszLongPath, StringBuilder lpszShortPath, uint cchBuffer); var shortPath = new StringBuilder(1024); GetShortPathNameW(inputPath, shortPath, 1024); ret = ffmpeg.avformat_open_input(&formatContext, shortPath.ToString(), null, null);4.5 现象:avcodec_send_packet后avcodec_receive_frame一直返回EAGAIN,无帧输出
原因:MP4 文件含 B 帧(双向预测帧),AVPacket的dts和pts未按解码顺序排列,av_read_frame返回的 packet 顺序不等于解码顺序。
解决:启用AVFMT_TS_DISCONT标志并手动排序,或更简单——在avformat_open_input后立即调用ffmpeg.avformat_find_stream_info(formatContext, null),它会预读部分数据并填充codecContext->has_b_frames,确保解码器内部缓冲区就绪。
5. 推流到 SRS:绕过 ffmpeg.exe 的低延迟 RTMP 推送实现
.rar包里若含PushToSrs.cs,它大概率是用avformat_write_header+av_interleaved_write_frame直接构造 RTMP 流。这比调用ffmpeg.exe -re -i in.mp4 -f flv rtmp://localhost/live/stream优势明显:延迟可控(可压到 300ms)、无进程启动开销、可动态修改时间戳、可注入自定义 SEI 数据。本节实现一个 H.264 Annex-B 流(如摄像头裸流)直推 SRS 的最小闭环。
5.1 构建 RTMP 输出上下文:AVFormatContext 的特殊配置
RTMP 推流的关键是AVOutputFormat必须为flv,且AVFormatContext.oformat需手动设置flags以禁用不必要的功能:
// 1. 分配输出上下文(URL 为 SRS 地址) AVFormatContext* outputContext = null; string rtmpUrl = "rtmp://localhost/live/test"; int ret = ffmpeg.avformat_alloc_output_context2(&outputContext, null, "flv", rtmpUrl); if (ret < 0) throw new Exception($"avformat_alloc_output_context2 failed: {ret}"); // 2. 配置输出格式标志:禁用自动 bitstream filtering(RTMP 需 Annex-B) outputContext->oformat->flags |= AVFMT_NOFILE; // 不使用文件 I/O,走网络 outputContext->oformat->flags &= ~AVFMT_GLOBALHEADER; // FLV 不需要 global header // 3. 添加视频流(假设输入是 H.264 Annex-B) AVStream* videoStream = ffmpeg.avformat_new_stream(outputContext, null); if (videoStream == null) throw new Exception("avformat_new_stream failed"); videoStream->id = 0; videoStream->time_base = (AVRational){1, 1000}; // 1ms 时间基 // 4. 设置编解码参数(关键:pix_fmt 必须为 AV_PIX_FMT_YUV420P,codec_id 为 H264) AVCodecParameters* outCodecParams = videoStream->codecpar; outCodecParams->codec_type = AVMediaType.AVMEDIA_TYPE_VIDEO; outCodecParams->codec_id = AVCodecID.AV_CODEC_ID_H264; outCodecParams->width = 1280; outCodecParams->height = 720; outCodecParams->format = (int)AVPixelFormat.AV_PIX_FMT_YUV420P; outCodecParams->bit_rate = 2_000_000; // ⚠️ 必须设置 extradata!否则 SRS 拒绝连接 // extradata = SPS + PPS(以 4 字节长度前缀的 Annex-B 格式) outCodecParams->extradata = (byte*)ffmpeg.av_malloc((UIntPtr)(sps.Length + pps.Length + 8)); outCodecParams->extradata_size = sps.Length + pps.Length + 8; Buffer.MemoryCopy(sps, outCodecParams->extradata + 4, sps.Length, sps.Length); Buffer.MemoryCopy(pps, outCodecParams->extradata + 4 + sps.Length, pps.Length, pps.Length); // 写入长度前缀(big-endian) BitConverter.TryWriteBytes(outCodecParams->extradata, (uint)sps.Length); BitConverter.TryWriteBytes(outCodecParams->extradata + 4 + sps.Length, (uint)pps.Length);参数说明:
extradata是 H.264 流的“启动密钥”。SRS 要求其为 Annex-B 格式(00 00 00 01分隔),但AVCodecParameters.extradata必须是00 00 00 xx+SPS+00 00 00 yy+PPS的拼接(xx/yy为 big-endian 长度)。avformat_new_stream后必须手动填充extradata,否则avformat_write_header会失败并返回AVERROR_INVALIDDATA。
5.2 推送帧循环:AVPacket 时间戳的精确控制
RTMP 推流的延迟核心在AVPacket.dts/pts。.rar包中的示例常直接用av_rescale_q转换,但实际需考虑DTS 递增、PTS 与 DTS 的 B 帧偏移、以及 SRS 的缓冲策略:
// 初始化起始时间戳(毫秒级) long startTimeMs = DateTimeOffset.Now.ToUnixTimeMilliseconds(); // 主推送循环(假设 packets 是从摄像头获取的 H.264 Annex-B packet) foreach (var packet in cameraPackets) { AVPacket* avPacket = ffmpeg.av_packet_alloc(); // 1. 复制 packet 数据(Annex-B 格式) avPacket->data = (byte*)ffmpeg.av_malloc((UIntPtr)packet.Length); Buffer.MemoryCopy(packet, avPacket->data, packet.Length, packet.Length); avPacket->size = packet.Length; // 2. 设置时间戳:DTS = 当前时间 - 起始时间,单位为 outputContext->streams[0]->time_base long elapsedMs = DateTimeOffset.Now.ToUnixTimeMilliseconds() - startTimeMs; avPacket->dts = ffmpeg.av_rescale_q(elapsedMs * 1000, (AVRational){1, 1000000}, videoStream->time_base); avPacket->pts = avPacket->dts; // 无 B 帧时 PTS=DTS // 3. 设置 stream index 和 keyframe 标志 avPacket->stream_index = 0; avPacket->flags = (packet[0] == 0x00 && packet[1] == 0x00 && packet[2] == 0x00 && packet[3] == 0x01 && packet[4] == 0x67) ? AVPacketFlags.AV_PKT_FLAG_KEY : 0; // 简单判断 IDR 帧(0x67 是 SPS,0x65 是 IDR) // 4. 写入帧(interleaved 确保音视频包交错) ret = ffmpeg.av_interleaved_write_frame(outputContext, avPacket); if (ret < 0) { Console.WriteLine($"av_interleaved_write_frame failed: {ret}"); break; } ffmpeg.av_packet_unref(avPacket); ffmpeg.av_packet_free(&avPacket); // 5. 控制发送节奏(模拟 30fps) Thread.Sleep(33); }逻辑说明:
av_interleaved_write_frame会自动处理AVPacket的dts/pts排序和FLVtag 头封装。av_rescale_q的time_base必须与videoStream->time_base一致(此处为1/1000),否则时间戳错乱导致 SRS 缓冲膨胀。Thread.Sleep(33)是粗略的帧率控制,生产环境应改用Stopwatch精确调度。
6. 生产就绪技巧:用 FFmpeg.AutoGen 做好三件事,比写一百行 ffmpeg 命令更可靠
我在线上系统用 FFmpeg.AutoGen 支撑过 200+ 路 RTSP 拉流 + AI 推理 pipeline,三年没因音视频层崩溃过。总结下来,真正值得投入的不是“功能多”,而是三个能让系统在高压下不抖动的硬核习惯:
6.1 用AVDictionary精确控制每个组件的行为,而不是依赖全局环境变量
FFmpeg 的ffmpeg.exe用-rtsp_transport tcp控制传输协议,但在 AutoGen 中,这必须通过AVDictionary传给avformat_open_input:
AVDictionary* options = null; ffmpeg.av_dict_set(&options, "rtsp_transport", "tcp", 0); // 强制 TCP ffmpeg.av_dict_set(&options, "stimeout", "5000000", 0); // 5秒超时(微秒) ffmpeg.av_dict_set(&options, "buffer_size", "1048576", 0); // 1MB 缓冲 int ret = ffmpeg.avformat_open_input(&formatContext, rtspUrl, null, &options); ffmpeg.av_dict_free(&options); // 必须释放!教训:
AVDictionary是 FFmpeg 组件间传递配置的唯一正统途径。setenv("FFMPEG_TCP_TIMEOUT", "5")这类环境变量对avformat_open_input无效。av_dict_set的第三个参数flags设为0表示追加,AV_DICT_APPEND会合并同名键——这对fflags(如"flush_packets")很关键。
6.2 把AVFrame的内存生命周期完全交给 FFmpeg,自己只管“借”和“还”
新手常犯的错:av_frame_alloc()后手动Marshal.FreeHGlobal(frame->data[0])。这是灾难——AVFrame的data可能指向 GPU 显存、DMA 缓冲区或 FFmpeg 内部池,av_frame_unref才是唯一正确的释放入口:
// ✅ 正确:让 FFmpeg 管理 data 内存 AVFrame* frame = ffmpeg.av_frame_alloc(); // ... 使用 frame ffmpeg.av_frame_unref(frame); // 自动释放 data[0], data[1]... ffmpeg.av_frame_free(&frame); // 释放 frame 结构体本身 // ❌ 错误:手动释放 data // Marshal.FreeHGlobal((IntPtr)frame->data[0]); // 可能 crash!技巧:在
avcodec_receive_frame后,立刻调用ffmpeg.av_frame_make_writable(frame)。它会确保frame->data[0]指向可写的内存(必要时 copy),避免后续sws_scale因只读内存触发 access violation。
6.3 用av_strerror把错误码翻译成中文,而不是靠ret < 0猜
FFmpeg 返回的负数错误码(如-32)对调试毫无意义。必须用av_strerror转为可读字符串:
string GetAvError(int ret) { Span<byte> errBuff = stackalloc byte[256]; ffmpeg.av_strerror(ret, errBuff, (UIntPtr)errBuff.Length); return Encoding.UTF8.GetString(errBuff).TrimEnd('\0'); } // 使用 if (ret < 0) throw new Exception($"avcodec_open2 failed: {ret} ({GetAvError(ret)})");教训:我曾为
-541478725这个错误排查两天,最后发现是AV_PIX_FMT_NV12传给了只支持AV_PIX_FMT_YUV420P的sws_getContext,av_strerror直接返回"Invalid argument",而查文档才知sws_getContext对输入格式有严格白名单。把av_strerror封装进所有 FFmpeg 调用的 error handling,是节省调试时间最划算的投资。
希望帮到你。
本文还有配套的精品资源,点击获取