add of changes
All checks were successful
/ mirror (push) Successful in 4s

This commit is contained in:
Bartosz 2025-07-07 12:00:29 +01:00
parent c4610fbcb9
commit 8b6ba00d8c
2 changed files with 67 additions and 74 deletions

View File

@ -25,7 +25,6 @@ class AudioPlayer(QObject):
self.audio = None
self.streams = {} # client_id -> stream
self.buffers = {} # client_id -> queue
self.threads = {} # client_id -> thread
self.recording_buffers = {} # client_id -> list of audio data
self.recording_enabled = {} # client_id -> bool
self.playback_enabled = {} # client_id -> bool
@ -65,30 +64,57 @@ class AudioPlayer(QObject):
return False
try:
# Create audio stream with larger buffer to prevent underruns
# Create buffer for this client
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)
# Create stream with callback
stream = self.audio.open(
format=pyaudio.paInt16,
channels=self.channels,
rate=self.sample_rate,
output=True,
frames_per_buffer=self.chunk_size * 2, # Doubled buffer size
stream_callback=None
frames_per_buffer=640, # 80ms buffer for smoother playback
stream_callback=audio_callback
)
self.streams[client_id] = stream
self.buffers[client_id] = queue.Queue()
self.playback_enabled[client_id] = True
stream.start_stream()
# Start playback thread
thread = threading.Thread(
target=self._playback_thread,
args=(client_id,),
daemon=True
)
self.threads[client_id] = thread
thread.start()
self.debug(f"Started playback for client {client_id}")
self.debug(f"Started callback-based playback for client {client_id}")
self.playback_started.emit(client_id)
return True
@ -103,12 +129,7 @@ class AudioPlayer(QObject):
self.playback_enabled[client_id] = False
# Wait for thread to finish
if client_id in self.threads:
self.threads[client_id].join(timeout=1.0)
del self.threads[client_id]
# Close stream
# Stop and close stream
if client_id in self.streams:
try:
self.streams[client_id].stop_stream()
@ -119,6 +140,12 @@ class AudioPlayer(QObject):
# Clear buffer
if client_id in self.buffers:
# Clear any remaining data
while not self.buffers[client_id].empty():
try:
self.buffers[client_id].get_nowait()
except:
break
del self.buffers[client_id]
self.debug(f"Stopped playback for client {client_id}")
@ -138,11 +165,23 @@ class AudioPlayer(QObject):
self.debug(f"Client {client_id} audio frame #{self._frame_count[client_id]}: {len(pcm_data)} bytes")
if client_id in self.buffers:
self.buffers[client_id].put(pcm_data)
if self._frame_count[client_id] == 1:
self.debug(f"Client {client_id} buffer started, queue size: {self.buffers[client_id].qsize()}")
try:
# Use put_nowait to avoid blocking
self.buffers[client_id].put_nowait(pcm_data)
if self._frame_count[client_id] == 1:
self.debug(f"Client {client_id} buffer started, queue size: {self.buffers[client_id].qsize()}")
except queue.Full:
# Buffer is full, drop oldest data to make room
try:
self.buffers[client_id].get_nowait() # Remove oldest
self.buffers[client_id].put_nowait(pcm_data) # Add newest
if self._frame_count[client_id] % 50 == 0: # Log occasionally
self.debug(f"Client {client_id} buffer overflow, dropping old data")
except:
pass
else:
self.debug(f"Client {client_id} has no buffer (playback not started?)")
if self._frame_count[client_id] == 1:
self.debug(f"Client {client_id} has no buffer (playback not started?)")
# Add to recording buffer if recording
if self.recording_enabled.get(client_id, False):
@ -150,53 +189,6 @@ class AudioPlayer(QObject):
self.recording_buffers[client_id] = []
self.recording_buffers[client_id].append(pcm_data)
def _playback_thread(self, client_id):
"""Thread function for audio playback"""
stream = self.streams.get(client_id)
buffer = self.buffers.get(client_id)
if not stream or not buffer:
return
self.debug(f"Playback thread started for client {client_id}")
# Buffer to accumulate data before playing
accumulated_data = b''
min_buffer_size = self.chunk_size * 2 # Buffer at least 2 chunks before playing
while self.playback_enabled.get(client_id, False):
try:
# Get audio data from buffer with timeout
audio_data = buffer.get(timeout=0.04) # 40ms timeout
accumulated_data += audio_data
# Only play when we have enough data to prevent underruns
if len(accumulated_data) >= min_buffer_size:
# Only log first frame to avoid spam
if not hasattr(self, '_playback_logged'):
self._playback_logged = {}
if client_id not in self._playback_logged:
self._playback_logged[client_id] = False
if not self._playback_logged[client_id]:
self.debug(f"Client {client_id} playback thread playing first frame: {len(accumulated_data)} bytes")
self._playback_logged[client_id] = True
# Play accumulated audio
stream.write(accumulated_data[:min_buffer_size])
accumulated_data = accumulated_data[min_buffer_size:]
except queue.Empty:
# If we have some accumulated data, play it to avoid gaps
if len(accumulated_data) >= self.chunk_size:
stream.write(accumulated_data[:self.chunk_size])
accumulated_data = accumulated_data[self.chunk_size:]
continue
except Exception as e:
self.debug(f"Playback error for client {client_id}: {e}")
break
self.debug(f"Playback thread ended for client {client_id}")
def start_recording(self, client_id):
"""Start recording received audio"""

View File

@ -11,7 +11,8 @@ from dissononce.dh.keypair import KeyPair
from dissononce.dh.x25519.public import PublicKey
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Add path to access voice_codec from Prototype directory
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'Prototype', 'Protocol_Alpha_0'))
from voice_codec import Codec2Wrapper, FSKModem, Codec2Mode
# ChaCha20 removed - using only Noise XK encryption