Skip to main content

codec_core/utils/
validation.rs

1//! Input validation utilities for codec operations
2
3use crate::error::{CodecError, Result};
4use crate::types::{CodecType, SampleRate};
5
6/// Validate audio samples for codec processing
7pub fn validate_samples(samples: &[i16]) -> Result<()> {
8    if samples.is_empty() {
9        return Err(CodecError::invalid_format("Input samples cannot be empty"));
10    }
11    // Per-sample range check removed: every `i16` is by definition
12    // within [-32768, 32767], and `i16::MIN.abs()` would overflow.
13    Ok(())
14}
15
16/// Validate encoded data for codec processing
17pub fn validate_encoded_data(data: &[u8]) -> Result<()> {
18    if data.is_empty() {
19        return Err(CodecError::invalid_format("Encoded data cannot be empty"));
20    }
21
22    // Check for reasonable data size (not too large)
23    if data.len() > 1024 * 1024 {
24        return Err(CodecError::invalid_format(format!(
25            "Encoded data too large: {} bytes",
26            data.len()
27        )));
28    }
29
30    Ok(())
31}
32
33/// Validate frame size for a specific codec
34pub fn validate_frame_size(codec_type: CodecType, frame_size: usize) -> Result<()> {
35    let expected_sizes = match codec_type {
36        CodecType::G711Pcmu | CodecType::G711Pcma => {
37            // G.711 commonly uses 10ms or 20ms frames at 8kHz
38            vec![80, 160, 240, 320]
39        }
40
41        CodecType::G729 | CodecType::G729A | CodecType::G729BA => {
42            // G.729/G.729A/G.729BA use fixed 10ms frames at 8kHz
43            vec![80]
44        }
45        CodecType::Opus => {
46            // Opus supports various frame sizes
47            vec![120, 240, 480, 960, 1920, 2880]
48        }
49    };
50
51    if !expected_sizes.contains(&frame_size) {
52        return Err(CodecError::InvalidFrameSize {
53            expected: expected_sizes[0],
54            actual: frame_size,
55        });
56    }
57
58    Ok(())
59}
60
61/// Validate sample rate for a specific codec
62pub fn validate_sample_rate(codec_type: CodecType, sample_rate: SampleRate) -> Result<()> {
63    let supported_rates = codec_type.supported_sample_rates();
64    let rate_hz = sample_rate.hz();
65
66    if !supported_rates.contains(&rate_hz) {
67        return Err(CodecError::InvalidSampleRate {
68            rate: rate_hz,
69            supported: supported_rates.to_vec(),
70        });
71    }
72
73    Ok(())
74}
75
76/// Validate channel count for a specific codec
77pub fn validate_channels(codec_type: CodecType, channels: u8) -> Result<()> {
78    let supported_channels = codec_type.supported_channels();
79
80    if !supported_channels.contains(&channels) {
81        return Err(CodecError::InvalidChannelCount {
82            channels,
83            supported: supported_channels.to_vec(),
84        });
85    }
86
87    Ok(())
88}
89
90/// Validate bitrate for a specific codec
91pub fn validate_bitrate(codec_type: CodecType, bitrate: u32) -> Result<()> {
92    let (min_bitrate, max_bitrate) = codec_type.bitrate_range();
93
94    if bitrate < min_bitrate || bitrate > max_bitrate {
95        return Err(CodecError::InvalidBitrate {
96            bitrate,
97            min: min_bitrate,
98            max: max_bitrate,
99        });
100    }
101
102    Ok(())
103}
104
105/// Validate buffer sizes for encoding/decoding operations
106pub fn validate_buffer_sizes(
107    input_size: usize,
108    output_size: usize,
109    expected_ratio: f32,
110) -> Result<()> {
111    let expected_output_size = (input_size as f32 * expected_ratio) as usize;
112
113    if output_size < expected_output_size {
114        return Err(CodecError::BufferTooSmall {
115            needed: expected_output_size,
116            actual: output_size,
117        });
118    }
119
120    Ok(())
121}
122
123/// Validate that frame samples are properly aligned for multi-channel audio
124pub fn validate_channel_alignment(samples: &[i16], channels: u8) -> Result<()> {
125    if samples.len() % channels as usize != 0 {
126        return Err(CodecError::invalid_format(format!(
127            "Sample count {} not divisible by channel count {}",
128            samples.len(),
129            channels
130        )));
131    }
132
133    Ok(())
134}
135
136/// Validate G.711 specific parameters
137pub fn validate_g711_frame(samples: &[i16], expected_frame_size: usize) -> Result<()> {
138    validate_samples(samples)?;
139
140    if samples.len() != expected_frame_size {
141        return Err(CodecError::InvalidFrameSize {
142            expected: expected_frame_size,
143            actual: samples.len(),
144        });
145    }
146
147    Ok(())
148}
149
150/// Validate G.722 specific parameters
151pub fn validate_g722_frame(samples: &[i16], expected_frame_size: usize) -> Result<()> {
152    validate_samples(samples)?;
153
154    if samples.len() != expected_frame_size {
155        return Err(CodecError::InvalidFrameSize {
156            expected: expected_frame_size,
157            actual: samples.len(),
158        });
159    }
160
161    // G.722 requires even number of samples for QMF processing
162    if samples.len() % 2 != 0 {
163        return Err(CodecError::invalid_format(
164            "G.722 requires even number of samples for QMF processing",
165        ));
166    }
167
168    Ok(())
169}
170
171/// Validate G.729 specific parameters
172pub fn validate_g729_frame(samples: &[i16]) -> Result<()> {
173    validate_samples(samples)?;
174
175    // G.729 uses fixed 80-sample frames (10ms at 8kHz)
176    if samples.len() != 80 {
177        return Err(CodecError::InvalidFrameSize {
178            expected: 80,
179            actual: samples.len(),
180        });
181    }
182
183    Ok(())
184}
185
186/// Validate Opus specific parameters
187pub fn validate_opus_frame(samples: &[i16], sample_rate: SampleRate) -> Result<()> {
188    validate_samples(samples)?;
189
190    let rate_hz = sample_rate.hz();
191    let frame_size = samples.len();
192
193    // Opus supports specific frame sizes based on sample rate
194    let valid_frame_sizes = match rate_hz {
195        8000 => vec![20, 40, 80, 160, 320, 480],
196        12000 => vec![30, 60, 120, 240, 480, 720],
197        16000 => vec![40, 80, 160, 320, 640, 960],
198        24000 => vec![60, 120, 240, 480, 960, 1440],
199        48000 => vec![120, 240, 480, 960, 1920, 2880],
200        _ => {
201            return Err(CodecError::InvalidSampleRate {
202                rate: rate_hz,
203                supported: vec![8000, 12000, 16000, 24000, 48000],
204            })
205        }
206    };
207
208    if !valid_frame_sizes.contains(&frame_size) {
209        return Err(CodecError::InvalidFrameSize {
210            expected: valid_frame_sizes[0],
211            actual: frame_size,
212        });
213    }
214
215    Ok(())
216}
217
218/// Validate that two buffers have compatible sizes for processing
219pub fn validate_buffer_compatibility(
220    input: &[i16],
221    output: &[u8],
222    compression_ratio: f32,
223) -> Result<()> {
224    let expected_output_size = (input.len() as f32 * compression_ratio) as usize;
225
226    if output.len() < expected_output_size {
227        return Err(CodecError::BufferTooSmall {
228            needed: expected_output_size,
229            actual: output.len(),
230        });
231    }
232
233    Ok(())
234}
235
236/// Validate memory alignment for SIMD operations
237pub fn validate_simd_alignment(data: &[i16]) -> Result<()> {
238    let ptr = data.as_ptr() as usize;
239
240    // Check for 16-byte alignment (required for SSE)
241    if ptr % 16 != 0 {
242        tracing::debug!("Data not aligned for SIMD operations, falling back to scalar");
243    }
244
245    Ok(())
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use crate::types::SampleRate;
252
253    #[test]
254    fn test_validate_samples() {
255        let valid_samples = vec![0, 1000, -1000, 16000, -16000];
256        assert!(validate_samples(&valid_samples).is_ok());
257
258        let empty_samples: Vec<i16> = vec![];
259        assert!(validate_samples(&empty_samples).is_err());
260    }
261
262    #[test]
263    fn test_validate_encoded_data() {
264        let valid_data = vec![0u8, 127, 255, 64, 192];
265        assert!(validate_encoded_data(&valid_data).is_ok());
266
267        let empty_data: Vec<u8> = vec![];
268        assert!(validate_encoded_data(&empty_data).is_err());
269
270        let too_large_data = vec![0u8; 2 * 1024 * 1024]; // 2MB
271        assert!(validate_encoded_data(&too_large_data).is_err());
272    }
273
274    #[test]
275    fn test_validate_frame_size() {
276        // G.711 valid frame sizes
277        assert!(validate_frame_size(CodecType::G711Pcmu, 160).is_ok());
278        assert!(validate_frame_size(CodecType::G711Pcmu, 123).is_err());
279
280        // G.729 fixed frame size
281        assert!(validate_frame_size(CodecType::G729, 80).is_ok());
282        assert!(validate_frame_size(CodecType::G729, 160).is_err());
283    }
284
285    #[test]
286    fn test_validate_sample_rate() {
287        // G.711 supports only 8kHz
288        assert!(validate_sample_rate(CodecType::G711Pcmu, SampleRate::Rate8000).is_ok());
289        assert!(validate_sample_rate(CodecType::G711Pcmu, SampleRate::Rate48000).is_err());
290
291        // Opus supports multiple rates
292        assert!(validate_sample_rate(CodecType::Opus, SampleRate::Rate8000).is_ok());
293        assert!(validate_sample_rate(CodecType::Opus, SampleRate::Rate48000).is_ok());
294    }
295
296    #[test]
297    fn test_validate_channels() {
298        // G.711 supports only mono
299        assert!(validate_channels(CodecType::G711Pcmu, 1).is_ok());
300        assert!(validate_channels(CodecType::G711Pcmu, 2).is_err());
301
302        // Opus supports mono and stereo
303        assert!(validate_channels(CodecType::Opus, 1).is_ok());
304        assert!(validate_channels(CodecType::Opus, 2).is_ok());
305        assert!(validate_channels(CodecType::Opus, 3).is_err());
306    }
307
308    #[test]
309    fn test_validate_bitrate() {
310        // G.711 has fixed bitrate
311        assert!(validate_bitrate(CodecType::G711Pcmu, 64000).is_ok());
312        assert!(validate_bitrate(CodecType::G711Pcmu, 128000).is_err());
313
314        // Opus has variable bitrate
315        assert!(validate_bitrate(CodecType::Opus, 32000).is_ok());
316        assert!(validate_bitrate(CodecType::Opus, 600000).is_err());
317    }
318
319    #[test]
320    fn test_validate_buffer_sizes() {
321        // G.711 has 1:1 compression ratio (16-bit to 8-bit)
322        assert!(validate_buffer_sizes(160, 80, 0.5).is_ok());
323        assert!(validate_buffer_sizes(160, 40, 0.5).is_err());
324    }
325
326    #[test]
327    fn test_validate_channel_alignment() {
328        let mono_samples = vec![0, 1, 2, 3, 4]; // 5 samples
329        assert!(validate_channel_alignment(&mono_samples, 1).is_ok());
330        assert!(validate_channel_alignment(&mono_samples, 2).is_err());
331
332        let stereo_samples = vec![0, 1, 2, 3]; // 4 samples = 2 stereo pairs
333        assert!(validate_channel_alignment(&stereo_samples, 2).is_ok());
334    }
335
336    #[test]
337    fn test_codec_specific_validation() {
338        // G.711 frame validation
339        let g711_frame = vec![0i16; 160];
340        assert!(validate_g711_frame(&g711_frame, 160).is_ok());
341        assert!(validate_g711_frame(&g711_frame, 80).is_err());
342
343        // G.729 frame validation
344        let g729_frame = vec![0i16; 80];
345        assert!(validate_g729_frame(&g729_frame).is_ok());
346
347        let wrong_g729_frame = vec![0i16; 160];
348        assert!(validate_g729_frame(&wrong_g729_frame).is_err());
349    }
350
351    #[test]
352    fn test_buffer_compatibility() {
353        let input = vec![0i16; 160];
354        let output = vec![0u8; 80];
355
356        // G.711 compression ratio: 0.5 (16-bit to 8-bit)
357        assert!(validate_buffer_compatibility(&input, &output, 0.5).is_ok());
358
359        let small_output = vec![0u8; 40];
360        assert!(validate_buffer_compatibility(&input, &small_output, 0.5).is_err());
361    }
362}