Stream audio file in a loop
(audio_file_path, stereo=False)
| 86 | |
| 87 | |
| 88 | def audio_file_streamer(audio_file_path, stereo=False): |
| 89 | """Stream audio file in a loop""" |
| 90 | from pydub import AudioSegment |
| 91 | |
| 92 | print(f"🎵 Loading audio file: {audio_file_path}") |
| 93 | |
| 94 | # Load audio file (supports MP3, WAV, etc.) |
| 95 | audio = AudioSegment.from_file(audio_file_path) |
| 96 | |
| 97 | # Convert to 48kHz with specified channel count |
| 98 | channels = 2 if stereo else 1 |
| 99 | audio = audio.set_frame_rate(48000).set_channels(channels) |
| 100 | |
| 101 | channel_mode = "stereo" if stereo else "mono" |
| 102 | print(f"✓ Audio loaded ({channel_mode}):") |
| 103 | print(f" - Duration: {len(audio) / 1000:.2f} seconds") |
| 104 | print(f" - Sample rate: {audio.frame_rate} Hz") |
| 105 | print(f" - Channels: {audio.channels}") |
| 106 | print(f" - Sample width: {audio.sample_width} bytes") |
| 107 | |
| 108 | # Get raw audio data as int16 samples |
| 109 | # For stereo, this array is interleaved: [L, R, L, R, L, R, ...] |
| 110 | audio_samples = np.array(audio.get_array_of_samples(), dtype=np.int16) |
| 111 | total_samples = len(audio_samples) |
| 112 | |
| 113 | # Track position in audio file (in terms of individual samples, not frames) |
| 114 | current_position = 0 |
| 115 | |
| 116 | def generate_audio(audio_frame): |
| 117 | nonlocal current_position |
| 118 | |
| 119 | # Number of samples per frame PER CHANNEL |
| 120 | num_samples_per_channel = audio_frame.samples |
| 121 | |
| 122 | # Total samples needed (for stereo: num_samples * 2, for mono: num_samples * 1) |
| 123 | num_samples_needed = num_samples_per_channel * channels |
| 124 | |
| 125 | # Extract samples for this frame |
| 126 | end_position = current_position + num_samples_needed |
| 127 | |
| 128 | # Handle looping |
| 129 | if end_position >= total_samples: |
| 130 | # Wrap around to beginning |
| 131 | samples_before_end = total_samples - current_position |
| 132 | samples_after_wrap = num_samples_needed - samples_before_end |
| 133 | |
| 134 | # Combine end + beginning |
| 135 | frame_data = np.concatenate([ |
| 136 | audio_samples[current_position:], |
| 137 | audio_samples[:samples_after_wrap] |
| 138 | ]) |
| 139 | |
| 140 | current_position = samples_after_wrap |
| 141 | else: |
| 142 | # Normal case: extract from current position |
| 143 | frame_data = audio_samples[current_position:end_position] |
| 144 | current_position = end_position |
| 145 |
no test coverage detected