mediaway-sw 0.1.6

Pure Rust sans-io software codec fallbacks (no C codec FFI)
Documentation
#![cfg(test)]
#![allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::cast_precision_loss,
    reason = "test module may unwrap; sample indices here are tiny (hundreds of samples) so \
              the f32 cast never loses meaningful precision"
)]

use super::*;
use crate::opus::config::{OpusApplication, OpusEncoderConfig};
use crate::opus::encoder::OpusEncoder;
use mediaway_common::Rational;

const SAMPLE_RATE: u32 = 48_000;
const CHANNELS: u16 = 1;
const FRAME_SAMPLES: usize = 960;

fn encoder_config() -> OpusEncoderConfig {
    OpusEncoderConfig::new(
        SAMPLE_RATE,
        CHANNELS,
        OpusApplication::Voip,
        Rational::new(1, 50),
    )
}

fn decoder_config() -> OpusDecoderConfig {
    OpusDecoderConfig::new(SAMPLE_RATE, CHANNELS, Rational::new(1, 50))
}

fn sine_frame(samples: usize, channels: u16) -> AudioFrame {
    let mut data = Vec::with_capacity(samples * usize::from(channels) * 4);
    for i in 0..samples {
        let t = i as f32 / SAMPLE_RATE as f32;
        let v = (t * 440.0 * std::f32::consts::TAU).sin() * 0.5;
        for _ in 0..channels {
            data.extend_from_slice(&v.to_le_bytes());
        }
    }
    AudioFrame {
        pts: 0,
        duration: samples as u64,
        sample_rate: SAMPLE_RATE,
        channels,
        format: SampleFormat::F32,
        data: Bytes::from(data),
    }
}

#[test]
fn open_succeeds_for_standard_20ms_config() {
    OpusDecoder::open(&decoder_config()).expect("open");
}

#[test]
fn open_rejects_non_legal_frame_duration() {
    let mut cfg = decoder_config();
    cfg.time_base = Rational::new(1, 700);
    let err = OpusDecoder::open(&cfg).unwrap_err();
    assert!(matches!(err, OpusError::InvalidFrameDuration { .. }));
}

#[test]
fn push_packet_decodes_encoder_output_to_expected_sample_count() {
    let mut enc = OpusEncoder::open(&encoder_config()).expect("open encoder");
    let mut dec = OpusDecoder::open(&decoder_config()).expect("open decoder");

    let frame = sine_frame(FRAME_SAMPLES, CHANNELS);
    enc.push_frame(&frame).expect("push_frame");
    let packet = enc.poll_packet().expect("poll_packet").expect("one packet");

    dec.push_packet(&packet).expect("push_packet");
    let decoded = dec.poll_frame().expect("poll_frame").expect("one frame");
    assert_eq!(decoded.format, SampleFormat::F32);
    assert_eq!(
        decoded.data.len(),
        FRAME_SAMPLES * usize::from(CHANNELS) * 4
    );
    assert!(dec.poll_frame().expect("poll_frame").is_none());
}

#[test]
fn push_packet_treats_empty_payload_as_packet_loss_concealment() {
    let mut dec = OpusDecoder::open(&decoder_config()).expect("open");
    let packet = Packet {
        stream_id: 0,
        pts: 0,
        dts: 0,
        duration: FRAME_SAMPLES as u64,
        is_keyframe: true,
        is_discard: false,
        payload: Bytes::new(),
    };
    dec.push_packet(&packet).expect("push_packet (PLC)");
    let decoded = dec.poll_frame().expect("poll_frame").expect("one frame");
    assert_eq!(decoded.data.len() % 4, 0);
}

#[test]
fn push_packet_rejects_after_flush() {
    let mut dec = OpusDecoder::open(&decoder_config()).expect("open");
    dec.flush().expect("flush");
    let packet = Packet {
        stream_id: 0,
        pts: 0,
        dts: 0,
        duration: 0,
        is_keyframe: true,
        is_discard: false,
        payload: Bytes::new(),
    };
    let err = dec.push_packet(&packet).unwrap_err();
    assert_eq!(err, OpusError::Closed);
}

#[test]
fn stream_info_reports_opus_codec() {
    let dec = OpusDecoder::open(&decoder_config()).expect("open");
    assert_eq!(dec.stream_info().codec(), CodecKind::Opus);
}