import io import wave import numpy as np import pytest try: from agents import UserError from agents.voice import AudioInput, StreamedAudioInput from agents.voice.input import DEFAULT_SAMPLE_RATE, _buffer_to_audio_file except ImportError: pass def test_buffer_to_audio_file_int16(): # Create a simple sine wave in int16 format t = np.linspace(0, 1, DEFAULT_SAMPLE_RATE) buffer = (np.sin(2 * np.pi * 440 * t) * 32767).astype(np.int16) filename, audio_file, content_type = _buffer_to_audio_file(buffer) assert filename == "audio.wav" assert content_type == "audio/wav" assert isinstance(audio_file, io.BytesIO) # Verify the WAV file contents with wave.open(audio_file, "rb") as wav_file: assert wav_file.getnchannels() == 1 assert wav_file.getsampwidth() == 2 assert wav_file.getframerate() == DEFAULT_SAMPLE_RATE assert wav_file.getnframes() == len(buffer) def test_buffer_to_audio_file_float32(): # Create a simple sine wave in float32 format t = np.linspace(0, 1, DEFAULT_SAMPLE_RATE) buffer = np.sin(2 * np.pi * 440 * t).astype(np.float32) filename, audio_file, content_type = _buffer_to_audio_file(buffer) assert filename == "audio.wav" assert content_type == "audio/wav" assert isinstance(audio_file, io.BytesIO) # Verify the WAV file contents with wave.open(audio_file, "rb") as wav_file: assert wav_file.getnchannels() == 1 assert wav_file.getsampwidth() == 2 assert wav_file.getframerate() == DEFAULT_SAMPLE_RATE assert wav_file.getnframes() == len(buffer) def test_buffer_to_audio_file_invalid_dtype(): # Create a buffer with invalid dtype (float64) buffer = np.array([1.0, 2.0, 3.0], dtype=np.float64) with pytest.raises(UserError, match="Buffer must be a numpy array of int16 or float32"): _buffer_to_audio_file(buffer=buffer) class TestAudioInput: def test_audio_input_default_params(self): # Create a simple sine wave t = np.linspace(0, 1, DEFAULT_SAMPLE_RATE) buffer = np.sin(2 * np.pi * 440 * t).astype(np.float32) audio_input = AudioInput(buffer=buffer) assert audio_input.frame_rate == DEFAULT_SAMPLE_RATE assert audio_input.sample_width == 2 assert audio_input.channels == 1 assert np.array_equal(audio_input.buffer, buffer) def test_audio_input_custom_params(self): # Create a simple sine wave t = np.linspace(0, 1, 48000) buffer = np.sin(2 * np.pi * 440 * t).astype(np.float32) audio_input = AudioInput(buffer=buffer, frame_rate=48000, sample_width=4, channels=2) assert audio_input.frame_rate == 48000 assert audio_input.sample_width == 4 assert audio_input.channels == 2 assert np.array_equal(audio_input.buffer, buffer) def test_audio_input_to_audio_file(self): # Create a simple sine wave t = np.linspace(0, 1, DEFAULT_SAMPLE_RATE) buffer = np.sin(2 * np.pi * 440 * t).astype(np.float32) audio_input = AudioInput(buffer=buffer) filename, audio_file, content_type = audio_input.to_audio_file() assert filename == "audio.wav" assert content_type == "audio/wav" assert isinstance(audio_file, io.BytesIO) # Verify the WAV file contents with wave.open(audio_file, "rb") as wav_file: assert wav_file.getnchannels() == 1 assert wav_file.getsampwidth() == 2 assert wav_file.getframerate() == DEFAULT_SAMPLE_RATE assert wav_file.getnframes() == len(buffer) def test_audio_input_to_base64_does_not_mutate_float32_buffer(self): # Regression: to_base64() previously rebound self.buffer to int16, # silently corrupting any caller-held reference to the original float32 array. buffer = np.sin(2 * np.pi * 440 * np.linspace(0, 1, 100)).astype(np.float32) original = buffer.copy() audio_input = AudioInput(buffer=buffer) audio_input.to_base64() assert audio_input.buffer.dtype == np.float32 assert np.array_equal(audio_input.buffer, original) # Calling it twice should still work and return the same encoding. assert audio_input.to_base64() == audio_input.to_base64() class TestStreamedAudioInput: @pytest.mark.asyncio async def test_streamed_audio_input(self): streamed_input = StreamedAudioInput() # Create some test audio data t = np.linspace(0, 1, DEFAULT_SAMPLE_RATE) audio1 = np.sin(2 * np.pi * 440 * t).astype(np.float32) audio2 = np.sin(2 * np.pi * 880 * t).astype(np.float32) # Add audio to the queue await streamed_input.add_audio(audio1) await streamed_input.add_audio(audio2) # Verify the queue contents assert streamed_input.queue.qsize() == 2 # Test non-blocking get retrieved_audio1 = streamed_input.queue.get_nowait() # Satisfy type checker assert retrieved_audio1 is not None assert np.array_equal(retrieved_audio1, audio1) # Test blocking get retrieved_audio2 = await streamed_input.queue.get() # Satisfy type checker assert retrieved_audio2 is not None assert np.array_equal(retrieved_audio2, audio2) assert streamed_input.queue.empty()