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
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); |
|
} |
|
} |