Skip to content

Commit 8dca68e

Browse files
committed
Fixing samples
1 parent 5eee277 commit 8dca68e

12 files changed

+383
-216
lines changed

sdk/ai/Azure.AI.VoiceLive/samples/BasicVoiceAssistant/BasicVoiceAssistant.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ItemGroup>
1414
<PackageReference Include="Azure.Core"/>
1515
<PackageReference Include="Azure.Identity"/>
16-
<PackageReference Include="NAudio" VersionOverride="1.10.0"/>
16+
<PackageReference Include="NAudio" VersionOverride="2.2.1"/>
1717
<PackageReference Include="Microsoft.Extensions.Configuration" VersionOverride="8.0.0"/>
1818
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" VersionOverride="8.0.0"/>
1919
<PackageReference Include="Microsoft.Extensions.Configuration.Json" VersionOverride="8.0.0"/>

sdk/ai/Azure.AI.VoiceLive/samples/CustomerServiceBot/AudioProcessor.cs

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,41 @@ namespace Azure.AI.VoiceLive.Samples;
88

99
/// <summary>
1010
/// Handles real-time audio capture and playback for the voice assistant.
11-
///
11+
/// </summary>
12+
/// <remarks>
1213
/// Threading Architecture:
1314
/// - Main thread: Event loop and UI
1415
/// - Capture thread: NAudio input stream reading
1516
/// - Send thread: Async audio data transmission to VoiceLive
1617
/// - Playback thread: NAudio output stream writing
17-
/// </summary>
18+
/// </remarks>
1819
public class AudioProcessor : IDisposable
1920
{
2021
private readonly VoiceLiveSession _session;
2122
private readonly ILogger<AudioProcessor> _logger;
22-
23+
2324
// Audio configuration - PCM16, 24kHz, mono as specified
2425
private const int SampleRate = 24000;
2526
private const int Channels = 1;
2627
private const int BitsPerSample = 16;
27-
28+
2829
// NAudio components
2930
private WaveInEvent? _waveIn;
3031
private WaveOutEvent? _waveOut;
3132
private BufferedWaveProvider? _playbackBuffer;
32-
33+
3334
// Audio capture and playback state
3435
private bool _isCapturing;
3536
private bool _isPlaying;
36-
37+
3738
// Audio streaming channels
3839
private readonly Channel<byte[]> _audioSendChannel;
3940
private readonly Channel<byte[]> _audioPlaybackChannel;
4041
private readonly ChannelWriter<byte[]> _audioSendWriter;
4142
private readonly ChannelReader<byte[]> _audioSendReader;
4243
private readonly ChannelWriter<byte[]> _audioPlaybackWriter;
4344
private readonly ChannelReader<byte[]> _audioPlaybackReader;
44-
45+
4546
// Background tasks
4647
private Task? _audioSendTask;
4748
private Task? _audioPlaybackTask;
@@ -57,44 +58,45 @@ public AudioProcessor(VoiceLiveSession session, ILogger<AudioProcessor> logger)
5758
{
5859
_session = session ?? throw new ArgumentNullException(nameof(session));
5960
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
60-
61+
6162
// Create unbounded channels for audio data
6263
_audioSendChannel = Channel.CreateUnbounded<byte[]>();
6364
_audioSendWriter = _audioSendChannel.Writer;
6465
_audioSendReader = _audioSendChannel.Reader;
65-
66+
6667
_audioPlaybackChannel = Channel.CreateUnbounded<byte[]>();
6768
_audioPlaybackWriter = _audioPlaybackChannel.Writer;
6869
_audioPlaybackReader = _audioPlaybackChannel.Reader;
69-
70+
7071
_cancellationTokenSource = new CancellationTokenSource();
72+
_playbackCancellationTokenSource = new CancellationTokenSource();
7173

7274
_logger.LogInformation("AudioProcessor initialized with {SampleRate}Hz PCM16 mono audio", SampleRate);
7375
}
74-
76+
7577
/// <summary>
7678
/// Start capturing audio from microphone.
7779
/// </summary>
7880
public Task StartCaptureAsync()
7981
{
8082
if (_isCapturing)
8183
return Task.CompletedTask;
82-
84+
8385
_isCapturing = true;
84-
86+
8587
try
8688
{
8789
_waveIn = new WaveInEvent
8890
{
8991
WaveFormat = new WaveFormat(SampleRate, BitsPerSample, Channels),
9092
BufferMilliseconds = 50 // 50ms buffer for low latency
9193
};
92-
94+
9395
_waveIn.DataAvailable += OnAudioDataAvailable;
9496
_waveIn.RecordingStopped += OnRecordingStopped;
9597

9698
_logger.LogInformation($"There are {WaveIn.DeviceCount} devices available.");
97-
for(int i = 0; i < WaveIn.DeviceCount; i++)
99+
for (int i = 0; i < WaveIn.DeviceCount; i++)
98100
{
99101
var deviceInfo = WaveIn.GetCapabilities(i);
100102

@@ -103,10 +105,10 @@ public Task StartCaptureAsync()
103105
_waveIn.DeviceNumber = 0; // Default to first device
104106

105107
_waveIn.StartRecording();
106-
108+
107109
// Start audio send task
108110
_audioSendTask = ProcessAudioSendAsync(_cancellationTokenSource.Token);
109-
111+
110112
_logger.LogInformation("Started audio capture");
111113
return Task.CompletedTask;
112114
}
@@ -117,17 +119,17 @@ public Task StartCaptureAsync()
117119
throw;
118120
}
119121
}
120-
122+
121123
/// <summary>
122124
/// Stop capturing audio.
123125
/// </summary>
124126
public async Task StopCaptureAsync()
125127
{
126128
if (!_isCapturing)
127129
return;
128-
130+
129131
_isCapturing = false;
130-
132+
131133
if (_waveIn != null)
132134
{
133135
_waveIn.StopRecording();
@@ -136,49 +138,49 @@ public async Task StopCaptureAsync()
136138
_waveIn.Dispose();
137139
_waveIn = null;
138140
}
139-
141+
140142
// Complete the send channel and wait for the send task
141143
_audioSendWriter.TryComplete();
142144
if (_audioSendTask != null)
143145
{
144146
await _audioSendTask.ConfigureAwait(false);
145147
_audioSendTask = null;
146148
}
147-
149+
148150
_logger.LogInformation("Stopped audio capture");
149151
}
150-
152+
151153
/// <summary>
152154
/// Initialize audio playback system.
153155
/// </summary>
154156
public Task StartPlaybackAsync()
155157
{
156158
if (_isPlaying)
157159
return Task.CompletedTask;
158-
160+
159161
_isPlaying = true;
160-
162+
161163
try
162164
{
163165
_waveOut = new WaveOutEvent
164166
{
165167
DesiredLatency = 100 // 100ms latency
166168
};
167-
169+
168170
_playbackBuffer = new BufferedWaveProvider(new WaveFormat(SampleRate, BitsPerSample, Channels))
169171
{
170172
BufferDuration = TimeSpan.FromMinutes(5), // 5 second buffer
171173
DiscardOnBufferOverflow = true
172174
};
173-
175+
174176
_waveOut.Init(_playbackBuffer);
175177
_waveOut.Play();
176178

177179
_playbackCancellationTokenSource = new CancellationTokenSource();
178180

179181
// Start audio playback task
180182
_audioPlaybackTask = ProcessAudioPlaybackAsync();
181-
183+
182184
_logger.LogInformation("Audio playback system ready");
183185
return Task.CompletedTask;
184186
}
@@ -189,34 +191,35 @@ public Task StartPlaybackAsync()
189191
throw;
190192
}
191193
}
192-
194+
193195
/// <summary>
194196
/// Stop audio playback and clear buffer.
195197
/// </summary>
196198
public async Task StopPlaybackAsync()
197199
{
198200
if (!_isPlaying)
199201
return;
200-
202+
201203
_isPlaying = false;
202-
204+
203205
// Clear the playback channel
204-
while (_audioPlaybackReader.TryRead(out _)) { }
205-
206+
while (_audioPlaybackReader.TryRead(out _))
207+
{ }
208+
206209
if (_playbackBuffer != null)
207210
{
208211
_playbackBuffer.ClearBuffer();
209212
}
210-
213+
211214
if (_waveOut != null)
212215
{
213216
_waveOut.Stop();
214217
_waveOut.Dispose();
215218
_waveOut = null;
216219
}
217-
220+
218221
_playbackBuffer = null;
219-
222+
220223
// Complete the playback channel and wait for the playback task
221224
_playbackCancellationTokenSource.Cancel();
222225

@@ -225,10 +228,10 @@ public async Task StopPlaybackAsync()
225228
await _audioPlaybackTask.ConfigureAwait(false);
226229
_audioPlaybackTask = null;
227230
}
228-
231+
229232
_logger.LogInformation("Stopped audio playback");
230233
}
231-
234+
232235
/// <summary>
233236
/// Queue audio data for playback.
234237
/// </summary>
@@ -240,7 +243,7 @@ public async Task QueueAudioAsync(byte[] audioData)
240243
await _audioPlaybackWriter.WriteAsync(audioData).ConfigureAwait(false);
241244
}
242245
}
243-
246+
244247
/// <summary>
245248
/// Event handler for audio data available from microphone.
246249
/// </summary>
@@ -250,15 +253,15 @@ private void OnAudioDataAvailable(object? sender, WaveInEventArgs e)
250253
{
251254
byte[] audioData = new byte[e.BytesRecorded];
252255
Array.Copy(e.Buffer, 0, audioData, 0, e.BytesRecorded);
253-
256+
254257
// Queue audio data for sending (non-blocking)
255258
if (!_audioSendWriter.TryWrite(audioData))
256259
{
257260
_logger.LogWarning("Failed to queue audio data for sending - channel may be full");
258261
}
259262
}
260263
}
261-
264+
262265
/// <summary>
263266
/// Event handler for recording stopped.
264267
/// </summary>
@@ -269,19 +272,19 @@ private void OnRecordingStopped(object? sender, StoppedEventArgs e)
269272
_logger.LogError(e.Exception, "Audio recording stopped due to error");
270273
}
271274
}
272-
275+
273276
/// <summary>
274277
/// Background task to process audio data and send to VoiceLive service.
275278
/// </summary>
276279
private async Task ProcessAudioSendAsync(CancellationToken cancellationToken)
277280
{
278281
try
279282
{
280-
await foreach (byte[] audioData in _audioSendReader.ReadAllAsync(cancellationToken))
283+
await foreach (byte[] audioData in _audioSendReader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
281284
{
282285
if (cancellationToken.IsCancellationRequested)
283286
break;
284-
287+
285288
try
286289
{
287290
// Send audio data directly to the session
@@ -303,7 +306,7 @@ private async Task ProcessAudioSendAsync(CancellationToken cancellationToken)
303306
_logger.LogError(ex, "Error in audio send processing");
304307
}
305308
}
306-
309+
307310
/// <summary>
308311
/// Background task to process audio playback.
309312
/// </summary>
@@ -314,11 +317,11 @@ private async Task ProcessAudioPlaybackAsync()
314317
CancellationTokenSource combinedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_playbackCancellationTokenSource.Token, _cancellationTokenSource.Token);
315318
var cancellationToken = combinedTokenSource.Token;
316319

317-
await foreach (byte[] audioData in _audioPlaybackReader.ReadAllAsync(cancellationToken))
320+
await foreach (byte[] audioData in _audioPlaybackReader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
318321
{
319322
if (cancellationToken.IsCancellationRequested)
320323
break;
321-
324+
322325
try
323326
{
324327
if (_playbackBuffer != null && _isPlaying)
@@ -342,36 +345,38 @@ private async Task ProcessAudioPlaybackAsync()
342345
_logger.LogError(ex, "Error in audio playback processing");
343346
}
344347
}
345-
348+
346349
/// <summary>
347350
/// Clean up audio resources.
348351
/// </summary>
349352
public async Task CleanupAsync()
350353
{
351354
await StopCaptureAsync().ConfigureAwait(false);
352355
await StopPlaybackAsync().ConfigureAwait(false);
353-
356+
354357
_cancellationTokenSource.Cancel();
355-
358+
356359
// Wait for background tasks to complete
357360
var tasks = new List<Task>();
358-
if (_audioSendTask != null) tasks.Add(_audioSendTask);
359-
if (_audioPlaybackTask != null) tasks.Add(_audioPlaybackTask);
360-
361+
if (_audioSendTask != null)
362+
tasks.Add(_audioSendTask);
363+
if (_audioPlaybackTask != null)
364+
tasks.Add(_audioPlaybackTask);
365+
361366
if (tasks.Count > 0)
362367
{
363368
await Task.WhenAll(tasks).ConfigureAwait(false);
364369
}
365-
370+
366371
_logger.LogInformation("Audio processor cleaned up");
367372
}
368-
373+
369374
/// <summary>
370375
/// Dispose of resources.
371376
/// </summary>
372377
public void Dispose()
373378
{
374-
CleanupAsync().GetAwaiter().GetResult();
379+
CleanupAsync().Wait();
375380
_cancellationTokenSource.Dispose();
376381
}
377382
}

0 commit comments

Comments
 (0)