add fix for samples
All checks were successful
/ mirror (push) Successful in 4s

This commit is contained in:
Bartosz 2025-07-07 14:05:07 +01:00
parent 8b6ba00d8c
commit d3d14919a8

View File

@ -32,6 +32,7 @@ class AudioPlayer(QObject):
self.channels = 1
self.chunk_size = 320 # 40ms at 8kHz
self.debug_callback = None
self.actual_sample_rate = 8000 # Will be updated if needed
if PYAUDIO_AVAILABLE:
try:
@ -68,58 +69,91 @@ class AudioPlayer(QObject):
self.buffers[client_id] = queue.Queue(maxsize=100) # Limit queue size
self.playback_enabled[client_id] = True
# Create audio stream with callback for continuous playback
def audio_callback(in_data, frame_count, time_info, status):
if status:
self.debug(f"Playback status for client {client_id}: {status}")
# Get audio data from buffer
audio_data = b''
bytes_needed = frame_count * 2 # 16-bit samples
# Try to get enough data for the requested frame count
while len(audio_data) < bytes_needed:
try:
chunk = self.buffers[client_id].get_nowait()
audio_data += chunk
except queue.Empty:
# No more data available, pad with silence
if len(audio_data) < bytes_needed:
silence = b'\x00' * (bytes_needed - len(audio_data))
audio_data += silence
break
# Trim to exact size if we got too much
if len(audio_data) > bytes_needed:
# Put extra back in queue
extra = audio_data[bytes_needed:]
try:
self.buffers[client_id].put_nowait(extra)
except queue.Full:
pass
audio_data = audio_data[:bytes_needed]
return (audio_data, pyaudio.paContinue)
# Try different sample rates if 8000 Hz fails
sample_rates = [8000, 16000, 44100, 48000]
stream = None
# Create stream with callback
stream = self.audio.open(
format=pyaudio.paInt16,
channels=self.channels,
rate=self.sample_rate,
output=True,
frames_per_buffer=640, # 80ms buffer for smoother playback
stream_callback=audio_callback
)
for rate in sample_rates:
try:
# Adjust buffer size based on sample rate
buffer_frames = int(640 * rate / 8000) # Scale buffer size
# Create audio stream with callback for continuous playback
def audio_callback(in_data, frame_count, time_info, status):
if status:
self.debug(f"Playback status for client {client_id}: {status}")
# Get audio data from buffer
audio_data = b''
bytes_needed = frame_count * 2 # 16-bit samples
# Try to get enough data for the requested frame count
while len(audio_data) < bytes_needed:
try:
chunk = self.buffers[client_id].get_nowait()
# Resample if needed
if self.actual_sample_rate != self.sample_rate:
chunk = self._resample_audio(chunk, self.sample_rate, self.actual_sample_rate)
audio_data += chunk
except queue.Empty:
# No more data available, pad with silence
if len(audio_data) < bytes_needed:
silence = b'\x00' * (bytes_needed - len(audio_data))
audio_data += silence
break
# Trim to exact size if we got too much
if len(audio_data) > bytes_needed:
# Put extra back in queue
extra = audio_data[bytes_needed:]
try:
self.buffers[client_id].put_nowait(extra)
except queue.Full:
pass
audio_data = audio_data[:bytes_needed]
return (audio_data, pyaudio.paContinue)
# Try to create stream with current sample rate
stream = self.audio.open(
format=pyaudio.paInt16,
channels=self.channels,
rate=rate,
output=True,
frames_per_buffer=buffer_frames,
stream_callback=audio_callback
)
self.actual_sample_rate = rate
if rate != self.sample_rate:
self.debug(f"Using sample rate {rate} Hz (resampling from {self.sample_rate} Hz)")
break # Success!
except Exception as e:
if rate == sample_rates[-1]: # Last attempt
raise e
else:
self.debug(f"Sample rate {rate} Hz failed, trying next...")
continue
if not stream:
raise Exception("Could not create audio stream with any sample rate")
self.streams[client_id] = stream
stream.start_stream()
self.debug(f"Started callback-based playback for client {client_id}")
self.debug(f"Started callback-based playback for client {client_id} at {self.actual_sample_rate} Hz")
self.playback_started.emit(client_id)
return True
except Exception as e:
self.debug(f"Failed to start playback for client {client_id}: {e}")
self.playback_enabled[client_id] = False
if client_id in self.buffers:
del self.buffers[client_id]
return False
def stop_playback(self, client_id):
@ -230,7 +264,7 @@ class AudioPlayer(QObject):
with wave.open(save_path, 'wb') as wav_file:
wav_file.setnchannels(self.channels)
wav_file.setsampwidth(2) # 16-bit
wav_file.setframerate(self.sample_rate)
wav_file.setframerate(self.sample_rate) # Always save at original 8kHz
wav_file.writeframes(combined_audio)
self.debug(f"Saved recording for client {client_id} to {save_path}")
@ -245,6 +279,40 @@ class AudioPlayer(QObject):
self.debug(f"Failed to save recording for client {client_id}: {e}")
return None
def _resample_audio(self, audio_data, from_rate, to_rate):
"""Simple linear resampling of audio data"""
if from_rate == to_rate:
return audio_data
import struct
# Convert bytes to samples
samples = struct.unpack(f'{len(audio_data)//2}h', audio_data)
# Calculate resampling ratio
ratio = to_rate / from_rate
new_length = int(len(samples) * ratio)
# Simple linear interpolation
resampled = []
for i in range(new_length):
# Find position in original samples
pos = i / ratio
idx = int(pos)
frac = pos - idx
if idx < len(samples) - 1:
# Linear interpolation between samples
sample = int(samples[idx] * (1 - frac) + samples[idx + 1] * frac)
else:
# Use last sample
sample = samples[-1] if samples else 0
resampled.append(sample)
# Convert back to bytes
return struct.pack(f'{len(resampled)}h', *resampled)
def cleanup(self):
"""Clean up audio resources"""
# Stop all playback