1use serde::{Deserialize, Serialize};
141use std::path::{Path, PathBuf};
142use thiserror::Error;
143
144
145use ffmpeg_sidecar::{command::FfmpegCommand};
146use hound::WavReader;
147use rubato::{
148 Async, FixedAsync, Resampler, SincInterpolationParameters, SincInterpolationType,
149 WindowFunction,
150};
151use audioadapter_buffers::direct::SequentialSliceOfVecs;
152
153#[derive(Debug, Error)]
154pub enum AudioError {
155 #[error("I/O error: {0}")]
156 Io(#[from] std::io::Error),
157
158 #[error("FFmpeg not available: {0}")]
160 FfmpegNotAvailable(String),
161 #[error("FFmpeg execution failed: {0}")]
162 FfmpegExecution(String),
163 #[error("FFmpeg configuration error: {0}")]
164 FfmpegConfig(String),
165
166 #[error("Format not supported: {format}, supported formats: {supported}")]
168 FormatNotSupported { format: String, supported: String },
169 #[error("Decode failed: {reason}")]
170 DecodeError { reason: String },
171 #[error("Encode failed: {reason}")]
172 EncodeError { reason: String },
173 #[error("Audio file corrupted or malformed: {0}")]
174 CorruptedFile(String),
175
176 #[error("Sample rate mismatch: expected {expected}, got {actual}")]
178 SampleRateMismatch { expected: u32, actual: u32 },
179 #[error("Channel count mismatch: expected {expected}, got {actual}")]
180 ChannelMismatch { expected: u16, actual: u16 },
181 #[error("Invalid sample rate: {rate}, must be between {min}-{max}")]
182 InvalidSampleRate { rate: u32, min: u32, max: u32 },
183 #[error("Invalid channel count: {channels}, must be between {min}-{max}")]
184 InvalidChannelCount { channels: u16, min: u16, max: u16 },
185 #[error("Invalid parameter: {0}")]
186 InvalidParameter(String),
187 #[error("Invalid buffer size: {size}, must be greater than {min}")]
188 InvalidBufferSize { size: usize, min: usize },
189
190 #[error("File not found: {0}")]
192 FileNotFound(String),
193 #[error("Path is not a file: {0}")]
194 NotAFile(String),
195 #[error("Permission denied: {0}")]
196 PermissionDenied(String),
197 #[error("Insufficient disk space: {0}")]
198 InsufficientSpace(String),
199
200 #[error("Resampling failed: {0}")]
202 ResampleError(String),
203 #[error("Audio processing failed: {0}")]
204 ProcessingError(String),
205 #[error("Out of memory: {0}")]
206 OutOfMemory(String),
207 #[error("Operation timeout: {0}")]
208 Timeout(String),
209
210 #[error("Unknown error: {0}")]
212 Other(String),
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
238pub enum AudioFormat {
239 Wav,
244
245 Mp3,
250
251 Flac,
256
257 M4a,
262
263 Ogg,
268}
269
270impl AudioFormat {
271 pub fn from_extension(ext: &str) -> Option<Self> {
273 match ext.to_lowercase().as_str() {
274 "wav" => Some(AudioFormat::Wav),
275 "mp3" => Some(AudioFormat::Mp3),
276 "flac" => Some(AudioFormat::Flac),
277 "m4a" => Some(AudioFormat::M4a),
278 "ogg" => Some(AudioFormat::Ogg),
279 _ => None,
280 }
281 }
282
283 pub fn extension(&self) -> &'static str {
285 match self {
286 AudioFormat::Wav => "wav",
287 AudioFormat::Mp3 => "mp3",
288 AudioFormat::Flac => "flac",
289 AudioFormat::M4a => "m4a",
290 AudioFormat::Ogg => "ogg",
291 }
292 }
293
294 pub fn is_whisper_native(&self) -> bool {
296 matches!(self, AudioFormat::Wav)
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
321pub struct AudioConfig {
322 pub sample_rate: u32,
331
332 pub channels: u16,
339
340 pub bit_depth: u16,
348}
349
350impl Default for AudioConfig {
351 fn default() -> Self {
352 Self {
353 sample_rate: 16000, channels: 1, bit_depth: 16, }
357 }
358}
359
360impl AudioConfig {
361 pub fn new(sample_rate: u32, channels: u16, bit_depth: u16) -> Self {
363 Self {
364 sample_rate,
365 channels,
366 bit_depth,
367 }
368 }
369
370 pub fn whisper_optimized() -> Self {
372 Self::default()
373 }
374
375 pub fn is_whisper_compatible(&self) -> bool {
377 self.sample_rate == 16000 && self.channels == 1
378 }
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct AudioMeta {
383 pub sample_rate: u32,
385 pub channels: u16,
387 pub duration_ms: Option<u64>,
389 pub format: Option<String>,
391}
392
393#[derive(Debug, Clone)]
394pub struct CompatibleWav {
395 pub path: std::path::PathBuf,
397}
398
399#[derive(Debug, Clone)]
400pub struct Resampled {
401 pub samples: Vec<f32>,
403 pub sample_rate: u32,
405}
406
407pub fn probe<P: AsRef<std::path::Path>>(input: P) -> Result<AudioMeta, AudioError> {
452 let path = input.as_ref();
453 if !path.exists() {
454 return Err(AudioError::FileNotFound(format!("{}", path.display())));
455 }
456 if path.is_dir() {
457 return Err(AudioError::NotAFile(format!("{}", path.display())));
458 }
459
460 let ext = path
462 .extension()
463 .and_then(|e| e.to_str())
464 .unwrap_or("")
465 .to_lowercase();
466 if ext == "wav" {
467 let reader = WavReader::open(path).map_err(|e| AudioError::DecodeError {
468 reason: format!("Failed to open WAV: {e}"),
469 })?;
470 let spec = reader.spec();
471 let total_samples = reader.duration();
473 let frames = if spec.channels > 0 {
474 total_samples as u64 / spec.channels as u64
475 } else {
476 0
477 };
478 let duration_ms = if spec.sample_rate > 0 {
479 Some(frames * 1000 / spec.sample_rate as u64)
480 } else {
481 None
482 };
483 return Ok(AudioMeta {
484 sample_rate: spec.sample_rate,
485 channels: spec.channels,
486 duration_ms,
487 format: Some("wav".into()),
488 });
489 } else if !ext.is_empty() {
490 return Err(AudioError::FormatNotSupported {
491 format: ext,
492 supported: "wav".to_string(),
493 });
494 }
495
496 Err(AudioError::FormatNotSupported {
498 format: "unknown".to_string(),
499 supported: "wav, mp3, flac, m4a".to_string(),
500 })
501}
502
503
504pub fn ensure_whisper_compatible<P: AsRef<Path>>(
568 input: P,
569 output: Option<PathBuf>,
570) -> Result<CompatibleWav, AudioError> {
571 let in_path = input.as_ref();
572
573 if !in_path.exists() {
575 return Err(AudioError::FileNotFound(format!("{}", in_path.display())));
576 }
577 if in_path.is_dir() {
578 return Err(AudioError::NotAFile(format!("{}", in_path.display())));
579 }
580
581 let out_path = if let Some(p) = output {
583 p
584 } else {
585 let mut temp = std::env::temp_dir();
586 let file_stem = in_path
587 .file_stem()
588 .and_then(|s| s.to_str())
589 .unwrap_or("audio");
590 temp.push(format!("{file_stem}_mono16k.wav"));
591 temp
592 };
593
594 let filter = "aformat=sample_fmts=s16:channel_layouts=mono:sample_rates=16000";
596
597 let status = FfmpegCommand::new()
598 .input(in_path.to_string_lossy())
599 .args(["-filter:a", filter])
600 .overwrite()
601 .output(out_path.to_string_lossy())
602 .spawn()?
603 .wait()?;
604
605 if !status.success() {
606 return Err(AudioError::FfmpegExecution(
607 "FFmpeg conversion failed".to_string(),
608 ));
609 }
610
611 let reader = WavReader::open(&out_path).map_err(|e| AudioError::DecodeError {
613 reason: format!("Failed to verify output WAV: {e}"),
614 })?;
615 let spec = reader.spec();
616
617 if spec.sample_rate != 16000 {
618 return Err(AudioError::SampleRateMismatch {
619 expected: 16000,
620 actual: spec.sample_rate,
621 });
622 }
623
624 if spec.channels != 1 {
625 return Err(AudioError::ChannelMismatch {
626 expected: 1,
627 actual: spec.channels,
628 });
629 }
630
631 if spec.bits_per_sample != 16 {
632 return Err(AudioError::FormatNotSupported {
633 format: format!("{} bit PCM", spec.bits_per_sample),
634 supported: "16 bit PCM".to_string(),
635 });
636 }
637
638 Ok(CompatibleWav { path: out_path })
639}
640
641
642pub fn resample(samples: &[f32], from_rate: u32, to_rate: u32) -> Result<Resampled, AudioError> {
643 if from_rate == 0 {
644 return Err(AudioError::InvalidSampleRate {
645 rate: from_rate,
646 min: 1,
647 max: 192000,
648 });
649 }
650 if to_rate == 0 {
651 return Err(AudioError::InvalidSampleRate {
652 rate: to_rate,
653 min: 1,
654 max: 192000,
655 });
656 }
657 if samples.is_empty() || from_rate == to_rate {
658 return Ok(Resampled {
659 samples: samples.to_vec(),
660 sample_rate: to_rate,
661 });
662 }
663
664 let ratio = to_rate as f64 / from_rate as f64;
666
667 let params = SincInterpolationParameters {
669 sinc_len: 256,
670 f_cutoff: 0.95,
671 interpolation: SincInterpolationType::Linear,
672 oversampling_factor: 256,
673 window: WindowFunction::BlackmanHarris2,
674 };
675
676 let mut resampler = Async::<f32>::new_sinc(
678 ratio,
679 2.0, ¶ms,
681 samples.len(),
682 1, FixedAsync::Input,
684 )
685 .map_err(|e| AudioError::ResampleError(format!("Failed to create resampler: {e}")))?;
686
687 let input_data = vec![samples.to_vec()];
689 let input = SequentialSliceOfVecs::new(&input_data, 1, samples.len())
690 .map_err(|e| AudioError::ProcessingError(format!("Failed to prepare input data: {e}")))?;
691
692 let output_data = resampler
694 .process(&input, 0, None)
695 .map_err(|e| AudioError::ProcessingError(format!("Resampling failed: {e}")))?;
696
697 let output_samples = output_data.take_data();
699
700 Ok(Resampled {
701 samples: output_samples,
702 sample_rate: to_rate,
703 })
704}
705
706pub struct StreamingResampler {
709 resampler: Option<Async<f32>>,
711 from_rate: u32,
713 to_rate: u32,
715 buffer: Vec<f32>,
717 chunk_size: usize,
719}
720
721impl StreamingResampler {
722 pub fn new(from_rate: u32, to_rate: u32) -> Result<Self, AudioError> {
724 if from_rate == 0 {
725 return Err(AudioError::InvalidSampleRate {
726 rate: from_rate,
727 min: 1,
728 max: 192000,
729 });
730 }
731 if to_rate == 0 {
732 return Err(AudioError::InvalidSampleRate {
733 rate: to_rate,
734 min: 1,
735 max: 192000,
736 });
737 }
738
739 let chunk_size = 1024;
740
741 if from_rate == to_rate {
742 return Ok(Self {
744 resampler: None,
745 from_rate,
746 to_rate,
747 buffer: Vec::new(),
748 chunk_size,
749 });
750 }
751
752 let ratio = to_rate as f64 / from_rate as f64;
753
754 let params = SincInterpolationParameters {
756 sinc_len: 256,
757 f_cutoff: 0.95,
758 interpolation: SincInterpolationType::Linear,
759 oversampling_factor: 256,
760 window: WindowFunction::BlackmanHarris2,
761 };
762
763 let resampler = Async::<f32>::new_sinc(
765 ratio, 2.0, ¶ms, chunk_size, 1, FixedAsync::Input,
769 )
770 .map_err(|e| AudioError::ResampleError(format!("Failed to create resampler: {e}")))?;
771
772 Ok(Self {
773 resampler: Some(resampler),
774 from_rate,
775 to_rate,
776 buffer: Vec::new(),
777 chunk_size,
778 })
779 }
780
781 pub fn process_chunk(&mut self, input: &[f32]) -> Result<Vec<f32>, AudioError> {
783 if input.is_empty() {
784 return Ok(Vec::new());
785 }
786
787 if self.from_rate == self.to_rate {
788 return Ok(input.to_vec());
789 }
790
791 let resampler = self
792 .resampler
793 .as_mut()
794 .ok_or_else(|| AudioError::ProcessingError("Resampler not initialized".into()))?;
795
796 self.buffer.extend_from_slice(input);
798
799 let mut output = Vec::new();
800
801 while self.buffer.len() >= self.chunk_size {
803 let chunk: Vec<f32> = self.buffer.drain(0..self.chunk_size).collect();
805
806 let input_data = vec![chunk];
808 let input = SequentialSliceOfVecs::new(&input_data, 1, self.chunk_size)
809 .map_err(|e| AudioError::ProcessingError(format!("Failed to prepare input data: {e}")))?;
810
811 let output_data = resampler
813 .process(&input, 0, None)
814 .map_err(|e| AudioError::ProcessingError(format!("Resampling failed: {e}")))?;
815
816 output.extend(output_data.take_data());
818 }
819
820 Ok(output)
821 }
822
823 pub fn finalize(&mut self) -> Result<Vec<f32>, AudioError> {
825 if self.from_rate == self.to_rate {
826 let remaining = self.buffer.clone();
828 self.buffer.clear();
829 return Ok(remaining);
830 }
831
832 if let Some(resampler) = self.resampler.as_mut() {
833 let mut output = Vec::new();
834
835 if !self.buffer.is_empty() {
837 let mut padded_buffer = self.buffer.clone();
839 padded_buffer.resize(self.chunk_size, 0.0);
840
841 let input_data = vec![padded_buffer];
842 let input = SequentialSliceOfVecs::new(&input_data, 1, self.chunk_size)
843 .map_err(|e| AudioError::ProcessingError(format!("Failed to prepare input data: {e}")))?;
844
845 let output_data = resampler
846 .process(&input, 0, None)
847 .map_err(|e| AudioError::ProcessingError(format!("Failed to process remaining samples: {e}")))?;
848
849 output.extend(output_data.take_data());
850
851 self.buffer.clear();
852 }
853
854 Ok(output)
855 } else {
856 Ok(Vec::new())
857 }
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use hound::WavReader;
865
866 #[test]
867 fn test_probe_stub() {
868 let err = probe("/tmp/nonexist.wav").expect_err("应返回错误");
869 match err {
870 AudioError::FileNotFound(_) => {}
871 _ => panic!("应为 FileNotFound 错误"),
872 }
873 }
874
875 #[test]
876 fn test_resample_ratio() {
877 let input: Vec<f32> = (0..160).map(|i| (i as f32).sin()).collect();
878 let out = resample(&input, 16000, 8000).unwrap();
879 assert_eq!(out.sample_rate, 8000);
880 assert!(!out.samples.is_empty(), "Resampled output should not be empty");
882 let ratio = 8000.0 / 16000.0; let expected_min = (input.len() as f64 * ratio * 0.1) as usize; let expected_max = (input.len() as f64 * ratio * 2.0) as usize;
886 assert!(out.samples.len() >= expected_min && out.samples.len() <= expected_max,
887 "Output length {} not in expected range [{}, {}]", out.samples.len(), expected_min, expected_max);
888 }
889
890 #[test]
891 fn test_resample_quality() {
892 let sample_rate = 16000;
894 let freq = 440.0; let duration = 1.0; let num_samples = (sample_rate as f64 * duration) as usize;
897 let input: Vec<f32> = (0..num_samples)
898 .map(|i| (2.0 * std::f32::consts::PI * i as f32 * freq / sample_rate as f32).sin())
899 .collect();
900
901 let out = resample(&input, sample_rate as u32, 8000).unwrap();
903 assert_eq!(out.sample_rate, 8000);
904
905 let mut zero_crossings = 0;
907 for i in 1..out.samples.len() {
908 if out.samples[i - 1] * out.samples[i] <= 0.0 {
909 zero_crossings += 1;
910 }
911 }
912
913 log::debug!("Zero crossings: {zero_crossings}, expected around 440");
916 assert!((zero_crossings as f64 - 440.0).abs() < 500.0);
917 }
918
919 #[test]
920 fn test_ensure_whisper_compatible_on_fixture() {
921 let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
923 let root_dir = crate_dir.parent().expect("audio crate has parent");
924 let input = root_dir.join("fixtures/audio/jfk.wav");
925 if !input.exists() {
926 log::warn!("Skipping: missing test audio {}", input.display());
927 return;
928 }
929
930 let out = ensure_whisper_compatible(&input, None).expect("Conversion should succeed");
931 assert!(out.path.exists(), "Output file should exist");
932
933 let reader = WavReader::open(&out.path).expect("Should be able to open output WAV");
935 let spec = reader.spec();
936 assert_eq!(spec.sample_rate, 16000);
937 assert_eq!(spec.channels, 1);
938 assert_eq!(spec.bits_per_sample, 16);
939
940 let _ = std::fs::remove_file(&out.path);
942 }
943
944 #[test]
945 fn test_probe_wav_on_fixture() {
946 let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
947 let root_dir = crate_dir.parent().expect("audio crate has parent");
948 let input = root_dir.join("fixtures/audio/jfk.wav");
949 if !input.exists() {
950 log::warn!("Skipping: missing test audio {}", input.display());
951 return;
952 }
953 let meta = probe(&input).expect("应能探测 WAV 元数据");
954 assert_eq!(meta.format.as_deref(), Some("wav"));
955 assert_eq!(meta.channels, 1);
956 assert!(meta.sample_rate > 0);
957 assert!(meta.duration_ms.unwrap_or(0) > 0);
958 }
959
960 #[test]
961 fn test_ensure_whisper_compatible_errors() {
962 let missing = std::path::PathBuf::from("/tmp/__definitely_missing_audio__.wav");
964 let err = ensure_whisper_compatible(&missing, None).expect_err("Should return error");
965
966 match err {
968 AudioError::FileNotFound(_) | AudioError::FfmpegNotAvailable(_) => {}
969 _ => panic!("Should be FileNotFound or FfmpegNotAvailable error"),
970 }
971
972 let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
974 let err2 = ensure_whisper_compatible(&crate_dir, None).expect_err("Should return error");
975
976 match err2 {
978 AudioError::NotAFile(_) | AudioError::FfmpegNotAvailable(_) => {}
979 _ => panic!("Should be NotAFile or FfmpegNotAvailable error"),
980 }
981 }
982
983 #[test]
984 fn test_resample_invalid_rate() {
985 let input: Vec<f32> = vec![0.0, 1.0, 0.0];
986 let err = resample(&input, 0, 16000).expect_err("应返回错误");
988 match err {
989 AudioError::InvalidSampleRate { .. } => {}
990 _ => panic!("应为 InvalidSampleRate 错误"),
991 }
992 let err2 = resample(&input, 16000, 0).expect_err("应返回错误");
994 match err2 {
995 AudioError::InvalidSampleRate { .. } => {}
996 _ => panic!("应为 InvalidSampleRate 错误"),
997 }
998 }
999
1000 #[test]
1001 fn test_streaming_resampler_upsample_matches_batch() {
1002 let from = 16000u32;
1004 let to = 32000u32;
1005 let input: Vec<f32> = (0..1000).map(|i| i as f32 / 1000.0).collect();
1006
1007 let batch = resample(&input, from, to).unwrap().samples;
1009
1010 let mut sr = StreamingResampler::new(from, to).unwrap();
1012 let mut stream_out = Vec::new();
1013 for chunk in input.chunks(123) {
1014 let y = sr.process_chunk(chunk).unwrap();
1015 stream_out.extend(y);
1016 }
1017 stream_out.extend(sr.finalize().unwrap());
1018
1019 let diff = (batch.len() as isize - stream_out.len() as isize).abs();
1022 log::debug!(
1023 "Length difference: {}, batch: {}, stream: {}",
1024 diff,
1025 batch.len(),
1026 stream_out.len()
1027 );
1028 assert!(diff <= 2500);
1029
1030 let n = batch.len().min(stream_out.len());
1032 let mut mse = 0.0f64;
1033 for i in 0..n {
1034 let d = batch[i] - stream_out[i];
1035 mse += (d as f64).powi(2);
1036 }
1037 mse /= n.max(1) as f64;
1038 assert!(mse < 1e-6, "MSE too large: {mse}");
1039 }
1040
1041 #[test]
1042 fn test_streaming_resampler_downsample_length() {
1043 let from = 16000u32;
1044 let to = 8000u32;
1045 let input: Vec<f32> = (0..4000).map(|i| ((i as f32) * 0.01).sin()).collect();
1046
1047 let batch = resample(&input, from, to).unwrap().samples;
1048
1049 let mut sr = StreamingResampler::new(from, to).unwrap();
1050 let mut stream_out = Vec::new();
1051 for chunk in input.chunks(777) {
1052 stream_out.extend(sr.process_chunk(chunk));
1053 }
1054 stream_out.extend(sr.finalize());
1055
1056 let diff = (batch.len() as isize - stream_out.len() as isize).abs();
1058 log::debug!(
1059 "Length difference: {}, batch: {}, stream: {}",
1060 diff,
1061 batch.len(),
1062 stream_out.len()
1063 );
1064 assert!(diff <= 2000);
1065 }
1066
1067 #[test]
1068 fn test_streaming_finalize_handles_partial_chunk() {
1069 let from = 16000u32;
1071 let to = 24000u32;
1072 let input: Vec<f32> = (0..2500).map(|i| i as f32 / 2500.0).collect();
1073
1074 let batch = resample(&input, from, to).unwrap().samples;
1075
1076 let mut sr = StreamingResampler::new(from, to).unwrap();
1077 let mut stream_out = Vec::new();
1078 for chunk in input.chunks(500) {
1079 stream_out.extend(sr.process_chunk(chunk).unwrap());
1080 }
1081 let tail = sr.finalize().unwrap();
1082 stream_out.extend(tail);
1083
1084 let diff = (batch.len() as isize - stream_out.len() as isize).abs();
1086 assert!(
1087 diff <= 2500,
1088 "Length difference too large: batch={}, stream={}, diff={}",
1089 batch.len(),
1090 stream_out.len(),
1091 diff
1092 );
1093 }
1094
1095 #[test]
1096 fn test_streaming_finalize_exact_chunk_boundary() {
1097 let from = 16000u32;
1099 let to = 22050u32;
1100 let input: Vec<f32> = (0..3072).map(|i| (i as f32 * 0.01).sin()).collect();
1102
1103 let batch = resample(&input, from, to).unwrap().samples;
1104
1105 let mut sr = StreamingResampler::new(from, to).unwrap();
1106 let mut stream_out = Vec::new();
1107 for chunk in input.chunks(1024) {
1108 stream_out.extend(sr.process_chunk(chunk).unwrap());
1109 }
1110 let tail = sr.finalize().unwrap();
1111 stream_out.extend(tail);
1112
1113 let diff = (batch.len() as isize - stream_out.len() as isize).abs();
1115 assert!(
1116 diff <= 100,
1117 "Exact chunk boundary length diff should be small: batch={}, stream={}, diff={}",
1118 batch.len(),
1119 stream_out.len(),
1120 diff
1121 );
1122 }
1123
1124 #[test]
1125 fn test_streaming_same_rate_passthrough() {
1126 let input: Vec<f32> = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1128 let mut sr = StreamingResampler::new(16000, 16000).unwrap();
1129
1130 let output = sr.process_chunk(&input).unwrap();
1131 assert_eq!(output, input);
1132
1133 let tail = sr.finalize().unwrap();
1134 assert!(tail.is_empty(), "相同采样率 finalize 不应有剩余数据");
1135 }
1136
1137 #[test]
1138 fn test_streaming_empty_input() {
1139 let mut sr = StreamingResampler::new(16000, 24000).unwrap();
1140
1141 let output = sr.process_chunk(&[]).unwrap();
1142 assert!(output.is_empty(), "空输入应返回空输出");
1143 }
1144
1145 #[test]
1146 fn test_extreme_sample_rates() {
1147 let input: Vec<f32> = vec![0.0, 1.0, 0.0, -1.0];
1149
1150 let result = resample(&input, 192000, 16000);
1152 assert!(result.is_ok(), "192kHz 到 16kHz 重采样应该成功");
1153
1154 let result = resample(&input, 200000, 16000);
1156 assert!(result.is_ok(), "200kHz 到 16kHz 重采样应该成功(虽然超过文档上限但实际可能工作)");
1157
1158 let result = resample(&input, 8000, 16000);
1160 assert!(result.is_ok(), "8kHz 到 16kHz 重采样应该成功");
1161
1162 let result = resample(&input, 16000, 16000);
1164 assert!(result.is_ok(), "16kHz 到 16kHz 重采样应该成功");
1165 assert_eq!(result.unwrap().samples, input, "相同采样率应该返回原始样本");
1166 }
1167
1168 #[test]
1169 fn test_basic_resampling_functionality() {
1170 let input: Vec<f32> = (0..1000).map(|i| (i as f32 * 0.01).sin()).collect();
1172
1173 let result = resample(&input, 16000, 8000);
1175 assert!(result.is_ok(), "降采样应该成功");
1176 let downsampled = result.unwrap();
1177 assert!(!downsampled.samples.is_empty(), "降采样应该产生非空输出");
1178 assert_eq!(downsampled.sample_rate, 8000, "输出采样率应该正确");
1179
1180 let result = resample(&input, 8000, 16000);
1182 assert!(result.is_ok(), "升采样应该成功");
1183 let upsampled = result.unwrap();
1184 assert!(!upsampled.samples.is_empty(), "升采样应该产生非空输出");
1185 assert_eq!(upsampled.sample_rate, 16000, "输出采样率应该正确");
1186
1187 let result = resample(&input, 16000, 16000);
1189 assert!(result.is_ok(), "相同采样率重采样应该成功");
1190 let same_rate = result.unwrap();
1191 assert_eq!(same_rate.samples, input, "相同采样率应该返回原始样本");
1192 assert_eq!(same_rate.sample_rate, 16000, "输出采样率应该正确");
1193
1194 log::info!("Basic resampling test passed - downsample: {} -> {} samples, upsample: {} -> {} samples",
1195 input.len(), downsampled.samples.len(), input.len(), upsampled.samples.len());
1196 }
1197}