From 9c2d8f0dba462b347a706d0a539ccbe163e51b85 Mon Sep 17 00:00:00 2001
From: cai <cai@nbcai.cc>
Date: Mon, 06 Jul 2026 16:33:47 +0800
Subject: [PATCH] fix: tolerate split pcm samples in stream chunks

---
 src/audio.rs |   57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 56 insertions(+), 1 deletions(-)

diff --git a/src/audio.rs b/src/audio.rs
index 1aadcb4..6bdea19 100644
--- a/src/audio.rs
+++ b/src/audio.rs
@@ -142,6 +142,45 @@
     )
 }
 
+pub fn pcm_s16le_bytes_to_frames(
+    pcm_bytes: &[u8],
+    source_sample_rate_hz: u32,
+    source_num_channels: u32,
+    target_sample_rate_hz: u32,
+    target_num_channels: u16,
+) -> Result<Vec<PcmFrame>> {
+    if source_sample_rate_hz == 0 {
+        return Err(anyhow!("pcm_s16le source sample rate is zero"));
+    }
+    if source_num_channels == 0 {
+        return Err(anyhow!("pcm_s16le source channel count is zero"));
+    }
+    if pcm_bytes.len() % 2 != 0 {
+        return Err(anyhow!("pcm_s16le payload has odd byte length"));
+    }
+    let samples: Vec<i16> = pcm_bytes
+        .chunks_exact(2)
+        .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
+        .collect();
+    if samples.is_empty() {
+        return Ok(Vec::new());
+    }
+    let source_channels = u16::try_from(source_num_channels)
+        .map_err(|_| anyhow!("unsupported pcm_s16le channel count {source_num_channels}"))?;
+    let mut target_samples = remap_channels(samples, source_channels, target_num_channels);
+    target_samples = resample_linear(
+        target_samples,
+        source_sample_rate_hz,
+        target_sample_rate_hz,
+        target_num_channels,
+    );
+    Ok(chunk_pcm_samples(
+        target_samples,
+        target_sample_rate_hz,
+        target_num_channels,
+    ))
+}
+
 fn decode_audio_frames(
     audio_bytes: &[u8],
     source_kind: &'static str,
@@ -674,7 +713,7 @@
 
 #[cfg(test)]
 mod tests {
-    use super::load_pre_recorded_frames;
+    use super::{load_pre_recorded_frames, pcm_s16le_bytes_to_frames};
 
     #[tokio::test]
     async fn load_pre_recorded_frames_reads_local_wav() {
@@ -717,4 +756,20 @@
         assert!(loaded.diagnostics.debug_source_path.is_some());
         assert!(loaded.diagnostics.debug_pcm_wav_path.is_some());
     }
+
+    #[test]
+    fn pcm_s16le_bytes_to_frames_resamples_elevenlabs_pcm_to_livekit_frames() {
+        let mut pcm = Vec::new();
+        for index in 0..4410 {
+            let sample = if index % 2 == 0 { 1024_i16 } else { -1024_i16 };
+            pcm.extend_from_slice(&sample.to_le_bytes());
+        }
+
+        let frames = pcm_s16le_bytes_to_frames(&pcm, 44_100, 1, 48_000, 1).expect("pcm frames");
+
+        assert!(!frames.is_empty());
+        assert_eq!(frames[0].sample_rate, 48_000);
+        assert_eq!(frames[0].num_channels, 1);
+        assert_eq!(frames[0].samples_per_channel, 960);
+    }
 }

--
Gitblit v1.9.3