ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity游戏内录指南:官方Recorder与AudioListener音频捕获实战

Unity游戏内录指南:官方Recorder与AudioListener音频捕获实战 做 Unity 项目时“内录”通常有两类需求一类是把 Play 模式下 Game 视图的画面连同声音一起录成视频用来做演示、回放或项目验收素材另一类是把游戏内正在播放的音频直接捕获成音频文件比如录下 BGM、音效、某个动态生成的语音而不是拿麦克风去外录环境声。很多开发者第一反应是装 OBS 或第三方录屏插件其实 Unity 自己的方案完全可以覆盖这两种场景而且不需要额外付费。这篇文章覆盖三条路径第一使用 Unity 官方 Recorder 插件录制带声音的游戏视频第二用 AudioListener 的OnAudioFilterRead回调做运行时音频内录纯 C# 脚本零插件依赖第三补充 AudioRenderer 的离线捕获思路作为进阶备用方案。另外会给出 Recorder 的脚本控制写法、常见排查清单和批量录制建议。看完之后你可以直接照着把“录视频”和“录音频”两条链路分别跑通。先把结论放前面需要录带声音的视频优先装 Unity Recorder它是 Unity 官方包通过 Package Manager 安装属于第一方插件如果只是想拿游戏内部最终混合的音频不想额外装任何东西用AudioListener OnAudioFilterRead最直接。两种方式都不挑显卡硬件重点在 CPU 编码能力和磁盘写入速度。1. 核心能力速览能力项Unity Recorder官方包AudioListener 内录脚本AudioRenderer 捕获安装方式Package Manager 安装 com.unity.recorder不需要安装纯 C# 脚本不需要安装引擎 API录制内容Game View 或指定 Camera 视频帧 场景音频AudioListener 最终输出 PCM捕获整个音频输出缓冲输出格式H.264 MP4、VP8 WebM、ProRes 等WAV 16bit PCM需要自己封装录制期间是否影响正常听感不影响场景音频正常播放不影响拿到的是正常播放链路上的数据有影响捕获状态下扬声器输出会被接管是否支持脚本控制支持Editor 脚本可自动启停支持运行时任意时刻启停支持运行时 API 控制硬件门槛主要吃 CPU 编码分辨率越高越吃 CPU极低但要注意音频线程不能做耗时写入较低脚本缓冲需要自己处理典型场景游戏演示视频、关卡回放、项目交付录屏采集游戏内音频、音频素材备份、语音内容留档无头环境或自定义音频处理管线需要注意这里的“自带”按 Unity 官方渠道理解Recorder 是 Unity 官方维护的录制包在包管理器里直接搜索安装不是第三方插件音频内录方案更是完全使用引擎已有能力连包都不用装。2. 适用场景与使用边界这套方案适合这几类开发者独立开发或个人开发者需要快速产出游戏 Demo 视频要求画面和声音同步不想折腾 OBS 场景配置。需要录制“游戏内部听到的声音”例如动态生成的语音、程序化音效、AudioMixer 混合后的最终结果外录麦克风会混入环境噪声必须走内录链路。音频或美术同学需要把 Unity 里的 AudioClip 资源批量导出成 WAV可以用运行时脚本批量遍历资源完成。项目需要自动化录制例如 CI 流程里每天自动跑一遍关卡并录屏用 Recorder 的 Editor 脚本 API 可以做到。使用边界也要先说清楚Recorder 的实时录屏依赖 Play 模式本质上是在编辑器或打包后的运行时抓取画面帧录制 Game View 时不要随便拖动编辑器窗口否则画面可能抖动。Recorder 在 Windows 上最通用的视频格式是 H.264 MP4部分编码器如 ProRes主要在 macOS 上可用具体看版本。音频内录获取的是 Unity 音频引擎混合后的数据它不会录到麦克风声音也不会录到系统其他应用的声音。想要“连讲话带游戏声音一起录”那是另一个方案不是本文讨论的内录。如果录制对象包含付费音乐、商业音效、他人语音发布和商用前必须确认授权。尤其游戏内播放的外部音乐直接录出来再分发会带来版权风险。3. 原理先理解视频抓帧与音频回调写代码之前先花两分钟想清楚这两条链路的数据走向后面排查问题会快很多。视频链路Recorder 在编辑器里通过 Camera 或 Game View 进行纹理抓帧每一帧图像送到编码器同时 Recorder 内部有音频捕获模块把 AudioListener 输出的音频采样同步写入视频文件的音轨最终混流成 MP4 或 WebM。所以 Recorder 的能力本质是“图像抓帧 音频同步 编码器封装”。音频链路Unity 音频引擎每帧混合完所有 AudioSource、AudioMixer、实时效果器之后会把一段 PCM 数据交给 AudioListener 播放。如果在带有 AudioListener 的 GameObject 上挂一个实现了OnAudioFilterRead的脚本音频渲染线程就会在送出数据之前回调这个方法参数data就是混合好的 float 数组。把这段 float 数组转成 16bit PCM 并写入文件就是最纯粹的“内录”。关键点有两个OnAudioFilterRead回调发生在音频线程不能在里面直接调用主线程的 GameObject、Transform、UI、Debug 大部分 API也不能做耗时文件写入。data数组长度由音频系统当前缓冲设置决定channels表示当前音频输出的声道数。每帧拿到的 data 是“帧样本数 × 声道数”的线性数组顺序是交错排列左声道样本、右声道样本、左声道样本、右声道样本依此类推。理解了这两个链路下面两种方案就很容易落地。4. 方案一Unity Recorder 官方插件录制带声音视频Recorder 是最省事的官方视频录制方案适合录制画面和声音同步的游戏演示。因为是通过 Package Manager 安装实际版本会随着项目 Unity 版本变化但基本操作流程一致。4.1 安装 Recorder 包打开 Package Manager左上角包来源选择 Unity Registry在搜索框输入 Recorder安装官方发布的 com.unity.recorder。安装完成后菜单栏会出现对应入口。较新的 Unity 版本路径是Window General Recorder Recorder Window如果用的是旧版本可能是Window Recorder Recorder Window。找不到时直接看菜单栏是否有 Recorder 相关入口即可。4.2 Recorder Window 图形化录制打开 Recorder Window 后操作步骤如下点击 Add Recorder选择 Movie。Source 选择 Game View这时录制内容就是 Game 标签页的画面。也可以选 Camera再指定场景中某个摄像机适合只录单独摄像机画面的情况。Resolution 选择 1080p 或 Custom自定义分辨率时直接填宽高。Frame Rate 设置 30 或 60录动作多的游戏建议 60。勾选 Capture Audio这一步是视频带声音的关键不勾的话录出来的 MP4 没有音轨。Output Path 保持默认一般会输出到项目根目录下的 Recordings 文件夹。点击 Start Recording然后正常进入 Play 或直接在 Play 模式下操作游戏。结束点击 Stop Recording文件会出现在 Project 窗口的 Recordings 目录下。录制过程中注意Game View 里如果出现编辑器辅助信息、Gizmos、Stats 浮层都会一起录进去。正式录制前先把Gizmos按钮关掉。4.3 用脚本控制 Recorder 自动录制如果只是手动录一次图形界面完全够用。但要接入自动化流程比如一键录制当前场景并生成 MP4可以用 Recorder 的 Editor API 写一个菜单脚本。下面是一个参考示例创建 MovieRecorderSettings 并挂到 RecorderControllerusing UnityEditor; using UnityEditor.Recorder; using UnityEditor.Recorder.Input; using UnityEngine; public static class RecorderAutoStart { [MenuItem(Tools/开始录制 1080P 视频)] public static void StartRecording() { var controllerSettings ScriptableObject.CreateInstanceRecorderControllerSettings(); var recorderController new RecorderController(controllerSettings); var movieRecorder ScriptableObject.CreateInstanceMovieRecorderSettings(); movieRecorder.name Auto Movie Recorder; movieRecorder.Enabled true; // 输出格式H.264 MP4 movieRecorder.OutputFormat MovieRecorderSettings.VideoRecorderOutputFormat.MP4; movieRecorder.VideoBitRateMode VideoBitrateMode.High; // 录制 Game View movieRecorder.ImageInputSettings new GameViewInputSettings { OutputWidth 1920, OutputHeight 1080 }; // 同时录制场景音频 movieRecorder.CaptureAudio true; controllerSettings.AddRecorderSettings(movieRecorder); controllerSettings.SetRecordModeToManual(); controllerSettings.FrameRate 30.0f; recorderController.StartRecording(); } [MenuItem(Tools/停止录制)] public static void StopRecording() { var controllerSettings ScriptableObject.CreateInstanceRecorderControllerSettings(); var recorderController new RecorderController(controllerSettings); recorderController.StopRecording(); } }这里有两个细节要说明不同版本的 Recorder 命名空间略有差异导出 MP4 的枚举写法在 2.x 和 3.x 中都接近上述形式但如果你安装的是旧版需要以当前版本 API 为准CaptureAudio属性用来控制视频文件是否写入音轨一定不要漏掉。这个脚本放在 Editor 文件夹下编译通过后点击菜单即可开始和停止录制。5. 方案二AudioListener 音频内录零依赖写 WAV如果只需要录游戏内最终播放的音频不想引入 Recorder 包也完全可行。做法是把脚本挂在带有 AudioListener 的物体上通过OnAudioFilterRead拿到混合好的 PCM再用一个缓冲队列交给主线程写文件。5.1 为什么用缓冲队列直接写在OnAudioFilterRead里最快但这是错误做法。音频线程是实时线程文件写入有磁盘 IO 延迟一旦阻塞会导致音频卡顿、爆音甚至整个音频管线异常。正确做法是音频线程只把 float 数据塞进队列主线程的Update里统一取出并写入文件。5.2 完整 C# 脚本下面是一个可直接用的脚本包含开始录制、停止录制、WAV 头生成和缓冲处理using System.Collections.Concurrent; using System.IO; using UnityEngine; public class AudioCaptureToWav : MonoBehaviour { [Header(录制状态)] public bool IsRecording { get; private set; } private ConcurrentQueuefloat sampleQueue new ConcurrentQueuefloat(); private FileStream fileStream; private BinaryWriter writer; private int sampleRate; private int channelCount; private long totalSamples; void Update() { if (!IsRecording || writer null) return; // 把音频线程积累的数据写入文件 int writeCount 0; while (sampleQueue.TryDequeue(out float sample)) { short value (short)(Mathf.Clamp(sample, -1f, 1f) * short.MaxValue); writer.Write(value); totalSamples; writeCount; // 防止主线程写太猛每帧最多写 65536 个采样 if (writeCount 65536) break; } } /// summary /// 开始录制输出路径如: C:/Recordings/test.wav /// /summary public void StartRecording(string filePath) { if (IsRecording) return; sampleRate AudioSettings.outputSampleRate; channelCount 2; // 按双声道写OnAudioFilterRead 回调时会被修正 fileStream new FileStream(filePath, FileMode.Create); writer new BinaryWriter(fileStream); // 先写 44 字节占位停止录制时再回填 WAV 头 writer.Write(new byte[44]); totalSamples 0; IsRecording true; Debug.Log([AudioCapture] 开始录制: filePath); } /// summary /// 停止录制并生成正确的 WAV 文件头 /// /summary public void StopRecording() { if (!IsRecording) return; IsRecording false; // 把剩余队列写干净 while (sampleQueue.TryDequeue(out float sample)) { short value (short)(Mathf.Clamp(sample, -1f, 1f) * short.MaxValue); writer.Write(value); totalSamples; } long fileLength writer.BaseStream.Length; int dataLength (int)(totalSamples * 2); // 回填 WAV 头 writer.Seek(0, SeekOrigin.Begin); writer.Write(System.Text.Encoding.ASCII.GetBytes(RIFF)); writer.Write((int)(fileLength - 8)); writer.Write(System.Text.Encoding.ASCII.GetBytes(WAVE)); writer.Write(System.Text.Encoding.ASCII.GetBytes(fmt )); writer.Write(16); // PCM chunk size writer.Write((short)1); // PCM format writer.Write((short)channelCount); // 声道数 writer.Write(sampleRate); // 采样率 writer.Write(sampleRate * channelCount * 2); // 字节率 writer.Write((short)(channelCount * 2)); // block align writer.Write((short)16); // 位深 writer.Write(System.Text.Encoding.ASCII.GetBytes(data)); writer.Write(dataLength); writer.Close(); fileStream.Close(); writer null; fileStream null; Debug.Log([AudioCapture] 录制结束文件大小: fileLength bytes); } private void OnAudioFilterRead(float[] data, int channels) { if (!IsRecording) return; channelCount channels; for (int i 0; i data.Length; i) { sampleQueue.Enqueue(data[i]); } } private void OnDisable() { if (IsRecording) StopRecording(); } }脚本逻辑分三层OnAudioFilterRead是音频线程回调只负责把 float 数据入队不碰文件。Update是主线程写入逻辑每帧从队列取数据转成 16bit 并写入文件。StartRecording和StopRecording对外暴露接口停止时回填 WAV 头。5.3 挂载和使用步骤把脚本挂到场景中带有 AudioListener 的 GameObject 上默认的 Main Camera 就有 AudioListener。确保场景里有正在播放的 AudioSource比如一个循环 BGM。运行时调用StartRecording(C:/Recordings/test.wav)。等音频播放几秒调用StopRecording()。用任意播放器打开 WAV 文件确认是游戏内部声音。这里有一个很关键的限制OnAudioFilterRead只有在场景里有 AudioListener 且脚本挂在包含 AudioListener 的物体上时才稳定触发。如果脚本挂在普通物体上回调可能完全不会被调用这是排查时最先要确认的条件。5.4 工程设计优化上面的脚本可运行但工程化使用可以继续优化用Queuefloat加锁代替ConcurrentQueuefloat减少无锁队列内部开销但要注意锁粒度。每帧最多写入 65536 个采样是一种保护避免 Update 卡太长时间。如果录 48kHz 立体声65536 个采样大约是 0.68 秒数据正常每帧最多几千个采样不会触发。文件路径建议加时间戳避免多次录制互相覆盖。如果录制时间很长考虑每隔一段时间手动调用Flush或重新打开文件防止程序崩溃丢数据。如果 Unity 工程里有多个 AudioListener 或者启用了空间音频相关设置以实际回调数据为准WAV 头里的声道数和采样率都要和回调参数保持一致。6. 方案三AudioRenderer 捕获进阶备用除了OnAudioFilterReadUnity 还提供了一个更底层的AudioRendererAPI它直接把音频输出重定向到脚本缓冲适合无头测试、自定义音频分析、离线批量处理。核心思路是调用AudioRenderer.Start()接管音频输出。每帧通过AudioRenderer.GetSampleCountForCaptureBuffer()获取当前未消费的采样数。用AudioRenderer.GetScriptingBuffer(buffer)把采样读出来。处理完后AudioRenderer.Stop()恢复。示例代码框架using UnityEngine; public class AudioRendererCapture : MonoBehaviour { void Start() { AudioRenderer.Start(); } void Update() { uint sampleCount AudioRenderer.GetSampleCountForCaptureBuffer(); if (sampleCount 0) { float[] buffer new float[sampleCount]; AudioRenderer.GetScriptingBuffer(buffer); // 在这里把 buffer 写入自定义音频处理管线或文件 // 注意启动 AudioRenderer 后扬声器不再输出声音 } } void OnDestroy() { AudioRenderer.Stop(); } }这个方案的特点是会接管音频输出录制过程中扬声器可能听不到声音而且从脚本缓冲拿到的数据格式也需要自己封装成 WAV 或交给后续处理。相比OnAudioFilterRead它的侵入性更强适合已经有音频处理管线、需要直接抓整条输出链路的项目。如果不是必须用底层 API日常内录选方案二更稳。7. 功能测试与效果验证写完之后不要直接上大参数先按小规模验证流程跑通确认链路正常再扩展。7.1 视频内录验证流程打开任意一个测试场景确认 Game View 有内容。安装 Recorder打开 Recorder Window添加 Movie。分辨率先选 720p帧率 30Capture Audio 勾选。场景里放一个循环 AudioSource跑 10 秒点击 Stop。打开 Recordings 目录下的 MP4检查画面是否流畅、声音是否同步。判断成功的标准画面帧率稳定视频音轨和场景声音一致文件没有被锁占用。常见失败点如果 MP4 没有声音先检查 Capture Audio 是否勾选如果文件生成失败看 Console 是否有编码器报错H.264 不可用时切成 VP8 WebM 再试。7.2 音频内录验证流程场景中保留默认 Main Camera挂上AudioCaptureToWav。添加一个 AudioSource指定任意测试 AudioClip勾选 Loop。在测试脚本里调用StartRecording播放 5 秒后调用StopRecording。用 Unity 的 AudioClip 加载或者外部播放器打开 WAV确认时长和内容一致。判断成功的标准WAV 文件头部正确播放时能听到 AudioSource 内容没有明显爆音和杂音文件长度和录制时长基本匹配。可以先做一个脚本自动循环验证using UnityEngine; public class RecorderTest : MonoBehaviour { public AudioCaptureToWav capture; public AudioSource targetSource; void Start() { capture.StartRecording(Application.dataPath /../test_record.wav); } void Update() { if (Time.time 5f capture.IsRecording) { capture.StopRecording(); Debug.Log(录制完成); #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying false; #endif } } }这个测试脚本跑 5 秒自动停止适合确认完整链路是否可用。7.3 资源占用观察录制视频时CPU 会有一段时间持续飙升尤其是 H.264 编码。观察重点在 Task Manager 或 macOS 活动监视器中的 Unity 进程 CPU 占用以及磁盘写入速度。如果编码导致掉帧优先降分辨率或帧率。音频内录的资源占用非常低它只是多了一层 PCM 拷贝和文件写入正常情况不会影响游戏帧率。但如果发现音频线程卡顿、爆音优先检查是不是在OnAudioFilterRead里做了编码、加密、网络发送等耗时操作这些都应该移到主线程或专用线程。8. 常见问题与排查方法问题现象可能原因排查方式解决方案Recorder 窗口菜单找不到Recorder 包未安装或版本过旧检查 Package Manager 是否已安装 com.unity.recorder安装最新 Recorder 包录出来的 MP4 没有声音未勾选 Capture Audio检查 Recorder 设置面板勾选 Capture AudioH.264 选项不存在当前平台或 Recorder 版本缺少对应编码器看 Recorder 设置里的格式列表切换 VP8 WebM 或更新 Recorder录制时画面掉帧严重分辨率过高、帧率过高、CPU 编码能力不足观察任务管理器中 Unity 进程 CPU降分辨率、降到 30fps或减少同时运行的其他程序OnAudioFilterRead不回调脚本没有挂在带 AudioListener 的物体上检查挂载物体的组件列表把脚本挂到 Main CameraWAV 文件播不了停止录制前没有回填 WAV 头用十六进制查看文件开头确保 StopRecording 正确关闭 writer录出来的声音全是爆音写入时没有 Clamp或采样格式不对检查转换逻辑写入前先 Clamp 到 -1 到 1 再转 short音频线程卡顿在 OnAudioFilterRead 里做了文件写入或网络请求断开调试观察是否复现把耗时操作移到主线程或缓冲队列停止 Play 后 WAV 文件丢失OnDisable 没有触发或异常中断看 Console 日志在 OnDisable 里补 StopRecording一个场景里录多个片段不好管理没有文件命名策略检查输出目录文件名加时间戳和场景名9. 最佳实践与合规提醒工程化使用内录时建议保持一套严格习惯先小参数验证。第一次录视频用 720p 30fps录音频只录 3 到 5 秒确认链路通了再上大任务。输出目录固定。视频用 Recorder 默认 Recordings 目录音频脚本统一输出到项目外的Recordings/Audio不要污染 Assets 目录否则 Unity 会频繁刷新资源数据库。文件名加时间戳。用DateTime.Now.ToString(yyyyMMdd_HHmmss)拼文件名避免覆盖。批量任务需要加日志。在开始、停止、出错三个节点都打日志录制队列卡住时能快速定位。接 API 自动化时录制服务要设计成可重入。如果 Recorder 已经开始录制再触发 Start 会报错需要先判断当前状态。音频内录只用于已经获得授权的素材。游戏里的音乐、音效、语音如果来源是第三方录制后分发或商用前必须确认许可。如果录到真实用户的声音比如玩家语音聊天或实时对话要提前告知用户并获得授权不能把用户语音随意保存和使用。10. 总结与下一步Unity 的内录需求可以拆成两条独立链路要视频用官方 Recorder图形界面和脚本 API 都能控制只要音频用OnAudioFilterRead写 WAV零插件依赖非常适合游戏内音频采集。最先应该验证的是最简单的链路挂脚本、录 5 秒 AudioSource、回放检查声音。最容易踩的坑是脚本没挂在带 AudioListener 的物体上导致回调不触发以及 WAV 头没回填导致文件打不开。后续可以继续扩展的方向是把 Recorder 脚本接入 CI每日自动录制关卡画面把音频内录脚本接成批量音频导出工具遍历 Assets 里的 AudioClip 转成 WAV或者在OnAudioFilterRead的基础上加音频分析逻辑做响度统计和频谱可视化。这套基础跑通后其他高级用法都是在这个数据链路上加处理模块。
返回列表