资讯动态

Unity实时画面采集与RTMP推流实战:RenderTexture+FFmpeg方案详解

发布时间:2026/9/18 23:21:15 来源:尧图企业网站定制
搞过Unity实时画面采集的人应该都有同感引擎自带的Camera输出到屏幕容易但一旦涉及“把画面送到外部去”比如录制成文件、推成直播流能查到的完整资料就少得可怜。Unity官方对录屏、推流这块基本处于“你自己看着办”的状态而市面上能直接用的商业插件又贵又黑盒出了问题根本没法调。这篇文章我就把自己在Unity 3D C#里做摄像头画面采集、视频录制、RTMP推流直播的完整思路和关键实现拆开讲一遍。核心逻辑是用RenderTexture接住相机画面用Texture2D把画面读回内存再交给FFmpeg进程做编码和推流。整套方案跨平台思路统一Windows、Linux、Android都能跑关键是每一步你都拿得到源码、调得了参数——出了问题能自己修这才是自己动手做推流而不是买个黑盒插件的最大好处。先提醒一句下方所有步骤我默认你用的是Unity 2021 LTS及以上版本C#语法基于.NET Standard 2.1操作系统以Windows为主Android部分会单独标注差异。1. 方案选型为什么最终选了“RenderTexture FFmpeg进程”推流功能的第一步不是写代码而是选型。选错路后面全白干。Unity里获取摄像头画面有几种常规方式但都只解决了“画面从哪来”的问题没有解决“画面怎么直播出去”的问题。1.1 常见画面采集方案的边界Camera.onPostRender回调里用GL.ReadPixels或Texture2D.ReadPixels读屏幕像素这个思路能拿到画面但问题在于屏幕坐标反走样、UI层干扰、Canvas Overlay遮挡、以及平台差异Android上GL.ReadPixels有各种兼容性坑都会让“录屏幕”变成“录了个寂寞”。直接对Camera.targetTexture做RenderTexture.active targetTexture再ReadPixels这是最干净的方案。它能绕过UI层只拿相机画面而且和分辨率、抗锯齿、HDR的配合都很直接。但问题也随之而来ReadPixels是个同步阻塞操作你把它放在主线程里一跑帧率立刻掉给你看。这个后面我会专门讲怎么优化。用Unity Recorder或第三方插件录制是省事但录出来的文件是本地MP4距离直播还差一个推流。再叠加商业授权费用很多项目根本划不来。1.2 视频编码与推流的三个可选路径画面拿到之后接下来是编码和推流。这里有三个方向我逐个说下为什么最后选了第三个。Unity原生接口直接编码引擎本身没有暴露出通用的H.264/H.265编码API就算你用VideoCapture这类接口它也只适用于特定平台比如WebCamTexture采集外接摄像头不适用“场景相机画面”这种输入源。做直播编码此路不通。纯C#软编码如FFmpeg.AutoGen封装这个方向理论可行但工作量巨大。你要自己管理AVFrame、AVPacket、像素格式转换RenderTexture读出来是RGBA编码器一般要YUV420P、时间戳、关键帧间隔……这已经不是在写推流功能而是在写一个播放器内核。我见过有人在项目里这么干光调试花屏和音画同步就耗了两周。外部进程调用FFmpeg让Unity当“画面搬运工”把每一帧原始图像喂给FFmpeg进程由FFmpeg完成编码、封装、RTMP推流。这是目前Unity生态里工程上最成熟、坑最少、也最容易出活儿的方案。跨平台统一收到的是裸流任何支持FFmpeg的服务器都能对接而且在Unity崩溃时FFmpeg进程还在——调试定位很方便。所以我的最终选型方案如下表模块选型理由画面获取相机目标RenderTexture避开UI干扰支持任意分辨率与后处理像素读取Texture2D.ReadPixels主线程可控配合协程或子线程可优化编码与封装FFmpeg子进程libx264开源免费支持H.264兼容所有直播平台推流协议RTMP实时消息传输协议平台兼容性最好CDN支持完善服务器端可选方案nginx-rtmp / SRS自建测试链路成本低2. 从RenderTexture到屏幕之外画面采集链路的完整搭建现在开始写代码。整个采集管线的第一个环节是把相机画面装进RenderTexture。2.1 相机输出到RenderTexture的三种姿势我按推荐程度从低到高排序姿势A给相机挂脚本在OnRenderImage里做后处理void OnRenderImage(RenderTexture source, RenderTexture destination) { Graphics.Blit(source, _targetRT); Graphics.Blit(source, destination); }这种方式的好处是能拿到后处理之前的画面适合做美颜、滤镜。但有代价每帧多一次Blit开销而且在某些移动端GPU上OnRenderImage的时序不稳定读取像素的时机不好控制。建议只在需要做画面特效时用。姿势B直接把相机的targetTexture指定为某张RenderTexture_renderTexture new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32); _camera.targetTexture _renderTexture;这种方式干净利落相机直接渲染进RT不经过屏幕。缺点也明显一旦设置了targetTexture相机画面不会自动显示在屏幕上。你需要再拿一张RT或直接把原始RT转成屏幕显示所以通常还要配合一张Camera用于UI层或者手动Blit到屏幕上。姿势C独立渲染相机 主相机各司其职我推荐的做法给“采集相机”设置targetTexture它只负责画面采集。主相机正常渲染UI和场景互不干扰。采集相机的Culling Mask可以只渲染指定层直播画面和本地画面内容不同也没问题。这样最灵活。比如你做直播场景时直播画面需要低分辨率1280x720而本地画面是高分辨率2K或者直播画面要去掉某些UI元素本地画面保留——用独立相机是最省心的。2.2 像素回读从GPU到CPU的关键一步RenderTexture在GPU上FFmpeg需要CPU上的原始数据所以必须把GPU纹理数据读回内存。关键代码这样写public Texture2D ReadPixelsFromRT(RenderTexture rt) { if (_readbackTex null || _readbackTex.width ! rt.width || _readbackTex.height ! rt.height) { _readbackTex new Texture2D(rt.width, rt.height, TextureFormat.RGBA32, false); } RenderTexture.active rt; _readbackTex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0); _readbackTex.Apply(); RenderTexture.active null; return _readbackTex; }这段代码实现的就是把渲染纹理当前内容拷贝到可读写的Texture2D里。但有三个细节必须注意TextureFormat.RGBA32是FFmpeg最容易衔接的格式。注意不是所有平台都支持RGBA32的ReadPixels比如部分Android设备需要回退到BGRA32。Apply()调用会触发像素数据上传到CPU侧。它在某些平台上开销不高在另一些平台上尤其移动端可能莫名其妙卡一下建议实测。RenderTexture.active是全局状态用完必须置null。漏了会造成后续渲染指令发给错误的RT这种Bug排查起来极其痛苦。2.3 时间戳与帧率直播画面不卡顿的关键有了像素数据接下来要做的是按一定节奏把帧交给编码器。常见做法是Update循环里每帧执行一次读取但这样有严重隐患读取和编码都会阻塞主线程Update一卡整个游戏都跟着掉帧。我的节奏控制方案是private float _accumulator 0f; private float _frameInterval 1f / 30f; void Update() { _accumulator Time.deltaTime; if (_accumulator _frameInterval) return; _accumulator 0f; var tex ReadPixelsFromRT(_renderTexture); byte[] rgba tex.GetRawTextureData(); _frameQueue.Enqueue(new FrameData(rgba, Stopwatch.GetTimestamp())); }30FPS对应每帧间隔约33ms这个帧率对直播足够也不至于让编码负载过高。这里推荐用Stopwatch.GetTimestamp()而不是Time.time因为Time.time受Unity的时间缩放影响如果游戏里用了Time.timeScale 0暂停推流画面就会整个冻住——直播功能通常不应该跟着游戏暂停走。这里我必须多说一句如果你把推流帧率设成和游戏渲染帧率完全绑定比如游戏在复杂场景降到15FPS直播画面也会跟着卡成PPT。所以帧率控制必须独立于渲染循环用累加器按真实时间触发采集而不是“每帧都采”。2.4 异步读取移动端和PC的性能分水岭ReadPixels是同步操作它会在CPU等待GPU完成当前渲染任务后才继续执行。在PC上1280x720的读取耗时大约2~5ms勉强可接受。但在Android上同步ReadPixels经常导致单帧卡顿超过20ms推流能和游戏体验直接冲突。Unity 2020.2之后引入的AsyncGPUReadback能解决这个问题。它把读取操作放到GPU后台执行目标帧完成后回调不阻塞主线程AsyncGPUReadback.Request(_renderTexture, 0, TextureFormat.RGBA32, (request) { if (request.hasError) { Debug.LogError(GPU readback error); return; } var data request.GetDatabyte(); _frameQueue.Enqueue(new FrameData(data.ToArray(), Stopwatch.GetTimestamp())); });关键提醒GetDatabyte().ToArray()也会产生拷贝和GC开销建议在收到数据后直接交给编码线程处理不要在Unity主线程里做太多后续操作。在PC上AsyncGPUReadback的性能优势不明显因为显卡驱动对同步读取的优化很好但到了移动端它几乎成了推流不卡的必需品。3. 用FFmpeg子进程把像素变成直播流采集链路铺好之后真刀真枪的活儿来了喂给FFmpeg让它编码并推到RTMP服务器上。3.1 FFmpeg进程的启动参数设计我的做法是Unity作为父进程启动一个FFmpeg子进程通过标准输入stdin向它写入原始RGBA图像数据。FFmpeg参数如下以Windows推流为例ffmpeg -f rawvideo -pixel_format rgba -video_size 1280x720 -framerate 30 -i pipe:0 -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p -f flv rtmp://你的服务器地址/live/直播密钥逐个参数解读-f rawvideo告诉FFmpeg输入格式是未压缩的裸视频。-pixel_format rgba我们传入的数据是RGBA四通道。FFmpeg的rawvideo默认可能是bgr24不显式指定会花屏。-video_size 1280x720宽高必须和RenderTexture的分辨率完全一致。-framerate 30告诉FFmpeg按30FPS的节奏解码输入流实际推流帧率取决于我们喂数据的节奏。-i pipe:0从标准输入读取数据。-c:v libx264使用H.264编码器。libx264是CPU软编码画质好但耗CPU机器性能弱可以换-c:v h264_qsvIntel核显硬件编码或-c:v nvencNVIDIA显卡硬件编码。-preset veryfast -tune zerolatency直播场景必备。veryfast牺牲部分压缩率换编码速度zerolatency减少编码器缓冲端到端延迟能明显降低。-pix_fmt yuv420plibx264不认识RGBA必须转成YUV420P才能编码。注意这里有个坑如果你在前面用-pix_fmt指定了输入格式而这一步又转一次容易搞混。正确的流程是输入rgba交给FFmpeg由FFmpeg内部自动做色彩空间转换到yuv420p不需要手动指定输入像素格式。-f flvRTMP推流通常用FLV封装格式。末尾的地址要写完整RTMP地址的典型格式是rtmp://域名/应用名/串流密钥只写到/live不写密钥的话很多服务器不会接受。C#侧启动进程的代码private Process StartFFmpegProcess(string rtmpUrl, int width, int height, int fps) { string args $-f rawvideo -pixel_format rgba -video_size {width}x{height} -framerate {fps} $-i pipe:0 -c:v libx264 -preset veryfast -tune zerolatency $-pix_fmt yuv420p -f flv \{rtmpUrl}\; ProcessStartInfo psi new ProcessStartInfo { FileName ffmpeg, Arguments args, UseShellExecute false, CreateNoWindow true, RedirectStandardInput true, RedirectStandardOutput true, RedirectStandardError true }; Process proc new Process { StartInfo psi }; proc.Start(); // 关键FFmpeg的日志走stderr必须异步读取否则缓冲区满它会卡死 proc.BeginErrorReadLine(); proc.ErrorDataReceived (sender, e) { if (!string.IsNullOrEmpty(e.Data)) Debug.Log($[FFmpeg] {e.Data}); }; return proc; }几个容易被忽略的细节必须RedirectStandardInput这样进程才能接收Unity喂给它的数据。必须CreateNoWindow true否则发布之后每次推流都会弹出一个难看的黑窗口。必须BeginErrorReadLineFFmpeg的日志全部输出到stderr不异步读取的话缓冲区只要满了FFmpeg会停止工作Unity端看起来就是“推流推到一半没画面了”。进程启动前最好检查FFmpeg是否存在于PATH环境变量中。跨平台发布时建议把对应平台的FFmpeg可执行文件放进StreamingAssets目录运行时动态拼接完整路径。3.2 向FFmpeg写入图像数据进程启动之后Unity侧只需要把每一帧像素数据写入进程的标准输入流。private void EnqueueFrame(byte[] rgbaData) { _frameQueue.Enqueue(rgbaData); } // 在采集线程或协程中循环处理 private IEnumerator FrameSenderCoroutine() { while (_isStreaming) { if (_frameQueue.Count 0 _ffmpegProcess ! null _ffmpegProcess.HasExited false) { byte[] frame _frameQueue.Dequeue(); _ffmpegProcess.StandardInput.BaseStream.Write(frame, 0, frame.Length); _ffmpegProcess.StandardInput.BaseStream.Flush(); } yield return null; } }这段代码有几个问题值得注意第一StandardInput.BaseStream.Write是同步操作如果FFmpeg消费速度跟不上这里会阻塞。安全做法是把它放到独立线程中或者使用异步写WriteAsync。第二如果FFmpeg进程已经退出比如服务器拒连、RTMP地址写错还要继续写流的话StandardInput会直接抛IOException。所以发送前必须用HasExited判断——注意这个判断有竞态条件稳妥的写法是try-catch兜底。第三数据写太快会撑爆FFmpeg的pipe缓冲区。Linux上缺省管道缓冲区是64KB一帧1280x720x4字节大约是3.7MB根本装不下。FFmpeg自己会阻塞读取所以理论上不会丢帧但一旦阻塞后续写入就会排队表现为主线程卡顿。这也是为什么压帧队列长度必须设上限。我的经验是队列长度设为fps * 2超过就丢最老的帧宁可降帧率也不能无限积压。3.3 录制到本地文件同一套管线换个输出参数推流和录制共用前面的采集链路只是FFmpeg的输出目标不同。如果想边录边推可以给FFmpeg加两个输出-f rawvideo -pixel_format rgba -video_size 1280x720 -framerate 30 -i pipe:0 -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p -f flv rtmp://... -f mp4 local.mp4注意FFmpeg的输出路径用空格分开每个输出前都要带自己的编码参数不然第二个输出会继承第一个输出的编码链导致封装格式不匹配。更推荐的做法是推流和录制分别启动两个FFmpeg进程Unity把同一帧数据复制两份分别写入。这样某一端出错不影响另一端故障隔离更好。4. 实测性能数据与优化策略理论讲完上实测数据。以下数据基于我自己的测试环境Intel i7-12700HNVIDIA GeForce RTX 3060 Laptop GPU16GB内存Unity 2021.3.16f1压测场景采用Unity官方内置的HDRP示例场景。4.1 不同分辨率与帧率下的CPU和内存占用分辨率推流帧率画面采集耗时编码器主线程额外耗时Total CPU占用增量1280x72030~3mslibx264 veryfast1~2ms18%~25%1280x72030~3msnvenc约0.5ms6%~9%1920x108030~8mslibx264 veryfast3~4ms40%~55%1920x108030~8msnvenc约0.8ms10%~14%720x48020~1.5mslibx264 ultrafast小于1ms8%~12%从表格能看出几个规律推流分辨率越高编码耗时成倍增长但画面采集耗时的增长相对平缓。硬件编码器nvenc在CPU占用上有压倒性优势如果你面向PC端做直播推流强烈建议优先使用。移动端Android对应的是h264_mediacodec但Unity和Windows的API路径差异很大需要在Android平台上单独做兼容。RTMP推流没有音频的话CPU占用会比上面的数字低一些。一旦接入麦克风音频采集和AAC编码总CPU占用会增加10%左右。4.2 系统内存与GC压力每帧1280x720x4字节大约3.69MB30FPS意味着每秒产生约110MB的像素数据。如果不做内存池每一帧都new byte[]GC会在几秒内暴涨然后Unity卡到没法玩。我实测过两种写法写法A每帧new在推流启动后第8秒左右出现一次明显的帧率尖峰GC Alloc直接飙到300MB以上大概持续2秒才缓过来——画面完全没法看。写法B复用byte[]环形缓冲var frameBytes new byte[width * height * 4]; // 每帧用后归还给缓冲池 _frameBytePool.Enqueue(frameBytes);GC Alloc从300MB降到了不足10MB主线程卡顿彻底消失。这个优化不做你在移动端根本没法用。4.3 局域网延迟实测以nginx-rtmp作为本地服务器、VLC拉流作为播放端测试整体链路延迟如下推流到服务器本地回环300~450ms局域网内从服务器拉流播放450~700ms公网跨机房拉流播放1~3s取决于网络状况和CDN节点调度这个延迟包含采集、编码、推流、服务器分发、播放端缓冲多个环节。要注意的是播放端的缓冲设置对延迟影响极大。VLC默认缓冲较大可以手动把网络缓存调低到500ms以下延迟会有显著改善。5. 音频接入从无声直播到完整音视频流很多做直播的项目前期只做视频推流到联调阶段忽然发现怎么只有画面没有声音原因很简单——我们前面的FFmpeg命令里根本没有音频输入源。5.1 采集Unity内部音频Unity的AudioListener负责收集游戏内所有音源的声音可以通过OnAudioFilterRead回调拿到实时音频数据public class AudioCapture : MonoBehaviour { public int sampleRate 44100; public int channels 2; private Listfloat _audioBuffer new Listfloat(); void Awake() { AudioListener.OnAudioFilterRead OnAudioFilterRead; } void OnAudioFilterRead(float[] data, int channelsFromAudio) { // 追加到缓冲供推流线程取用 lock (_audioBufferLock) { _audioBuffer.AddRange(data); } } }拿到PCM浮点数据后要转成FFmpeg能吃的S16LE格式16位有符号整型小端序每帧按2048个采样点约46ms打包交给FFmpeg的第二个输入。5.2 FFmpeg多输入的音视频合成参数带音频的FFmpeg推流参数示例ffmpeg -f rawvideo -pixel_format rgba -video_size 1280x720 -framerate 30 -i pipe:0 -f s16le -ar 44100 -ac 2 -i pipe:1 -c:v libx264 -preset veryfast -tune zerolatency -pix_fmt yuv420p -c:a aac -b:a 128k -f flv rtmp://你的服务器地址/live/直播密钥这里第二路输入-f s16le -ar 44100 -ac 2 -i pipe:1就是音频。Unity侧启动进程时需要同时RedirectStandardInput和RedirectStandardOutput其中stdin要打开两次管道1是stdin管道2是stderr的流但实际上你不能有两个标准输入。正确的做法是把视频流和音频流用同一个标准输入的不同通道不行。实际上解决思路有两条方案A两条管道分别映射到两张匿名管道Unity自己写两个Stream这需要把FFmpeg的子进程标准输入重定向到自定义管道绕开Process默认的单一stdin。修改命令为-i pipe:1并在C#中用Process启动时还要创建匿名管道工程复杂度上来了。方案B推荐进程只保留一个标准输入用来写视频音频数据写到临时文件或者使用虚拟音频设备FFmpeg命令改成读临时音频文件。代价是本地多出一块磁盘占用但代码复杂度大幅降低。我实际项目里大部分时候用的是方案B用临时WAV文件存音频编码完成后再删掉。虽然不够“优雅”但胜在稳定。6. 踩坑实录RTMP推流的九重劫难最后这部分我把这两年做Unity推流踩过的坑集中整理一遍很多问题单独看很小但串在一起就是让人彻夜难眠的连环坑。6.1 花屏与绿屏的根源花屏80%是像素格式不匹配。RenderTexture里存的是ARGB32但ReadPixels后Texture2D的格式是RGBA32两者通道顺序一致而FFmpeg的rawvideo默认解析BGR24如果你不显式指定-pixel_format rgba它就会按BGR解析RGBA数据结果人脸的红色和蓝色直接对调画面看起来像外星生物。修法-pixel_format rgba写清楚一个字符都不能错。另一种花屏是分辨率不匹配。比如RT是1284x722FFmpeg参数写的是1280x720多出来的几行像素被当成下一帧的起始数据整个画面会斜着撕裂。解决办法只有一个RT创建和FFmpeg参数必须从同一个常量读取绝对不能手写两处。6.2 推流地址写错导致进程秒退RTMP地址的路径分为两段应用路径app name和串流密钥stream key。以rtmp://192.168.1.100/live/test123为例live是应用路径test123是密钥。如果服务器配置不匹配FFmpeg会输出类似Server error: 404或Connection refused的日志然后就退出了。遇到这种情况务必盯着FFmpeg的stderr输出排查而不是盲目改Unity代码。很多初学者一见到推流失败第一反应是检查C#代码其实90%的问题出在服务器端或者地址格式。6.3 IPC管道阻塞与帧队列长度前文提到的帧队列上限必须设置。当FFmpeg编码速度跟不上采集速度时帧队列会越积越长内存以每秒110MB的速度增长几十秒后内存就爆了。建议队列长度设为推流帧率 * 2超过上限直接丢弃最老的帧。这种“丢帧保实时”的策略在直播场景下远比“全部缓存”更合理。6.4 渲染管线兼容性内置管线 vs URP vs HDRP内置渲染管线我上面的代码直接用没问题。URP相机的targetTexture仍然有效但后处理Volume的某些效果比如Bloom可能在读回的画面里体现不出来。如果直播预期包含后处理效果需要在URP的RenderFeature或CustomPass里做处理。HDRP需要特别注意RenderTexture的格式设置。HDRP默认使用高精度HDR格式如果RT格式不匹配读出来会出现颜色过亮或发灰的情况。建议把RT格式改为RenderTextureFormat.ARGB32并关闭HDR。6.5 Android平台的坑AsyncGPUReadback在Android上需要OpenGL ES 3.0以上否则会报错。移动端推流使用CPU软编码libx264会直接把手机变成暖手宝。建议Android上用c:v h264_mediacodec硬件编码。注意不是所有Android设备都支持1080P以上分辨率的硬编适配前要在目标机型上做兼容测试。Android的Application.targetFrameRate如果设置成60而推流帧率是30CPU会有明显浪费。建议推流时把渲染帧率固定为推流帧率的整数倍比如60/30或者使用累加器策略跳过部分帧。6.6 纹理读回时机错误在Camera渲染完成之前去ReadPixels拿到的是上一帧甚至空白数据。解决办法是使用WaitForEndOfFrame协程等待本帧渲染完成后再读取或者直接在OnPostRender回调中等相机完成渲染。6.7 硬编解码导致的延迟堆积用硬件编码器时如果同时开了B帧编码器会把帧的重排缓冲开到很大最终表现为延迟越来越高。直播场景务必设置-bf 0禁用B帧或使用-tune zerolatency。这一点不管软编硬编都适用。6.8 断线重连机制RTMP连接不稳定时FFmpeg进程会退出推流直接断掉。工程上不能指望推流永远稳定必须在Unity侧实现断线重连监听FFmpeg进程退出事件确认退出之后重新拉起进程并重置状态。我的做法是proc.Exited (sender, e) { if (_isStreaming) { _reconnectAttempts; if (_reconnectAttempts 3) StartCoroutine(RestartFFmpegProcess()); else OnStreamFatalError(); } };重连的关键是先结束旧进程如果还没退出清空帧队列重新运行启动命令。因为推流断线一般意味着服务器连接有问题单靠继续写管道是挽救不了的必须重建进程。6.9 时间戳与音画同步如果你在推视频流的过程中播放端出现越拉越慢或音画不同步多半是时间戳问题。FFmpeg从rawvideo输入时没有时间戳会按设定的framerate自动生成。如果Unity端实际推送节奏不稳忽快忽慢播放端画面就会加快或减慢。缓解办法在FFmpeg输入参数里加上-use_wallclock_as_timestamps 1让FFmpeg根据系统时钟而不是输入帧数生成时间戳。加了这个参数后即使Unity端掉了几帧播放端也能相对正常地播放不会越积越偏。7. 扩展思路把推流能力模块化功能跑通之后你会发现整套推流模块其实是完全通用的——渲染管线里面不管是什么画面只要是RenderTexture就能推出去。所以到了项目后期我建议你把推流封装成一个独立的模块对外暴露三个接口StartStream(string rtmpUrl)启动推流。StopStream()停止推流释放资源。SetFrameSource(RenderTexture rt)切换推流画面源。这样无论后面是推场景相机、推UI层还是推某个独立的二次渲染RT都只是一行代码的事。甚至可以做一个带透明通道的RT用green screen做虚拟背景推流——这套架构完全撑得住。如果在实际落地中遇到“FFmpeg服务端拒绝连接”这类问题可以先在命令行手动执行一遍同样的FFmpeg命令单独确认是编码参数还是服务器问题。这条排错路径能帮你节省至少两个小时的无效调试时间。整个方案的完整源码结构大致为CameraCapturer负责RT创建与像素读取FrameQueue管理帧缓冲FFmpegStreamer管理进程生命周期与数据写入AudioCapture补上音频通道ReconnectHandler处理断线重连。每块职责单一替换起来也顺手。做完这一次之后以后任何Unity直播需求你都能在半天内接上而不是再从零开始撞坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价