use std::os::raw::{c_int, c_uchar, c_uint};
use std::panic::{catch_unwind, AssertUnwindSafe};
use crate::codec::{OpusChannels, OpusConfig, OpusEncodeError, OpusEncoder};
#[repr(i32)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PksCodecErrorCode {
InvalidPointer = -1,
Encode = -2,
OutputTooSmall = -3,
InvalidFrame = -4,
InternalPanic = -5,
}
impl PksCodecErrorCode {
const fn as_c_int(self) -> c_int {
self as c_int
}
}
fn codec_int_call(function: impl FnOnce() -> c_int) -> c_int {
catch_unwind(AssertUnwindSafe(function))
.unwrap_or_else(|_| PksCodecErrorCode::InternalPanic.as_c_int())
}
fn codec_pointer_call<T>(function: impl FnOnce() -> *mut T) -> *mut T {
catch_unwind(AssertUnwindSafe(function)).unwrap_or(std::ptr::null_mut())
}
fn codec_void_call(function: impl FnOnce()) {
let _ = catch_unwind(AssertUnwindSafe(function));
}
pub struct PksOpusEncoder {
inner: OpusEncoder,
encode_buf: Vec<u8>,
}
#[no_mangle]
pub extern "C" fn pks_opus_max_packet_bytes() -> usize {
crate::codec::OPUS_MAX_PACKET_BYTES
}
#[no_mangle]
pub unsafe extern "C" fn pks_opus_encoder_create(
sample_rate: c_uint,
channels: u8,
bitrate_kbps: c_uint,
) -> *mut PksOpusEncoder {
codec_pointer_call(|| {
if sample_rate != 48_000 {
return std::ptr::null_mut();
}
let ch = match channels {
1 => OpusChannels::Mono,
2 => OpusChannels::Stereo,
_ => return std::ptr::null_mut(),
};
let config = OpusConfig {
channels: ch,
bitrate_kbps: if bitrate_kbps > 0 {
Some(bitrate_kbps)
} else {
None
},
..OpusConfig::default()
};
match OpusEncoder::from_config(&config) {
Ok(enc) => Box::into_raw(Box::new(PksOpusEncoder {
inner: enc,
encode_buf: Vec::with_capacity(crate::codec::OPUS_MAX_PACKET_BYTES),
})),
Err(_) => std::ptr::null_mut(),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn pks_opus_encoder_destroy(enc: *mut PksOpusEncoder) {
if !enc.is_null() {
codec_void_call(|| {
unsafe { drop(Box::from_raw(enc)) };
});
}
}
#[no_mangle]
pub unsafe extern "C" fn pks_opus_encoder_set_bitrate(
enc: *mut PksOpusEncoder,
bitrate_kbps: c_uint,
) -> c_int {
codec_int_call(|| {
if enc.is_null() {
return PksCodecErrorCode::InvalidPointer.as_c_int();
}
let encoder = unsafe { &mut *enc };
match encoder.inner.set_bitrate_kbps(bitrate_kbps) {
Ok(()) => 0,
Err(_) => PksCodecErrorCode::Encode.as_c_int(),
}
})
}
#[no_mangle]
pub unsafe extern "C" fn pks_encode_opus(
enc: *mut PksOpusEncoder,
pcm: *const f32,
sample_count: usize,
out_buf: *mut c_uchar,
out_cap: usize,
) -> c_int {
codec_int_call(|| {
if enc.is_null() || pcm.is_null() || out_buf.is_null() {
return PksCodecErrorCode::InvalidPointer.as_c_int();
}
let encoder = unsafe { &mut *enc };
match encoder.inner.validate_frame_sample_count(sample_count) {
Ok(()) => {}
Err(OpusEncodeError::InvalidFrameSampleCount { .. }) => {
return PksCodecErrorCode::InvalidFrame.as_c_int();
}
Err(OpusEncodeError::Opus(_)) => {
return PksCodecErrorCode::Encode.as_c_int();
}
}
if out_cap < crate::codec::OPUS_MAX_PACKET_BYTES {
return PksCodecErrorCode::OutputTooSmall.as_c_int();
}
let samples = unsafe { std::slice::from_raw_parts(pcm, sample_count) };
encoder.encode_buf.clear();
match encoder.inner.encode_into(samples, &mut encoder.encode_buf) {
Ok(n) => {
unsafe {
std::ptr::copy_nonoverlapping(encoder.encode_buf.as_ptr(), out_buf, n);
}
n as c_int
}
Err(OpusEncodeError::InvalidFrameSampleCount { .. }) => {
PksCodecErrorCode::InvalidFrame.as_c_int()
}
Err(OpusEncodeError::Opus(_)) => PksCodecErrorCode::Encode.as_c_int(),
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
fn sine_960(hz: f32) -> Vec<f32> {
(0..crate::codec::OPUS_FRAME_SAMPLES)
.map(|i| (2.0 * PI * hz * i as f32 / 48_000.0).sin() * 0.25)
.collect()
}
#[test]
fn given_wrong_sample_rate_when_create_then_returns_null() {
unsafe {
let enc = pks_opus_encoder_create(44_100, 1, 64);
assert!(enc.is_null(), "must reject non-48000 sample rate");
}
}
#[test]
fn given_invalid_channel_count_when_create_then_returns_null() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 3, 64);
assert!(enc.is_null(), "must reject channels != 1 or 2");
}
}
#[test]
fn given_stereo_channels_when_create_then_succeeds() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 2, 64);
assert!(!enc.is_null(), "stereo encoder must succeed");
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_valid_pcm_when_encode_then_returns_positive_byte_count() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null(), "encoder creation failed");
let pcm = sine_960(440.0);
let mut out = vec![0u8; pks_opus_max_packet_bytes()];
let n = pks_encode_opus(enc, pcm.as_ptr(), pcm.len(), out.as_mut_ptr(), out.len());
assert!(n > 0, "expected positive byte count, got {n}");
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_null_encoder_when_encode_then_returns_minus_one() {
unsafe {
let mut out = vec![0u8; pks_opus_max_packet_bytes()];
let pcm = sine_960(440.0);
let n = pks_encode_opus(
std::ptr::null_mut(),
pcm.as_ptr(),
pcm.len(),
out.as_mut_ptr(),
out.len(),
);
assert_eq!(n, -1);
}
}
#[test]
fn given_small_output_buffer_when_encode_then_packet_is_not_truncated() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null());
let pcm = sine_960(440.0);
let mut out = [0xA5_u8; 1];
let status = pks_encode_opus(enc, pcm.as_ptr(), pcm.len(), out.as_mut_ptr(), out.len());
assert_eq!(status, PksCodecErrorCode::OutputTooSmall.as_c_int());
assert_eq!(out, [0xA5], "insufficient capacity must not copy a prefix");
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_invalid_frame_size_when_encode_then_error_is_typed_without_writing() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null());
let pcm = [0.0_f32; 961];
let mut out = [0xA5_u8; 256];
let status = pks_encode_opus(enc, pcm.as_ptr(), pcm.len(), out.as_mut_ptr(), out.len());
assert_eq!(status, PksCodecErrorCode::InvalidFrame.as_c_int());
assert!(out.iter().all(|byte| *byte == 0xA5));
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_rejected_capacity_when_retried_then_encoder_state_is_unchanged() {
unsafe {
let retried_encoder = pks_opus_encoder_create(48_000, 1, 64);
let reference_encoder = pks_opus_encoder_create(48_000, 1, 64);
assert!(!retried_encoder.is_null());
assert!(!reference_encoder.is_null());
let pcm = sine_960(440.0);
let mut too_small = [0xA5_u8; 1];
assert_eq!(
pks_encode_opus(
retried_encoder,
pcm.as_ptr(),
pcm.len(),
too_small.as_mut_ptr(),
too_small.len(),
),
PksCodecErrorCode::OutputTooSmall.as_c_int()
);
let mut retried = vec![0_u8; pks_opus_max_packet_bytes()];
let mut reference = vec![0_u8; pks_opus_max_packet_bytes()];
let retried_len = pks_encode_opus(
retried_encoder,
pcm.as_ptr(),
pcm.len(),
retried.as_mut_ptr(),
retried.len(),
);
let reference_len = pks_encode_opus(
reference_encoder,
pcm.as_ptr(),
pcm.len(),
reference.as_mut_ptr(),
reference.len(),
);
assert!(retried_len > 0);
assert_eq!(retried_len, reference_len);
assert_eq!(
&retried[..retried_len as usize],
&reference[..reference_len as usize]
);
pks_opus_encoder_destroy(retried_encoder);
pks_opus_encoder_destroy(reference_encoder);
}
}
#[test]
fn given_panicking_abi_bodies_when_guarded_then_panics_are_contained() {
assert_eq!(
codec_int_call(|| panic!("int guard probe")),
PksCodecErrorCode::InternalPanic.as_c_int()
);
let pointer = codec_pointer_call::<PksOpusEncoder>(|| panic!("pointer guard probe"));
assert!(pointer.is_null());
codec_void_call(|| panic!("void guard probe"));
}
#[test]
fn given_encoder_when_destroy_null_then_no_crash() {
unsafe { pks_opus_encoder_destroy(std::ptr::null_mut()) }
}
#[test]
fn given_valid_encoder_when_set_bitrate_then_returns_zero() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null());
assert_eq!(pks_opus_encoder_set_bitrate(enc, 32), 0);
assert_eq!(pks_opus_encoder_set_bitrate(enc, 96), 0);
assert_eq!(
pks_opus_encoder_set_bitrate(enc, 0),
0,
"0 kbps = VBR auto must succeed"
);
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_null_encoder_when_set_bitrate_then_returns_minus_one() {
unsafe {
assert_eq!(pks_opus_encoder_set_bitrate(std::ptr::null_mut(), 64), -1);
}
}
#[test]
fn given_bitrate_change_when_encode_then_still_produces_valid_packet() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null());
pks_opus_encoder_set_bitrate(enc, 32);
let pcm = sine_960(440.0);
let mut out = vec![0u8; pks_opus_max_packet_bytes()];
let n = pks_encode_opus(enc, pcm.as_ptr(), pcm.len(), out.as_mut_ptr(), out.len());
assert!(
n > 0,
"encode after bitrate change must produce valid packet"
);
pks_opus_encoder_destroy(enc);
}
}
#[test]
fn given_sine_440hz_when_round_trip_then_decoded_has_energy() {
unsafe {
let enc = pks_opus_encoder_create(48_000, 1, 64);
assert!(!enc.is_null());
let pcm = sine_960(440.0);
let mut out = vec![0u8; pks_opus_max_packet_bytes()];
let n = pks_encode_opus(enc, pcm.as_ptr(), pcm.len(), out.as_mut_ptr(), out.len());
assert!(n > 0);
assert!(
n <= pks_opus_max_packet_bytes() as c_int,
"encoded frame exceeds the documented maximum: {n}"
);
assert!(n >= 2, "encoded frame suspiciously small: {n}");
pks_opus_encoder_destroy(enc);
}
}
}