1#![allow(non_upper_case_globals)]
16#![allow(non_camel_case_types)]
17#![allow(non_snake_case)]
18#![allow(dead_code)]
19#![allow(clippy::all)]
20
21mod bindings;
22pub use bindings::*;
23
24#[cfg(test)]
25mod tests {
26 use super::*;
27
28 #[test]
29 fn test_opus_version() {
30 unsafe {
31 let version = opus_get_version_string();
32 assert!(!version.is_null());
33 let version_str = std::ffi::CStr::from_ptr(version);
34 let version_string = version_str.to_string_lossy();
35 println!("Opus version: {}", version_string);
36 assert!(version_string.contains("opus") || version_string.contains("libopus"));
37 }
38 }
39
40 #[test]
41 fn test_encoder_size() {
42 unsafe {
43 let size_mono = opus_encoder_get_size(1);
44 let size_stereo = opus_encoder_get_size(2);
45 assert!(size_mono > 0);
46 assert!(size_stereo > 0);
47 assert!(size_stereo >= size_mono);
48 }
49 }
50
51 #[test]
52 fn test_decoder_size() {
53 unsafe {
54 let size_mono = opus_decoder_get_size(1);
55 let size_stereo = opus_decoder_get_size(2);
56 assert!(size_mono > 0);
57 assert!(size_stereo > 0);
58 }
59 }
60
61 #[test]
62 fn test_error_string() {
63 unsafe {
64 let ok_str = opus_strerror(OPUS_OK as i32);
65 assert!(!ok_str.is_null());
66
67 let bad_arg_str = opus_strerror(OPUS_BAD_ARG);
68 assert!(!bad_arg_str.is_null());
69 }
70 }
71
72 #[test]
73 fn test_multistream_encoder_size() {
74 unsafe {
75 let size = opus_multistream_encoder_get_size(1, 1);
77 assert!(size > 0);
78
79 let size_surround = opus_multistream_surround_encoder_get_size(6, 1);
81 assert!(size_surround > 0);
82 }
83 }
84
85 #[test]
86 fn test_multistream_decoder_size() {
87 unsafe {
88 let size = opus_multistream_decoder_get_size(1, 1);
90 assert!(size > 0);
91 }
92 }
93
94 #[test]
95 fn test_projection_encoder_size() {
96 unsafe {
97 let size = opus_projection_ambisonics_encoder_get_size(4, 3);
99 assert!(size > 0, "Expected positive size, got {}", size);
100 }
101 }
102
103 #[test]
104 fn test_projection_decoder_size() {
105 unsafe {
106 let size = opus_projection_decoder_get_size(4, 2, 1);
108 assert!(size > 0);
109 }
110 }
111
112 #[test]
122 #[cfg(feature = "dnn")]
123 fn test_dnn_blob_loading() {
124 use std::path::PathBuf;
125
126 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
128 let model_dir = manifest_dir.join("target").join("model");
129
130 println!("Looking for weights in: {:?}", model_dir);
131
132 let weights_path = match std::fs::read_dir(&model_dir) {
134 Ok(dir) => {
135 match dir.filter_map(|e| e.ok()).map(|e| e.path()).find(|p| {
136 p.file_name()
137 .and_then(|n| n.to_str())
138 .map(|n| n.starts_with("opus_data-") && n.ends_with(".bin"))
139 .unwrap_or(false)
140 }) {
141 Some(path) => path,
142 None => {
143 panic!(
144 "No opus_data-*.bin found in {:?}.\n\
145 Run 'python generate_weights.py' to generate weights.",
146 model_dir
147 );
148 }
149 }
150 }
151 Err(e) => {
152 panic!(
153 "target/model directory not found ({:?}): {}\n\
154 Run 'python generate_weights.py' to generate weights.",
155 model_dir, e
156 );
157 }
158 };
159
160 println!("Loading weights from: {:?}", weights_path);
161
162 let weights_data = std::fs::read(&weights_path).expect("Failed to read weights file");
164
165 println!("Loaded {} bytes of DNN weights", weights_data.len());
166
167 assert!(weights_data.len() > 4, "Weights file too small");
169 assert_eq!(
170 &weights_data[0..4],
171 b"DNNw",
172 "Invalid weights file magic header"
173 );
174
175 assert!(
177 weights_data.len() > 1_000_000,
178 "Weights file seems too small"
179 );
180 assert!(
181 weights_data.len() < 100_000_000,
182 "Weights file seems too large"
183 );
184
185 println!("Weight file format validation passed");
186 println!(" Magic: DNNw");
187 println!(
188 " Size: {} bytes ({:.2} MB)",
189 weights_data.len(),
190 weights_data.len() as f64 / 1_000_000.0
191 );
192
193 unsafe {
195 let mut error: i32 = 0;
196
197 println!("Creating encoder...");
199 let encoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP as i32, &mut error);
200 println!(
201 "opus_encoder_create returned error={}, encoder={:?}",
202 error, encoder
203 );
204 if error != OPUS_OK as i32 || encoder.is_null() {
205 println!("Failed to create encoder");
206 return;
207 }
208
209 println!("Calling opus_encoder_ctl(OPUS_SET_DNN_BLOB)...");
211 let ret = opus_encoder_ctl(
212 encoder,
213 OPUS_SET_DNN_BLOB_REQUEST as i32,
214 weights_data.as_ptr() as *const std::ffi::c_void,
215 weights_data.len() as i32,
216 );
217 println!("opus_encoder_ctl(OPUS_SET_DNN_BLOB) returned: {}", ret);
218
219 println!("Destroying encoder...");
220 opus_encoder_destroy(encoder);
221 println!("Encoder destroyed");
222
223 if ret != OPUS_OK as i32 {
224 println!("DNN blob loading failed on encoder with code {}", ret);
225 println!("This may indicate the weights file format is incompatible.");
226 return;
227 }
228
229 println!("Encoder DNN blob loading: OK");
230
231 println!("Creating decoder...");
233 let decoder = opus_decoder_create(48000, 1, &mut error);
234 println!(
235 "opus_decoder_create returned error={}, decoder={:?}",
236 error, decoder
237 );
238 if error != OPUS_OK as i32 || decoder.is_null() {
239 println!("Failed to create decoder");
240 return;
241 }
242
243 println!("Calling opus_decoder_ctl(OPUS_SET_DNN_BLOB)...");
245 let ret = opus_decoder_ctl(
246 decoder,
247 OPUS_SET_DNN_BLOB_REQUEST as i32,
248 weights_data.as_ptr() as *const std::ffi::c_void,
249 weights_data.len() as i32,
250 );
251 println!("opus_decoder_ctl(OPUS_SET_DNN_BLOB) returned: {}", ret);
252
253 println!("Destroying decoder...");
254 opus_decoder_destroy(decoder);
255 println!("Decoder destroyed");
256
257 if ret != OPUS_OK as i32 {
258 println!("DNN blob loading failed on decoder with code {}", ret);
259 return;
260 }
261
262 println!("Decoder DNN blob loading: OK");
263 }
264
265 println!("DNN blob loading test passed!");
266 }
267
268 fn generate_noise_with_seed(samples: usize, seed: u64) -> Vec<i16> {
271 use std::collections::hash_map::DefaultHasher;
272 use std::hash::{Hash, Hasher};
273
274 let mut data = Vec::with_capacity(samples);
275 let mut hasher = DefaultHasher::new();
276 seed.hash(&mut hasher);
277
278 for i in 0..samples {
279 i.hash(&mut hasher);
280 let hash = hasher.finish();
281 let sample = ((hash as i32 % 32768) - 16384) as i16;
283 data.push(sample);
284 }
285 data
286 }
287
288 fn generate_noise(samples: usize) -> Vec<i16> {
290 generate_noise_with_seed(samples, 12345)
291 }
292
293 fn load_dnn_weights() -> Option<Vec<u8>> {
295 use std::path::PathBuf;
296
297 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
298 let model_dir = manifest_dir.join("target").join("model");
299
300 let weights_path = std::fs::read_dir(&model_dir)
301 .ok()?
302 .filter_map(|e| e.ok())
303 .map(|e| e.path())
304 .find(|p| {
305 p.file_name()
306 .and_then(|n| n.to_str())
307 .map(|n| n.starts_with("opus_data-") && n.ends_with(".bin"))
308 .unwrap_or(false)
309 })?;
310
311 std::fs::read(&weights_path).ok()
312 }
313
314 #[test]
317 fn test_encode_decode_without_dnn() {
318 const SAMPLE_RATE: i32 = 48000;
319 const CHANNELS: i32 = 1; const FRAME_SIZE: usize = 960; const BITRATE: i32 = 32000; unsafe {
324 let mut error: i32 = 0;
325
326 let encoder = opus_encoder_create(
328 SAMPLE_RATE,
329 CHANNELS,
330 OPUS_APPLICATION_VOIP as i32,
331 &mut error,
332 );
333 assert_eq!(error, OPUS_OK as i32, "Failed to create encoder");
334 assert!(!encoder.is_null());
335
336 let ret = opus_encoder_ctl(encoder, OPUS_SET_BITRATE_REQUEST as i32, BITRATE);
338 assert_eq!(ret, OPUS_OK as i32, "Failed to set bitrate");
339
340 let packet_loss_perc: i32 = 25;
342 let ret = opus_encoder_ctl(
343 encoder,
344 OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32,
345 packet_loss_perc,
346 );
347 assert_eq!(ret, OPUS_OK as i32, "Failed to set packet loss");
348
349 let dred_duration_ms: i32 = 100; let ret = opus_encoder_ctl(
352 encoder,
353 OPUS_SET_DRED_DURATION_REQUEST as i32,
354 dred_duration_ms,
355 );
356 println!("OPUS_SET_DRED_DURATION (without DNN) returned: {}", ret);
357 let decoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &mut error);
361 assert_eq!(error, OPUS_OK as i32, "Failed to create decoder");
362 assert!(!decoder.is_null());
363
364 let input = generate_noise(FRAME_SIZE);
366 let mut encoded = vec![0u8; 4000]; let mut decoded = vec![0i16; FRAME_SIZE];
368
369 let encoded_len = opus_encode(
371 encoder,
372 input.as_ptr(),
373 FRAME_SIZE as i32,
374 encoded.as_mut_ptr(),
375 encoded.len() as i32,
376 );
377 assert!(
378 encoded_len > 0,
379 "Encoding failed with error {}",
380 encoded_len
381 );
382 println!(
383 "Encoded {} samples to {} bytes (WITHOUT DNN, DRED attempted)",
384 FRAME_SIZE, encoded_len
385 );
386
387 let decoded_len = opus_decode(
389 decoder,
390 encoded.as_ptr(),
391 encoded_len,
392 decoded.as_mut_ptr(),
393 FRAME_SIZE as i32,
394 0,
395 );
396 assert_eq!(
397 decoded_len, FRAME_SIZE as i32,
398 "Decoding failed or wrong frame size"
399 );
400 println!(
401 "Decoded {} bytes to {} samples (WITHOUT DNN)",
402 encoded_len, decoded_len
403 );
404
405 opus_encoder_destroy(encoder);
407 opus_decoder_destroy(decoder);
408 }
409
410 println!("Encode/decode test WITHOUT DNN passed!");
411 }
412
413 #[test]
415 #[cfg(feature = "dnn")]
416 fn test_encode_decode_with_dnn() {
417 const SAMPLE_RATE: i32 = 48000;
418 const CHANNELS: i32 = 1;
419 const FRAME_SIZE: usize = 960; const BITRATE: i32 = 64000;
421
422 let weights_data = match load_dnn_weights() {
424 Some(data) => data,
425 None => {
426 panic!(
427 "DNN weights not found. Run 'python generate_weights.py' to generate weights."
428 );
429 }
430 };
431
432 println!("Loaded {} bytes of DNN weights", weights_data.len());
433
434 unsafe {
435 let mut error: i32 = 0;
436
437 let encoder = opus_encoder_create(
439 SAMPLE_RATE,
440 CHANNELS,
441 OPUS_APPLICATION_VOIP as i32,
442 &mut error,
443 );
444 assert_eq!(error, OPUS_OK as i32, "Failed to create encoder");
445 assert!(!encoder.is_null());
446
447 let ret = opus_encoder_ctl(encoder, OPUS_SET_BITRATE_REQUEST as i32, BITRATE);
449 assert_eq!(ret, OPUS_OK as i32, "Failed to set bitrate");
450
451 let packet_loss_perc: i32 = 25; let ret = opus_encoder_ctl(
454 encoder,
455 OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32,
456 packet_loss_perc,
457 );
458 assert_eq!(ret, OPUS_OK as i32, "Failed to set packet loss");
459 println!("Packet loss set to {}%", packet_loss_perc);
460
461 let ret = opus_encoder_ctl(
463 encoder,
464 OPUS_SET_DNN_BLOB_REQUEST as i32,
465 weights_data.as_ptr() as *const std::ffi::c_void,
466 weights_data.len() as i32,
467 );
468 assert_eq!(
469 ret, OPUS_OK as i32,
470 "Failed to load DNN weights into encoder"
471 );
472 println!("DNN weights loaded into encoder");
473
474 let dred_duration_ms: i32 = 100;
476 let ret = opus_encoder_ctl(
477 encoder,
478 OPUS_SET_DRED_DURATION_REQUEST as i32,
479 dred_duration_ms,
480 );
481 println!("OPUS_SET_DRED_DURATION (with DNN) returned: {}", ret);
482 assert_eq!(ret, OPUS_OK as i32, "Failed to enable DRED");
483 println!("DRED enabled with {}ms redundancy", dred_duration_ms);
484
485 let decoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &mut error);
487 assert_eq!(error, OPUS_OK as i32, "Failed to create decoder");
488 assert!(!decoder.is_null());
489
490 let ret = opus_decoder_ctl(
492 decoder,
493 OPUS_SET_DNN_BLOB_REQUEST as i32,
494 weights_data.as_ptr() as *const std::ffi::c_void,
495 weights_data.len() as i32,
496 );
497 if ret == OPUS_OK as i32 {
498 println!("DNN weights loaded into decoder");
499 } else {
500 println!(
501 "Note: Decoder DNN blob returned {} (OSCE may use different API)",
502 ret
503 );
504 }
505
506 let input = generate_noise(FRAME_SIZE);
508 let mut encoded = vec![0u8; 4000]; let mut decoded = vec![0i16; FRAME_SIZE];
510
511 let encoded_len = opus_encode(
513 encoder,
514 input.as_ptr(),
515 FRAME_SIZE as i32,
516 encoded.as_mut_ptr(),
517 encoded.len() as i32,
518 );
519 assert!(
520 encoded_len > 0,
521 "Encoding failed with error {}",
522 encoded_len
523 );
524 println!(
525 "Encoded {} samples to {} bytes (with DNN + DRED)",
526 FRAME_SIZE, encoded_len
527 );
528
529 let decoded_len = opus_decode(
531 decoder,
532 encoded.as_ptr(),
533 encoded_len,
534 decoded.as_mut_ptr(),
535 FRAME_SIZE as i32,
536 0,
537 );
538 assert_eq!(
539 decoded_len, FRAME_SIZE as i32,
540 "Decoding failed or wrong frame size"
541 );
542 println!(
543 "Decoded {} bytes to {} samples (with DNN + DRED)",
544 encoded_len, decoded_len
545 );
546
547 opus_encoder_destroy(encoder);
549 opus_decoder_destroy(decoder);
550 }
551
552 println!("Encode/decode test WITH DNN + DRED passed!");
553 }
554
555 #[test]
557 fn test_multi_frame_encode_decode_without_dnn() {
558 const SAMPLE_RATE: i32 = 48000;
559 const CHANNELS: i32 = 1; const FRAME_SIZE: usize = 960; const NUM_FRAMES: usize = 10;
562 const BITRATE: i32 = 32000; unsafe {
565 let mut error: i32 = 0;
566
567 let encoder = opus_encoder_create(
569 SAMPLE_RATE,
570 CHANNELS,
571 OPUS_APPLICATION_VOIP as i32,
572 &mut error,
573 );
574 assert_eq!(error, OPUS_OK as i32, "Failed to create encoder");
575
576 opus_encoder_ctl(encoder, OPUS_SET_BITRATE_REQUEST as i32, BITRATE);
578
579 let packet_loss_perc: i32 = 25;
581 let ret = opus_encoder_ctl(
582 encoder,
583 OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32,
584 packet_loss_perc,
585 );
586 assert_eq!(ret, OPUS_OK as i32, "Failed to set packet loss");
587
588 let dred_duration_ms: i32 = 100;
590 let ret = opus_encoder_ctl(
591 encoder,
592 OPUS_SET_DRED_DURATION_REQUEST as i32,
593 dred_duration_ms,
594 );
595 println!("OPUS_SET_DRED_DURATION (WITHOUT DNN) returned: {}", ret);
596
597 let decoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &mut error);
599 assert_eq!(error, OPUS_OK as i32, "Failed to create decoder");
600
601 let mut total_encoded_bytes = 0;
602 let mut frame_sizes = Vec::new();
603
604 for frame_num in 0..NUM_FRAMES {
605 let input = generate_noise(FRAME_SIZE);
607 let mut encoded = vec![0u8; 4000];
608 let mut decoded = vec![0i16; FRAME_SIZE];
609
610 let encoded_len = opus_encode(
612 encoder,
613 input.as_ptr(),
614 FRAME_SIZE as i32,
615 encoded.as_mut_ptr(),
616 encoded.len() as i32,
617 );
618 assert!(encoded_len > 0, "Frame {} encoding failed", frame_num);
619 total_encoded_bytes += encoded_len;
620 frame_sizes.push(encoded_len);
621
622 let decoded_len = opus_decode(
624 decoder,
625 encoded.as_ptr(),
626 encoded_len,
627 decoded.as_mut_ptr(),
628 FRAME_SIZE as i32,
629 0,
630 );
631 assert_eq!(
632 decoded_len, FRAME_SIZE as i32,
633 "Frame {} decoding failed",
634 frame_num
635 );
636 }
637
638 let duration_ms = NUM_FRAMES * 20;
639 let bitrate_actual = (total_encoded_bytes * 8 * 1000) / duration_ms as i32;
640 println!("\n=== WITHOUT DNN MODEL ===");
641 println!("Frame sizes: {:?}", frame_sizes);
642 println!(
643 "Encoded {} frames ({} ms) to {} bytes, actual bitrate: {} bps",
644 NUM_FRAMES, duration_ms, total_encoded_bytes, bitrate_actual
645 );
646
647 opus_encoder_destroy(encoder);
648 opus_decoder_destroy(decoder);
649 }
650
651 println!("Multi-frame encode/decode test WITHOUT DNN passed!");
652 }
653
654 #[test]
656 #[cfg(feature = "dnn")]
657 fn test_multi_frame_encode_decode_with_dnn() {
658 const SAMPLE_RATE: i32 = 48000;
659 const CHANNELS: i32 = 1; const FRAME_SIZE: usize = 960; const NUM_FRAMES: usize = 10;
662 const BITRATE: i32 = 32000; let weights_data = match load_dnn_weights() {
666 Some(data) => data,
667 None => {
668 panic!(
669 "DNN weights not found. Run 'python generate_weights.py' to generate weights."
670 );
671 }
672 };
673
674 unsafe {
675 let mut error: i32 = 0;
676
677 let encoder = opus_encoder_create(
679 SAMPLE_RATE,
680 CHANNELS,
681 OPUS_APPLICATION_VOIP as i32,
682 &mut error,
683 );
684 assert_eq!(error, OPUS_OK as i32, "Failed to create encoder");
685
686 opus_encoder_ctl(encoder, OPUS_SET_BITRATE_REQUEST as i32, BITRATE);
688
689 let packet_loss_perc: i32 = 25; let ret = opus_encoder_ctl(
692 encoder,
693 OPUS_SET_PACKET_LOSS_PERC_REQUEST as i32,
694 packet_loss_perc,
695 );
696 assert_eq!(ret, OPUS_OK as i32, "Failed to set packet loss");
697 println!("Packet loss set to {}%", packet_loss_perc);
698
699 let ret = opus_encoder_ctl(
701 encoder,
702 OPUS_SET_DNN_BLOB_REQUEST as i32,
703 weights_data.as_ptr() as *const std::ffi::c_void,
704 weights_data.len() as i32,
705 );
706 assert_eq!(
707 ret, OPUS_OK as i32,
708 "Failed to load DNN weights into encoder"
709 );
710
711 let dred_duration_ms: i32 = 100;
713 let ret = opus_encoder_ctl(
714 encoder,
715 OPUS_SET_DRED_DURATION_REQUEST as i32,
716 dred_duration_ms,
717 );
718 assert_eq!(ret, OPUS_OK as i32, "Failed to enable DRED");
719 println!("DRED enabled with {}ms redundancy", dred_duration_ms);
720
721 let decoder = opus_decoder_create(SAMPLE_RATE, CHANNELS, &mut error);
723 assert_eq!(error, OPUS_OK as i32, "Failed to create decoder");
724
725 let ret = opus_decoder_ctl(
727 decoder,
728 OPUS_SET_DNN_BLOB_REQUEST as i32,
729 weights_data.as_ptr() as *const std::ffi::c_void,
730 weights_data.len() as i32,
731 );
732 if ret == OPUS_OK as i32 {
733 println!("DNN weights loaded into decoder");
734 } else {
735 println!(
736 "Note: Decoder DNN blob returned {} (OSCE may use different API)",
737 ret
738 );
739 }
740
741 let mut total_encoded_bytes = 0;
742 let mut frame_sizes = Vec::new();
743
744 for frame_num in 0..NUM_FRAMES {
745 let input = generate_noise_with_seed(FRAME_SIZE, frame_num as u64);
747 let mut encoded = vec![0u8; 4000];
748 let mut decoded = vec![0i16; FRAME_SIZE];
749
750 let encoded_len = opus_encode(
752 encoder,
753 input.as_ptr(),
754 FRAME_SIZE as i32,
755 encoded.as_mut_ptr(),
756 encoded.len() as i32,
757 );
758 assert!(encoded_len > 0, "Frame {} encoding failed", frame_num);
759 total_encoded_bytes += encoded_len;
760 frame_sizes.push(encoded_len);
761
762 let decoded_len = opus_decode(
764 decoder,
765 encoded.as_ptr(),
766 encoded_len,
767 decoded.as_mut_ptr(),
768 FRAME_SIZE as i32,
769 0,
770 );
771 assert_eq!(
772 decoded_len, FRAME_SIZE as i32,
773 "Frame {} decoding failed",
774 frame_num
775 );
776 }
777
778 let duration_ms = NUM_FRAMES * 20;
779 let bitrate_actual = (total_encoded_bytes * 8 * 1000) / duration_ms as i32;
780 println!("\n=== WITH DNN MODEL + DRED ===");
781 println!("Frame sizes: {:?}", frame_sizes);
782 println!(
783 "Encoded {} frames ({} ms) to {} bytes, actual bitrate: {} bps",
784 NUM_FRAMES, duration_ms, total_encoded_bytes, bitrate_actual
785 );
786
787 opus_encoder_destroy(encoder);
788 opus_decoder_destroy(decoder);
789 }
790
791 println!("Multi-frame encode/decode test WITH DNN + DRED passed!");
792 }
793}