-
Notifications
You must be signed in to change notification settings - Fork 6
/
WaveVoiceBuffer.cs
141 lines (115 loc) · 4.29 KB
/
WaveVoiceBuffer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#nullable enable
using System;
using System.IO;
using System.Threading;
using Cysharp.Threading.Tasks;
using NAudio.Wave;
using Unity.Logging;
namespace Mochineko.VoiceActivityDetection
{
/// <summary>
/// A voice buffer that writes to a wave file stream.
/// </summary>
public sealed class WaveVoiceBuffer : IVoiceBuffer
{
private readonly IWaveStreamReceiver receiver;
private readonly WaveFormat format;
private readonly object lockObject = new();
private Stream? stream;
private WaveFileWriter? writer;
private int sizeCounter;
/// <summary>
/// Creates a new instance of <see cref="WaveVoiceBuffer"/>.
/// </summary>
/// <param name="receiver">Receiver of wave file stream when voice changes inactive state.</param>
/// <param name="samplingRate">Sampling rate of voice data.</param>
/// <param name="bitsPerSample">Bits count per each sample to write wave data, 16, 24 or 32.</param>
/// <param name="channels">Channels count of voice data</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
public WaveVoiceBuffer(
IWaveStreamReceiver receiver,
int samplingRate = 44100,
int bitsPerSample = 16,
int channels = 1)
{
if (samplingRate <= 0)
{
throw new ArgumentOutOfRangeException(nameof(samplingRate), samplingRate, "samplingRate must be positive value.");
}
if (bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32)
{
throw new ArgumentOutOfRangeException(nameof(bitsPerSample), bitsPerSample, "bitsPerSample must be 16, 24 or 32.");
}
if (channels <= 0)
{
throw new ArgumentOutOfRangeException(nameof(channels), channels, "channels must be positive value.");
}
this.receiver = receiver;
this.format = new WaveFormat(
rate: samplingRate,
bits: bitsPerSample,
channels: channels);
}
void IDisposable.Dispose()
{
Reset();
}
async UniTask IVoiceBuffer.BufferAsync(VoiceSegment segment, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (writer == null)
{
throw new InvalidOperationException();
}
await UniTask.SwitchToThreadPool();
lock (lockObject)
{
sizeCounter += segment.Length;
Log.Verbose("[VAD] Write {0} / {1} samples to wave stream.", segment.Length, sizeCounter);
writer.WriteSamples(segment.Buffer, offset: 0, segment.Length);
}
await UniTask.SwitchToMainThread(cancellationToken);
}
UniTask IVoiceBuffer.OnVoiceActiveAsync(CancellationToken cancellationToken)
{
Reset();
stream = new MemoryStream();
lock (lockObject)
{
writer = new WaveFileWriter(
outStream: stream,
format: format);
}
sizeCounter = 0;
return UniTask.CompletedTask;
}
async UniTask IVoiceBuffer.OnVoiceInactiveAsync(CancellationToken cancellationToken)
{
if (stream == null)
{
throw new InvalidOperationException();
}
if (writer == null)
{
throw new InvalidOperationException();
}
await writer.FlushAsync(cancellationToken);
// NOTE: Please dispose copied stream by receiver.
var copiedStream = new MemoryStream();
stream.Seek(offset: 0, SeekOrigin.Begin);
await stream.CopyToAsync(copiedStream, cancellationToken);
copiedStream.Seek(offset: 0, SeekOrigin.Begin);
receiver.OnReceive(copiedStream);
}
private void Reset()
{
lock (lockObject)
{
writer?.Dispose();
writer = null;
// NOTE: stream is disposed by writer.
stream = null;
}
}
}
}