from __future__ import annotations import asyncio import sys import types import wave from pathlib import Path from unittest import mock from app.livekit.runtime.wav_audio import wav_audio_frames, wav_duration_ms class _AudioFrame: def __init__( self, *, data: bytes, sample_rate: int, num_channels: int, samples_per_channel: int, ) -> None: self.data = data self.sample_rate = sample_rate self.num_channels = num_channels self.samples_per_channel = samples_per_channel def _write_wav(path: Path, *, samples: int, sample_rate: int = 1000) -> None: with wave.open(str(path), "wb") as wav: wav.setnchannels(1) wav.setsampwidth(2) wav.setframerate(sample_rate) wav.writeframes(b"\x01\x02" * samples) def test_wav_audio_frames_pads_last_frame_and_adds_tail_silence(tmp_path: Path) -> None: wav_path = tmp_path / "audio.wav" _write_wav(wav_path, samples=25) livekit_module = types.ModuleType("livekit") rtc_module = types.ModuleType("livekit.rtc") rtc_module.AudioFrame = _AudioFrame livekit_module.rtc = rtc_module async def _collect(): with mock.patch.dict(sys.modules, {"livekit": livekit_module, "livekit.rtc": rtc_module}): return [ frame async for frame in wav_audio_frames( str(wav_path), frame_duration_ms=20, tail_silence_ms=40, ) ] frames = asyncio.run(_collect()) assert len(frames) == 4 assert [frame.samples_per_channel for frame in frames] == [20, 20, 20, 20] assert frames[1].data[:10] == b"\x01\x02" * 5 assert frames[1].data[10:] == b"\x00" * 30 assert frames[2].data == b"\x00" * 40 assert frames[3].data == b"\x00" * 40 assert wav_duration_ms(str(wav_path)) == 25