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
5 years ago
|
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);
|
||
|
}
|
||
|
}
|