网上演练
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
4.2 KiB

using UnityEngine;
using System;
using AX.AudioSystem;
/// <summary>
/// 录音机,表示一个音频录制器。
/// </summary>
[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<ArraySegment<byte>> Stopped;
private volatile bool isRecording;
private TimeSpan timeSpan;
private DateTime startTime;
private AudioSource audioSource;
private OpusEncoder opusEncoder;
private BetterList<byte> opusBuffer;
private float[] audioBuffer;
private float[] packageBuffer;
private byte[] frameBuffer;
private int packageSize;
/// <summary>
/// 表示音频录制器是否已经准备就绪。返回 true 表示准备就绪;返回 false 表示当前没有录制设备。
/// </summary>
public bool Ready
{
get { return Microphone.devices.Length > 0; }
}
/// <summary>
/// 表示音频录制器当前是否正在录制中。
/// </summary>
public bool IsRecording { get { return isRecording; } }
/// <summary>
/// 表示单声道录制还是双声道录制。
/// </summary>
public int Channels
{
get { return audioSource.clip.channels; }
}
void Start()
{
audioSource = GetComponent<AudioSource>();
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<byte>(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<byte>(opusBuffer.GetBuffer(), 0, opusBuffer.Count));
opusBuffer.Clear();
return true;
}
protected virtual void OnStarted()
{
if (Started != null)
Started();
}
protected virtual void OnStopped(ArraySegment<byte> data)
{
if (Stopped != null)
Stopped(data);
}
}