using UnityEngine; using System; using AX.AudioSystem; /// /// 录音机,表示一个音频录制器。 /// [RequireComponent(typeof(AudioSource), typeof(PromptTone))] public class AudioRecorder : MonoBehaviour { [Header("音频录制设备的 Id")] public int DeviceId = 0; [Header("音频采样率")] public int SamplingRate = 8000; [Header("音频录制最大时长,按秒计")] public int RecordMaxTime = 60; public event Action Started; public event Action> Stopped; private volatile bool isRecording; private TimeSpan timeSpan; private DateTime startTime; private AudioSource audioSource; private OpusEncoder opusEncoder; private BetterList opusBuffer; private float[] audioBuffer; private float[] packageBuffer; private byte[] frameBuffer; private int packageSize; /// /// 表示音频录制器是否已经准备就绪。返回 true 表示准备就绪;返回 false 表示当前没有录制设备。 /// public bool Ready { get { return Microphone.devices.Length > 0; } } /// /// 表示音频录制器当前是否正在录制中。 /// public bool IsRecording { get { return isRecording; } } /// /// 表示单声道录制还是双声道录制。 /// public int Channels { get { return audioSource.clip.channels; } } void Start() { audioSource = GetComponent(); timeSpan = TimeSpan.FromSeconds(RecordMaxTime); frameBuffer = new byte[OpusEncoder.PacketMaxSize]; } void Update() { if (IsRecording && DateTime.Now - startTime >= timeSpan) StopRecorder(); } void OnDestroy() { if (opusEncoder != null) { opusEncoder.Dispose(); opusEncoder = null; } } public void StartRecorder() { var clip = Microphone.Start(Microphone.devices[DeviceId], false, RecordMaxTime, SamplingRate); if (opusBuffer == null) { opusEncoder = new OpusEncoder((SamplingRate)SamplingRate, (Channel)clip.channels); packageSize = opusEncoder.FrameSize * 1; opusBuffer = new BetterList(200 * 1024); // 200KB 大概相当于录音 60 秒编码的长度 packageBuffer = new float[packageSize]; } var length = clip.samples + PromptTone.Data.Length; if (audioBuffer == null || audioBuffer.Length < length) audioBuffer = new float[length]; audioSource.clip = clip; audioSource.mute = true; audioSource.Play(); startTime = DateTime.Now; isRecording = true; OnStarted(); } public bool StopRecorder() { if (!isRecording) return false; var device = Microphone.devices[DeviceId]; var pos = Microphone.GetPosition(device); // Unity3D 的 Microphone 获取的位置有时候会超过上限 // 因此限定一下,以免数组越界 pos = Math.Min(pos, audioSource.clip.samples); Microphone.End(device); audioSource.Stop(); if (pos > 0) audioSource.clip.GetData(audioBuffer, 0); Array.Copy(PromptTone.Data, 0, audioBuffer, pos, PromptTone.Data.Length); var length = pos + PromptTone.Data.Length; for (var i = 0; i + packageSize <= length; i += packageSize) { Array.Copy(audioBuffer, i, packageBuffer, 0, packageBuffer.Length); var encoded = opusEncoder.Encode(packageBuffer, frameBuffer); opusBuffer.Add((byte)encoded); opusBuffer.AddRange(frameBuffer, 0, encoded); } Destroy(audioSource.clip); audioSource.clip = null; isRecording = false; OnStopped(new ArraySegment(opusBuffer.GetBuffer(), 0, opusBuffer.Count)); opusBuffer.Clear(); return true; } protected virtual void OnStarted() { if (Started != null) Started(); } protected virtual void OnStopped(ArraySegment data) { if (Stopped != null) Stopped(data); } }