# # Copyright (c) 2024-2026, Daily # # SPDX-License-Identifier: BSD 2-Clause License # import asyncio import struct import unittest from pipecat.clocks.system_clock import SystemClock from pipecat.frames.frames import ( BotStartedSpeakingFrame, BotStoppedSpeakingFrame, InputAudioRawFrame, OutputAudioRawFrame, StartFrame, UserStartedSpeakingFrame, UserStoppedSpeakingFrame, ) from pipecat.processors.audio.audio_buffer_processor import AudioBufferProcessor from pipecat.processors.frame_processor import FrameDirection, FrameProcessorSetup from pipecat.utils.asyncio.task_manager import TaskManager, TaskManagerParams class _PassthroughResampler: async def resample( self, audio: bytes, in_rate: int, out_rate: int ) -> bytes: # pragma: no cover - trivial return audio async def _make_processor(*, buffer_size: int = 0) -> AudioBufferProcessor: """Create and start a processor ready to record. Calls setup() and sends a StartFrame through the public process_frame path so that the processor is fully initialised (task manager set, sample rate configured, __started flag set) without needing a full pipeline. """ processor = AudioBufferProcessor(sample_rate=16000, num_channels=2, buffer_size=buffer_size) processor._input_resampler = _PassthroughResampler() processor._output_resampler = _PassthroughResampler() loop = asyncio.get_event_loop() task_manager = TaskManager() task_manager.setup(TaskManagerParams(loop=loop)) await processor.setup(FrameProcessorSetup(clock=SystemClock(), task_manager=task_manager)) await processor.process_frame( StartFrame(audio_out_sample_rate=16000), FrameDirection.DOWNSTREAM ) await processor.start_recording() return processor async def _capture_track_audio(processor: AudioBufferProcessor) -> tuple[bytes, bytes]: """Flush the processor and return (user_track, bot_track) from on_track_audio_data.""" captured = {} event = asyncio.Event() async def on_track_audio_data(_, user, bot, sample_rate, num_channels): captured["user"] = user captured["bot"] = bot event.set() processor.add_event_handler("on_track_audio_data", on_track_audio_data) await processor.stop_recording() await asyncio.wait_for(event.wait(), timeout=1) return captured["user"], captured["bot"] class TestAudioBufferProcessor(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): self.processor = await _make_processor(buffer_size=4) async def asyncTearDown(self): if getattr(self.processor, "_recording", False): await self.processor.stop_recording() await self.processor.cleanup() async def test_flush_user_audio_pads_bot_track(self): user_audio = struct.pack("