From f4882d7fd89d257721212698a873d1f5473ed18d Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 2 Nov 2025 23:26:31 +0100 Subject: [PATCH] Better handling of crossfade buffer frame aligment --- music_assistant/controllers/streams.py | 12 ++++++++++++ music_assistant/helpers/smart_fades.py | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/music_assistant/controllers/streams.py b/music_assistant/controllers/streams.py index c750a634..515146c6 100644 --- a/music_assistant/controllers/streams.py +++ b/music_assistant/controllers/streams.py @@ -997,7 +997,13 @@ class StreamsController(CoreController): if queue_track.streamdetails.duration else crossfade_buffer_duration, ) + # Ensure crossfade buffer size is aligned to frame boundaries + # Frame size = bytes_per_sample * channels + bytes_per_sample = pcm_format.bit_depth // 8 + frame_size = bytes_per_sample * pcm_format.channels crossfade_buffer_size = int(pcm_format.pcm_sample_size * crossfade_buffer_duration) + # Round down to nearest frame boundary + crossfade_buffer_size = (crossfade_buffer_size // frame_size) * frame_size bytes_written = 0 buffer = b"" @@ -1405,7 +1411,13 @@ class StreamsController(CoreController): if streamdetails.duration else crossfade_buffer_duration, ) + # Ensure crossfade buffer size is aligned to frame boundaries + # Frame size = bytes_per_sample * channels + bytes_per_sample = pcm_format.bit_depth // 8 + frame_size = bytes_per_sample * pcm_format.channels crossfade_buffer_size = int(pcm_format.pcm_sample_size * crossfade_buffer_duration) + # Round down to nearest frame boundary + crossfade_buffer_size = (crossfade_buffer_size // frame_size) * frame_size fade_out_data: bytes | None = None if crossfade_data: diff --git a/music_assistant/helpers/smart_fades.py b/music_assistant/helpers/smart_fades.py index 32b76dfa..075aa318 100644 --- a/music_assistant/helpers/smart_fades.py +++ b/music_assistant/helpers/smart_fades.py @@ -74,6 +74,19 @@ class SmartFadesAnalyzer: # Convert PCM bytes to numpy array and then to mono for analysis audio_array = np.frombuffer(audio_data, dtype=np.float32) if pcm_format.channels > 1: + # Ensure array size is divisible by channel count + samples_per_channel = len(audio_array) // pcm_format.channels + valid_samples = samples_per_channel * pcm_format.channels + if valid_samples != len(audio_array): + self.logger.warning( + "Audio buffer size (%d) not divisible by channels (%d), " + "truncating %d samples", + len(audio_array), + pcm_format.channels, + len(audio_array) - valid_samples, + ) + audio_array = audio_array[:valid_samples] + # Reshape to separate channels and take average for mono conversion audio_array = audio_array.reshape(-1, pcm_format.channels) mono_audio = np.asarray(np.mean(audio_array, axis=1, dtype=np.float32)) @@ -81,6 +94,14 @@ class SmartFadesAnalyzer: # Single channel - ensure consistent array type mono_audio = np.asarray(audio_array, dtype=np.float32) + # Validate that the audio is finite (no NaN or Inf values) + if not np.all(np.isfinite(mono_audio)): + self.logger.error( + "Audio buffer contains non-finite values (NaN/Inf) for %s, cannot analyze", + stream_details_name, + ) + return None + analysis = await self._analyze_track_beats(mono_audio, fragment, pcm_format.sample_rate) total_time = time.perf_counter() - start_time -- 2.34.1