ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C#调用FFmpeg原生API:从解码到推流的完整实践指南

2026/9/1 2:28:21 拓冰建站 浏览量
C#调用FFmpeg原生API:从解码到推流的完整实践指南 简介本资源是一套面向C#开发者与多媒体应用工程师的FFmpeg底层调用实践工程聚焦于通过FFmpeg.AutoGen库在.NET环境中原生调用FFmpeg 3.4 API解决音视频解封装、编解码、流信息解析等核心问题。压缩包共148个文件含111个FFmpeg头文件.h支撑跨语言接口映射16个动态链接库.dll提供运行时依赖8个C#源码文件.cs实现从初始化、媒体打开、流查找、解码器配置到帧级解码与资源释放的完整流程另有可执行程序、配置文件及WinForm播放器界面资源结构完整、即开即用。资源包大小为30.25MB目录组织清晰涵盖项目配置.csproj/.sln、运行时辅助类FFmpegBinariesHelper.cs、主窗体逻辑frmPlayer.cs及多语言资源.resx便于理解封装原理与快速二次开发。目前已有2065人学习下载适合具备C#基础并希望深入掌握多媒体底层处理机制的中高级开发者。 做上位机和音视频处理的朋友对ffmpeg命令行肯定不陌生。很多人的第一反应就是用Process.Start拉起一个ffmpeg.exe进程传几个参数完事。但等你真正碰到推流、低延迟采集、实时滤镜、动态转码这类需求时命令行模式就完全顶不住了——你要不停拼参数、解析输出文本、处理进程退出性能和灵活性都大打折扣。这篇文章就围绕FFmpeg.AutoGen这个库把C#调用ffmpeg原生API这件事从思路到落地完整过一遍包括环境准备、初始化、解码流程、常见坑位排查保证你看完能直接上手写代码。这个东西具体是干什么的一句话概括FFmpeg.AutoGen是用C#自动生成的ffmpeg原生接口绑定库它把ffmpeg的C语言API裸映射成C#方法让你可以直接在.NET项目里操作AVFormatContext、AVCodecContext这类指针结构体而不用自己手写几千行P/Invoke声明。适合做上位机、视频工具、流媒体服务的开发者也适合想搞懂ffmpeg内部原理的人——因为它没有像Emgu.CV那样包一层面向对象的外壳而是把原生的逻辑直接暴露给你看。1. 方案选型为什么偏偏是FFmpeg.AutoGen1.1 用命令行进程调ffmpeg的最大痛点C#里要调用ffmpeg其实有几种路径。最原始的就是Process.Start拉起进程很多小工具和临时脚本都这么干写起来快ffmpeg.exe一换版本功能就跟着升级。但它的毛病也很明显进程启动有开销做个视频切片循环几十次进程拉起的时间比处理时间还长参数一复杂就很难维护滤镜图串起来之后在命令行里看像天书最致命的是无法拿到底层的帧数据你只能靠读标准输出或者落盘文件来中转内存拷贝和IO开销全浪费在这上面了。要做一个实时性要求高的桌面采集推流工具这条路基本走不通。1.2 包装库的取舍FFmpeg.NET、Xabe.FFmpeg这些能省事多少后来又出现了不少社区包装库比如Xabe.FFmpeg、FFmpeg.NET、FfMpegCore它们把ffmpeg命令行的常用操作封装成流式接口像FFmpeg.GetMediaInfo(filePath)、Conversion.New().Start()这样就能出结果。这种库对做简单转码、截图的场景确实方便但本质还是封装了命令行调用只是把字符串拼接藏起来了。一旦遇到需要自定义解码循环、逐帧处理、插入自定义滤镜或者要在一个进程里同时管理多个编码器实时推流这些库就变得很尴尬要么提供不了那么细的接口要么性能损耗大。它们适合处理“任务型”需求不适合处理“流式”需求。1.3 FFmpeg.AutoGen的理念裸调API不搞中间层FFmpeg.AutoGen走的是另一条路。它不封装进程不封装对象而是把ffmpeg的C头文件翻译成C#的委托和结构体直接放在你面前。你用ffmpeg.avformat_open_input、ffmpeg.avcodec_send_packet这些方法和使用C语言的ffmpeg写代码几乎没有区别。好处是零抽象、性能最大化你可以完全控制每帧的分配、拷贝、释放时机也能直接用ffmpeg所有的原生能力——从硬件解码、滤镜图到各类封装格式全支持。坏处也很直接没有吃透ffmpeg的调用流程很容易踩空指针和内存泄漏的坑毕竟C#的垃圾回收在这里管不到ffmpeg原生分配的内存。2. 环境准备与初始化流程2.1 下载正确的ffmpeg版本库FFmpeg.AutoGen只是绑定层实际干活的是ffmpeg的原生动态库。你需要去下载ffmpeg的shared构建版本Windows上常见的有BtbN的GitHub Actions自动构建包和gyan.dev发布版。这里有个非常关键的细节一定要选shared或者full-shared构建不要选static因为static构建把所有代码编译进一个exe里根本没有导出DLL供C#调用。下载之后解压目录里会看到bin文件夹里面有avcodec-XX.dll、avformat-XX.dll、avutil-XX.dll、swscale-XX.dll等一系列动态库。这些DLL要放在你程序能搜到的位置可以直接放到程序输出目录也可以放到自定义目录然后用RootPath指定。注意FFmpeg.AutoGen的版本和ffmpeg的大版本要对应。比如FFmpeg.AutoGen 7.x对应ffmpeg 7.x的API如果你下载了ffmpeg 6.x的DLL却用了7.x的绑定包很多方法签名会直接报错。我见过最典型的错误是把AutoGen 6.x和ffmpeg 7的DLL混用avformat_open_input直接抛出EntryPointNotFoundException排查了半天才发现是版本错位。2.2 NuGet安装和项目配置在Visual Studio里新建一个控制台或WinForms项目然后通过NuGet安装FFmpeg.AutoGen。装完之后需要做两件事第一在项目属性里把“允许不安全代码”勾上因为FFmpeg.AutoGen的操作都是基于指针的不打开unsafe编译选项编译器会直接拒绝你的代码第二确认目标平台是x64还是x86如果下载的ffmpeg DLL是64位的项目平台必须设成x64否则运行时加载DLL时会报“试图加载格式不正确的程序”。安装好之后初始化代码非常简单using System; using FFmpeg.AutoGen; class Program { static unsafe void Main(string[] args) { // 设置ffmpeg原生库所在目录关键的一行 ffmpeg.RootPath D:\ffmpeg\bin; // 初始化网络模块推流和拉流一定要调用 ffmpeg.avformat_network_init(); Console.WriteLine($ffmpeg版本: {ffmpeg.av_version_info()}); Console.WriteLine($库版本号: {ffmpeg.avcodec_version()}); // 用完网络模块后反初始化虽然现代版本基本是空操作但习惯要留 // ffmpeg.avformat_network_deinit(); } }这段代码能跑通说明ffmpeg原生库加载成功AutoGen的绑定也没问题。ffmpeg.RootPath不是NuGet包自带的配置是你手动指定的原生DLL目录如果这个路径没设置对后面的调用全部会抛DllNotFoundException。2.3 老版本API的过时问题如果你搜索过相关的旧教程可能会看到ffmpeg.av_register_all()这行代码。这里提醒一下在你现在下载的ffmpeg 5.x/6.x/7.x版本里这个方法已经不干活了它只是保留一个兼容入口注册所有编解码器和封装器的工作变成了自动执行。所以正确做法是不调用或者忽略它不用管。很多网上粘贴的老代码到这里就会多出一行编译错误或者运行时错误看到别慌这是ffmpeg版本迭代的结果。3. 核心实操读取视频文件并获取流信息3.1 打开文件和解析流信息现在开始写真正有用的代码。第一步是打开一个视频文件获取封装格式上下文和流信息。这个流程对应C语言版的经典写法用FFmpeg.AutoGen写出来几乎是逐字翻译static unsafe AVFormatContext* OpenMediaFile(string filePath) { AVFormatContext* pFormatContext null; // 打开输入文件context由ffmpeg内部分配 ffmpeg.avformat_open_input(pFormatContext, filePath, null, null) .ThrowIfError(无法打开输入文件); // 读取媒体文件中每个流的头部信息填充流信息 ffmpeg.avformat_find_stream_info(pFormatContext, null) .ThrowIfError(无法检索流信息); // 打印详细格式信息调试神器 ffmpeg.av_dump_format(pFormatContext, 0, filePath, 0); return pFormatContext; }ThrowIfError是ffmpeg代码库常见的一个扩展方法实际上就是检查返回值是否小于0小于0就抛异常并打印错误信息。ffmpeg几乎所有API的约定是返回值小于0表示失败0表示成功正数根据具体API含义不同。这个约定无论如何要记住排查问题全靠它。3.2 遍历流并定位视频流一个媒体文件可能有多个流视频流、音频流、字幕流、数据流。我们需要遍历pFormatContext-nb_streams找到视频流的索引。在ffmpeg 5.x之后的版本流的信息都存在AVStream-codecpar里即AVCodecParameters结构体其中codec_type表示流类型codec_id表示具体编码格式width和height是画面尺寸。这些字段在新版本中不能再通过codec直接访问因为AVStream-codec字段已经被移除了用老代码会直接编译失败。static unsafe int FindVideoStreamIndex(AVFormatContext* pFormatContext) { for (int i 0; i pFormatContext-nb_streams; i) { AVStream* stream pFormatContext-streams[i]; AVCodecParameters* codecParams stream-codecpar; if (codecParams-codec_type AVMediaType.AVMEDIA_TYPE_VIDEO) { Console.WriteLine($找到视频流索引: {i}); Console.WriteLine($编码格式: {codecParams-codec_id}); Console.WriteLine($分辨率: {codecParams-width} x {codecParams-height}); Console.WriteLine($帧率估算: {stream-avg_frame_rate.num} / {stream-avg_frame_rate.den}); return i; } } throw new InvalidOperationException(文件中没有视频流); }这里AVMediaType.AVMEDIA_TYPE_VIDEO是一个C#枚举定义在FFmpeg.AutoGen命名空间里。codec_id也是一个AVCodecID枚举值大部分时候你不需要手动区分它因为后面找解码器时直接把codec_id传进去就行。3.3 打开解码器准备解码拿到视频流的codecpar之后下一步是根据编码ID找到对应解码器并创建解码上下文。解码器的选择逻辑很简单先根据codec_id找解码器再把流参数填充到解码上下文中最后打开它。这里有一个容易踩坑的点avcodec_alloc_context3传入的是解码器指针但后续不一定使用这个解码器因为有可能流里的实际编码参数和默认值不一致所以必须用avcodec_parameters_to_context把真实参数同步到上下文里。static unsafe AVCodecContext* OpenCodecContext(AVFormatContext* pFormatContext, int streamIndex) { AVStream* stream pFormatContext-streams[streamIndex]; AVCodecParameters* codecParams stream-codecpar; // 查找解码器 AVCodec* pCodec ffmpeg.avcodec_find_decoder(codecParams-codec_id); if (pCodec null) throw new InvalidOperationException($未找到解码器编码ID: {codecParams-codec_id}); // 分配解码上下文 AVCodecContext* pCodecContext ffmpeg.avcodec_alloc_context3(pCodec); if (pCodecContext null) throw new OutOfMemoryException(无法分配解码上下文); // 把流参数同步进解码上下文这一步非常关键 ffmpeg.avcodec_parameters_to_context(pCodecContext, codecParams) .ThrowIfError(参数同步失败); // 打开解码器 ffmpeg.avcodec_open2(pCodecContext, pCodec, null) .ThrowIfError(无法打开解码器); Console.WriteLine($解码器: {pCodec-name}); return pCodecContext; }这个流程跑通之后你已经可以拿到视频的基本信息并准备好解码环境了。下一步才是重头戏——真正解码出画面帧数据。4. 解码循环与帧提取的完整实现4.1 分配Packet和Frameffmpeg解编码的基本单位是AVPacket和AVFrame。AVPacket存压缩后的数据AVFrame存解码后的原始数据。新版本ffmpeg要求用av_packet_alloc和av_frame_alloc来分配这两个对象然后用av_packet_free和av_frame_free释放。不推荐在栈上直接声明这两个结构体因为它们的内部有指针指向堆内存栈上拷贝会引发双重释放问题。AVPacket* pPacket ffmpeg.av_packet_alloc(); AVFrame* pFrame ffmpeg.av_frame_alloc();4.2 读取压缩数据包用av_read_frame从输入上下文中循环读取一帧压缩数据。这个方法返回0表示读到了数据包返回负数表示读到了文件尾或者发生了错误。读取到的数据包存放在AVPacket结构体中注意数据包可能不是当前视频流的需要判断一下pPacket-stream_index是不是视频流音频包要直接跳过并unref。4.3 送包和收帧分离的现代解码模式ffmpeg 4.x之后的解码接口从原来的avcodec_decode_video2改成了现在的avcodec_send_packet加avcodec_receive_frame组合。这对新手来说是一个理解门槛但你必须接受并适应它send_packet把压缩数据包送进解码器内部缓冲receive_frame从解码器里取出解码后的帧。为什么这么设计因为很多编码格式如H.264/H.265存在B帧重排解码器不一定是“送一包出一帧”的线性关系可能送三包才出两帧也可能送一包出三帧所以必须分成两个独立的循环来消费。static unsafe int DecodeVideoFrames(AVFormatContext* pFormatContext, AVCodecContext* pCodecContext, int videoStreamIndex, ActionAVFrame* frameHandler) { AVPacket* pPacket ffmpeg.av_packet_alloc(); AVFrame* pFrame ffmpeg.av_frame_alloc(); int frameCount 0; try { while (ffmpeg.av_read_frame(pFormatContext, pPacket) 0) { if (pPacket-stream_index ! videoStreamIndex) { // 非视频流数据包释放引用后继续 ffmpeg.av_packet_unref(pPacket); continue; } // 送入解码器 int sendRet ffmpeg.avcodec_send_packet(pCodecContext, pPacket); if (sendRet 0 sendRet ! ffmpeg.AVERROR(EAGAIN) sendRet ! ffmpeg.AVERROR_EOF) { sendRet.ThrowIfError(发送数据包失败); } // 收帧循环 int recvRet 0; while (recvRet 0) { recvRet ffmpeg.avcodec_receive_frame(pCodecContext, pFrame); if (recvRet 0) { // 成功收到一帧交给外部处理 frameHandler(pFrame); frameCount; } else if (recvRet ffmpeg.AVERROR(EAGAIN)) { // 解码器需要更多数据才能产生帧跳出本轮 break; } else if (recvRet ffmpeg.AVERROR_EOF) { // 解码器已输出所有缓冲帧 break; } else { recvRet.ThrowIfError(接收帧失败); } } // 释放数据包引用归还缓冲区 ffmpeg.av_packet_unref(pPacket); } // 文件读完后Flush解码器内部的缓冲帧 ffmpeg.avcodec_send_packet(pCodecContext, null); int flushTemp 0; while (flushTemp 0) { flushTemp ffmpeg.avcodec_receive_frame(pCodecContext, pFrame); if (flushTemp 0) { frameHandler(pFrame); frameCount; } else { break; } } } finally { ffmpeg.av_packet_free(pPacket); ffmpeg.av_frame_free(pFrame); } return frameCount; }这段代码有几个细节值得展开说。首先EAGAIN在ffmpeg语义里是“现在还没有数据再等等”它不是错误是一个正常分支。AVERROR_EOF表示解码器内部缓冲已经全部吐完。最后那个flus操作很多人会漏掉如果不flush视频末尾可能出现丢帧尤其是包含B帧的H.264视频最后几帧会凭空消失。flush的方法就是avcodec_send_packet传一个null指针进去。4.4 拿到AVFrame之后能干些什么frameHandler回调里你可以拿到原始视频帧存放在AVFrame-data[0]、data[1]、data[2]中常见格式是YUV420P即AV_PIX_FMT_YUV420P。如果不处理格式转换直接把这个数据存成文件或者送显通常是不行的因为大多数显示管线只认RGB或者BGR。此时要用swscale库做格式转换这也是ffmpeg调用的经典场景之一。转换的代码套路固定先拿到SwsContext然后每帧调一次sws_scale// 在解码前先创建转换上下文 SwsContext* pSwsContext ffmpeg.sws_getContext( codecContext-width, codecContext-height, codecContext-pix_fmt, codecContext-width, codecContext-height, AV_PIX_FMT_BGR24, ffmpeg.SWS_BICUBIC, null, null, null); // 在frameHandler内部做转换 byte*[] dstData { pBgrBuffer, null, null, null }; int[] dstLinesize { pBgrBufferWidth, 0, 0, 0 }; ffmpeg.sws_scale(pSwsContext, pFrame-data, pFrame-linesize, 0, codecContext-height, dstData, dstLinesize);转成BGR24之后你可以直接用Bitmap的Scan0指针把数据拷进去保存成图片或者用LockBits写入。这是做一个视频抽帧工具最简单的路径。5. 转码和推流的扩展思路5.1 从解码到编码用API实现转码读帧只是最基础的场景很多人的实际需求是转码比如把MP4文件转换成HLS切片或者把采集到的RAW帧编码成H.264。转码的逻辑可以总结为“解码到帧再送进编码器”。编码器的打开方式和解码器类似但有几点不同第一编码上下文要自己设置参数包括宽高、像素格式、帧率、码率、GOP大小第二编码器的像素格式通常有限制比如H.264编码器一般接受YUV420P如果你从解码器拿到的是其他格式中间需要先用sws_scale转换第三编码的send_frame和receive_packet与解码镜像前者送AVFrame后者收AVPacket。static unsafe AVCodecContext* CreateH264Encoder(int width, int height, int fps, int bitrate) { AVCodec* pCodec ffmpeg.avcodec_find_encoder(AVCodecID.AV_CODEC_ID_H264); if (pCodec null) throw new InvalidOperationException(H.264编码器不可用); AVCodecContext* pContext ffmpeg.avcodec_alloc_context3(pCodec); pContext-width width; pContext-height height; pContext-time_base new AVRational { num 1, den fps }; pContext-framerate new AVRational { num fps, den 1 }; pContext-pix_fmt AV_PIX_FMT.AV_PIX_FMT_YUV420P; pContext-bit_rate bitrate; pContext-gop_size fps * 2; // 两秒一个关键帧 // 设置码率控制策略这里是ABR pContext-rc_2pass 0; pContext-flags | ffmpeg.AV_CODEC_FLAG_GLOBAL_HEADER; ffmpeg.avcodec_open2(pContext, pCodec, null).ThrowIfError(打开编码器失败); return pContext; }设置AV_CODEC_FLAG_GLOBAL_HEADER很重要它让编码器把SPS/PPS写入extradata而不是每个关键帧前都带一份。这个标志位在推流到FLV或RTMP的时候基本是必需的否则播放器可能无法解析。这也是很多教程里没提、实际推流时会踩的坑。5.2 推流到RTMP音视频流的封装输出推流的思路比转码更进一步解码或采集得到帧编码成H.264/AAC然后用avformat_write_header写到一个输出URL。这需要先创建AVFormatContext作为输出封装器AVFormatContext* pOutContext null; ffmpeg.avformat_alloc_output_context2(pOutContext, null, flv, rtmpUrl); // 创建新的视频流 AVStream* outStream ffmpeg.avformat_new_stream(pOutContext, null); // 把编码器参数拷贝到输出流 ffmpeg.avcodec_parameters_from_context(outStream-codecpar, encoderContext); ffmpeg.avio_open(pOutContext-pb, rtmpUrl, ffmpeg.AVIO_FLAG_WRITE); ffmpeg.avformat_write_header(pOutContext, null);推流过程中还要注意时间基的转换。解码器拿到的帧时间基可能和输出流不一致需要用av_rescale_q把PTS和DTS从输入流的时间基转到输出流的时间基。这些细节做起来不算复杂但跳过去必然导致播放时间轴错乱或音画不同步。FFmpeg.AutoGen提供了AVRational结构体配合av_rescale_q使用long outPts ffmpeg.av_rescale_q(frame-pts, decoderContext-time_base, outStream-time_base); pPacket-pts outPts; pPacket-dts outPts; pPacket-duration ffmpeg.av_rescale_q(frame-pkt_duration, decoderContext-time_base, outStream-time_base);5.3 采集摄像头帧的场景补充热词里有人搜“C# AForge设置摄像头视频属性”其实如果是走ffmpeg路线也可以用ffmpeg直接打开本机摄像头比如Windows上的dshow设备。打开方式和打开文件几乎一样只是URL变了ffmpeg.avformat_open_input(pFormatContext, videoUSB Camera, ffmpeg.av_find_input_format(dshow), null);然后解码循环完全复用前面的逻辑。区别在于摄像头帧率是实时的解码速度必须跟得上采集速度否则缓冲队列会爆掉。这块建议在解码循环里加上时间戳判断跳过积压的旧帧。6. 常见问题与排查技巧实录6.1 典型报错速查表我在实际项目里踩过的坑整理成一张表方便你对照排查现象可能原因解决方案DllNotFoundExceptionffmpeg DLL没放在程序目录或者RootPath没设置检查ffmpeg.RootPath路径确认avformat-XX.dll存在EntryPointNotFoundExceptionAutoGen版本和ffmpeg版本不匹配核对NuGet包版本和DLL版本最好选同大版本BadImageFormatException32位/64位不匹配项目平台改为x64下载对应架构的ffmpeg构建打开文件失败返回-2文件路径或URL错误检查路径是否存在网络源先确认可以访问解码器打开失败编码器参数不匹配或缺少解码器确认codec_id和实际格式一致检查编译时是否开启对应模块收到帧为全绿或花屏像素格式或linesize处理错误检查sws_scale目标格式确认每行字节数和对齐方式内存不断上涨Packet或Frame没有unref/free逐帧检查是否调用av_packet_unrefAVFrame是否及时释放引用6.2 内存管理是最大的坑C#开发者很容易在ffmpeg调用上栽跟头因为GC不管理ffmpeg的原生内存。AVPacket在解码循环里必须反复unref否则av_read_frame内部会不断申请新缓冲区内存占用滚雪球。AVFrame如果是通过av_frame_alloc分配的最后要av_frame_free如果是引用计数式的数据用完后要av_frame_unref。这里分享一个经验原则谁分配谁释放引用计数加一必须对应减一。写好一处代码后建议拿一个中等大小的视频文件跑一遍用任务管理器观察内存趋势。如果发现内存曲线一路上涨不停九成是某个包或帧没有unref。别问我怎么知道的我就曾经在一个长达四小时的录播处理任务里看到内存冲到3GB最后发现是flush阶段漏了av_packet_unref。6.3 视频流结束后的收尾工作处理完视频别忘了释放所有资源。释放顺序有讲究先free packet和frame再关闭解码器最后关闭输入上下文。顺序反了虽然大概率不会崩但可能在调试模式下触发断言警告。ffmpeg.av_packet_free(pPacket); ffmpeg.av_frame_free(pFrame); ffmpeg.avcodec_free_context(pCodecContext); ffmpeg.avformat_close_input(pFormatContext);6.4 多线程环境下的注意事项如果你要在C#的多线程环境里使用ffmpeg API建议遵循“一个线程一个编解码上下文”的原则。ffmpeg的编解码上下文默认不是线程安全的多个线程同时调用同一个AVCodecContext会引发数据竞争导致崩溃。实际项目中我是用一个线程跑解码循环产生的帧通过ConcurrentQueue分发给多个工作线程做图像处理这样可以保证性能又不至于踩线程安全的雷。此外FFmpeg.AutoGen生成了很多扩展方法但底层还是非托管的跨线程调用要确保原生的AVFrame在被消费之前不被解码线程释放引用。7. 从调用到掌握几条实在的个人经验最后分享几个我觉得对新手比较有帮助的小细节。第一个调试时多用av_dump_format。这个方法会把媒体文件的核心信息打印到控制台包括封装格式、每个流的编码、分辨率、码率等。遇到任何打开文件后表现不对的情况先看它输出的信息能过滤掉一大批低级问题。第二个刚开始练手一定要从小文件开始。找一段几秒钟的短视频先跑通完整的“打开-读帧-转格式-存图”流程再逐步加入编码和推流。不要一上来就处理几十GB的视频或者直接推流到服务器不然问题叠加在一起根本不知道错在哪一步。第三个善用官方示例工程。FFmpeg.AutoGen的GitHub仓库里有示例代码比如transcode、remuxing、filtering这些经典场景都有配套实现。虽然是英文注释但代码本身就是最好的老师把你需要的部分摘出来改一改比从零摸索快很多。第四个如果只是想要一个“能跑”的转码工具其实用命令行也能解决但如果你要做的是自动化的视频管线、低延迟推流、实时预览这些场景花时间把FFmpeg.AutoGen吃透是值得的。它的学习曲线确实比包装库陡但一旦入口打通你能掌控的能力范围完全是另一个层次。后续如果你有兴趣这个方向还可以继续扩展接入硬件加速NVENC/QSV做低延迟推流、用滤镜图实现画中画和文字叠加、自定义协议从网络流读取数据这些都是基于这套基础调用框架可以继续深挖的点。先把这套骨架跑熟练了后面加功能就是顺理成章的事。本文还有配套的精品资源点击获取