|
1 | 1 | using NUnit.Framework;
|
2 | 2 | using OpenAI.RealtimeConversation;
|
3 | 3 | using System;
|
| 4 | +using System.Buffers; |
4 | 5 | using System.ClientModel;
|
5 | 6 | using System.ClientModel.Primitives;
|
6 | 7 | using System.Collections.Generic;
|
7 | 8 | using System.IO;
|
8 | 9 | using System.Linq;
|
9 | 10 | using System.Numerics;
|
10 | 11 | using System.Text;
|
| 12 | +using System.Threading; |
11 | 13 | using System.Threading.Tasks;
|
12 | 14 |
|
13 | 15 | namespace OpenAI.Tests.Conversation;
|
@@ -239,7 +241,47 @@ await session.AddItemAsync(
|
239 | 241 | }
|
240 | 242 |
|
241 | 243 | [Test]
|
242 |
| - public async Task AudioWithToolsWorks() |
| 244 | + public async Task AudioStreamConvenienceBlocksCorrectly() |
| 245 | + { |
| 246 | + RealtimeConversationClient client = GetTestClient(); |
| 247 | + using RealtimeConversationSession session = await client.StartConversationSessionAsync(CancellationToken); |
| 248 | + |
| 249 | + string inputAudioFilePath = Path.Join("Assets", "realtime_whats_the_weather_pcm16_24khz_mono.wav"); |
| 250 | + using TestDelayedFileReadStream delayedStream = new(inputAudioFilePath, TimeSpan.FromMilliseconds(200), readsBeforeDelay: 2); |
| 251 | + _ = session.SendInputAudioAsync(delayedStream, CancellationToken); |
| 252 | + |
| 253 | + bool gotSpeechStarted = false; |
| 254 | + |
| 255 | + await foreach (ConversationUpdate update in session.ReceiveUpdatesAsync(CancellationToken)) |
| 256 | + { |
| 257 | + if (update is ConversationInputSpeechStartedUpdate) |
| 258 | + { |
| 259 | + gotSpeechStarted = true; |
| 260 | + Assert.ThrowsAsync<InvalidOperationException>( |
| 261 | + async () => |
| 262 | + { |
| 263 | + using MemoryStream dummyStream = new(); |
| 264 | + await session.SendInputAudioAsync(dummyStream, CancellationToken); |
| 265 | + }, |
| 266 | + "Sending a Stream while another Stream is being sent should throw!"); |
| 267 | + Assert.ThrowsAsync<InvalidOperationException>( |
| 268 | + async () => |
| 269 | + { |
| 270 | + BinaryData dummyData = BinaryData.FromString("hello, world! this isn't audio."); |
| 271 | + await session.SendInputAudioAsync(dummyData, CancellationToken); |
| 272 | + }, |
| 273 | + "Sending BinaryData while a Stream is being sent should throw!"); |
| 274 | + break; |
| 275 | + } |
| 276 | + } |
| 277 | + |
| 278 | + Assert.That(gotSpeechStarted, Is.True); |
| 279 | + } |
| 280 | + |
| 281 | + [Test] |
| 282 | + [TestCase(TestAudioSendType.WithAudioStreamHelper)] |
| 283 | + [TestCase(TestAudioSendType.WithManualAudioChunks)] |
| 284 | + public async Task AudioWithToolsWorks(TestAudioSendType audioSendType) |
243 | 285 | {
|
244 | 286 | RealtimeConversationClient client = GetTestClient();
|
245 | 287 | using RealtimeConversationSession session = await client.StartConversationSessionAsync(CancellationToken);
|
@@ -285,8 +327,27 @@ public async Task AudioWithToolsWorks()
|
285 | 327 |
|
286 | 328 | await session.ConfigureSessionAsync(options, CancellationToken);
|
287 | 329 |
|
288 |
| - using Stream audioStream = File.OpenRead(Path.Join("Assets", "realtime_whats_the_weather_pcm16_24khz_mono.wav")); |
289 |
| - _ = session.SendInputAudioAsync(audioStream, CancellationToken); |
| 330 | + _ = Task.Run(async () => |
| 331 | + { |
| 332 | + string inputAudioFilePath = Path.Join("Assets", "realtime_whats_the_weather_pcm16_24khz_mono.wav"); |
| 333 | + if (audioSendType == TestAudioSendType.WithAudioStreamHelper) |
| 334 | + { |
| 335 | + using Stream audioStream = File.OpenRead(inputAudioFilePath); |
| 336 | + await session.SendInputAudioAsync(audioStream, CancellationToken); |
| 337 | + } |
| 338 | + else if (audioSendType == TestAudioSendType.WithManualAudioChunks) |
| 339 | + { |
| 340 | + byte[] allAudioBytes = await File.ReadAllBytesAsync(inputAudioFilePath, CancellationToken); |
| 341 | + const int audioSendBufferLength = 8 * 1024; |
| 342 | + byte[] audioSendBuffer = ArrayPool<byte>.Shared.Rent(audioSendBufferLength); |
| 343 | + for (int readPos = 0; readPos < allAudioBytes.Length; readPos += audioSendBufferLength) |
| 344 | + { |
| 345 | + int nextSegmentLength = Math.Min(audioSendBufferLength, allAudioBytes.Length - readPos); |
| 346 | + ArraySegment<byte> nextSegment = new(allAudioBytes, readPos, nextSegmentLength); |
| 347 | + await session.SendInputAudioAsync(BinaryData.FromBytes(nextSegment), CancellationToken); |
| 348 | + } |
| 349 | + } |
| 350 | + }); |
290 | 351 |
|
291 | 352 | string userTranscript = null;
|
292 | 353 |
|
@@ -465,4 +526,46 @@ public async Task CanAddItems()
|
465 | 526 |
|
466 | 527 | Assert.That(itemCreatedCount, Is.EqualTo(items.Count + 1));
|
467 | 528 | }
|
| 529 | + |
| 530 | + public enum TestAudioSendType |
| 531 | + { |
| 532 | + WithAudioStreamHelper, |
| 533 | + WithManualAudioChunks |
| 534 | + } |
| 535 | + |
| 536 | + private class TestDelayedFileReadStream : FileStream |
| 537 | + { |
| 538 | + private readonly TimeSpan _delayBetweenReads; |
| 539 | + private readonly int _readsBeforeDelay; |
| 540 | + private int _readsPerformed; |
| 541 | + |
| 542 | + public TestDelayedFileReadStream( |
| 543 | + string path, |
| 544 | + TimeSpan delayBetweenReads, |
| 545 | + int readsBeforeDelay = 0) |
| 546 | + : base(path, FileMode.Open, FileAccess.Read) |
| 547 | + { |
| 548 | + _delayBetweenReads = delayBetweenReads; |
| 549 | + _readsBeforeDelay = readsBeforeDelay; |
| 550 | + _readsPerformed = 0; |
| 551 | + } |
| 552 | + |
| 553 | + public override int Read(byte[] buffer, int offset, int count) |
| 554 | + { |
| 555 | + if (++_readsPerformed > _readsBeforeDelay) |
| 556 | + { |
| 557 | + System.Threading.Thread.Sleep((int)_delayBetweenReads.TotalMilliseconds); |
| 558 | + } |
| 559 | + return base.Read(buffer, offset, count); |
| 560 | + } |
| 561 | + |
| 562 | + public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) |
| 563 | + { |
| 564 | + if (++_readsPerformed > _readsBeforeDelay) |
| 565 | + { |
| 566 | + await Task.Delay(_delayBetweenReads); |
| 567 | + } |
| 568 | + return await base.ReadAsync(buffer, offset, count, cancellationToken); |
| 569 | + } |
| 570 | + } |
468 | 571 | }
|
0 commit comments