From e83f248c676f1ed56e5c42d16da9fba395285fe3 Mon Sep 17 00:00:00 2001
From: cai <cai@nbcai.cc>
Date: Mon, 06 Jul 2026 03:54:46 +0800
Subject: [PATCH] fix: resample pcm stream chunks for livekit

---
 src/audio.rs |   57 ++++++++++++++++++++++++++++
 src/main.rs  |   40 +++----------------
 2 files changed, 63 insertions(+), 34 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);
+    }
 }
diff --git a/src/main.rs b/src/main.rs
index 02d1777..e05f602 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1465,39 +1465,13 @@
     sample_rate: u32,
     channels: u32,
 ) -> Result<Vec<PcmFrame>> {
-    if sample_rate != TARGET_SAMPLE_RATE_HZ || channels != u32::from(TARGET_NUM_CHANNELS) {
-        return Err(anyhow!(
-            "unsupported pcm_s16le stream format: sample_rate={}, channels={}",
-            sample_rate,
-            channels
-        ));
-    }
-    if payload.len() % 2 != 0 {
-        return Err(anyhow!("pcm_s16le payload has odd byte length"));
-    }
-    let samples: Vec<i16> = payload
-        .chunks_exact(2)
-        .map(|chunk| i16::from_le_bytes([chunk[0], chunk[1]]))
-        .collect();
-    if samples.is_empty() {
-        return Ok(Vec::new());
-    }
-    let samples_per_channel = (sample_rate / 50).max(1);
-    let frame_sample_count = samples_per_channel as usize * channels as usize;
-    let mut frames = Vec::new();
-    for chunk in samples.chunks(frame_sample_count) {
-        let chunk_samples_per_channel = (chunk.len() / channels as usize) as u32;
-        if chunk_samples_per_channel == 0 {
-            continue;
-        }
-        frames.push(PcmFrame::new(
-            chunk.to_vec(),
-            sample_rate,
-            channels,
-            chunk_samples_per_channel,
-        ));
-    }
-    Ok(frames)
+    audio::pcm_s16le_bytes_to_frames(
+        payload,
+        sample_rate,
+        channels,
+        TARGET_SAMPLE_RATE_HZ,
+        TARGET_NUM_CHANNELS,
+    )
 }
 
 fn trim_ascii_whitespace(value: &[u8]) -> &[u8] {

--
Gitblit v1.9.3