use audiopus::coder::Encoder as OpusEncoder; use audiopus::{ Application as OpusApp, Bitrate as OpusBitrate, Channels as OpusChannels, SampleRate as OpusSampleRate, }; use std::sync::atomic::{AtomicU32, Ordering}; use tokio::sync::mpsc; use tracing::{info, warn}; use chanora_protocol::{AudioData, CodecType, OutAudio, OutPacket}; use crate::AudioError; pub(crate) const MAX_OPUS_FRAME: usize = 1275; const VOIP_BITRATE_BPS: i32 = 32_000; const VOIP_COMPLEXITY: u8 = 10; const VOIP_PACKET_LOSS_PERC: u8 = 5; pub(crate) fn new_voip_encoder(context: &str) -> Result { let mut encoder = OpusEncoder::new(OpusSampleRate::Hz48000, OpusChannels::Mono, OpusApp::Voip) .map_err(|e| AudioError::Opus(format!("encoder new ({context}): {e}")))?; tune_voip_encoder(&mut encoder, context); Ok(encoder) } pub(crate) fn tune_voip_encoder(encoder: &mut OpusEncoder, context: &str) { if let Err(e) = encoder.set_bitrate(OpusBitrate::BitsPerSecond(VOIP_BITRATE_BPS)) { warn!(target: "chanora_audio", context = %context, error = %e, "opus set_bitrate failed"); } if let Err(e) = encoder.set_complexity(VOIP_COMPLEXITY) { warn!(target: "chanora_audio", context = %context, error = %e, "opus set_complexity failed"); } if let Err(e) = encoder.set_inband_fec(true) { warn!(target: "chanora_audio", context = %context, error = %e, "opus set_inband_fec failed"); } if let Err(e) = encoder.set_packet_loss_perc(VOIP_PACKET_LOSS_PERC) { warn!( target: "chanora_audio", context = %context, error = %e, "opus set_packet_loss_perc failed" ); } info!( target: "chanora_audio", context = %context, bitrate_bps = VOIP_BITRATE_BPS, complexity = VOIP_COMPLEXITY, inband_fec = true, packet_loss_perc = VOIP_PACKET_LOSS_PERC, "opus encoder tuned for VoIP" ); } /// Encode-scope send helper for a freshly encoded Opus voice frame. pub(crate) fn send_voip_frame( voice_out_tx: &mpsc::Sender, frames_sent: &AtomicU32, opus_out: &[u8], len: usize, on_full: F, on_closed: G, ) where F: FnOnce(), G: FnOnce(), { let packet = OutAudio::new(&AudioData::C2S { id: 0, codec: CodecType::OpusVoice, data: &opus_out[..len], }); match voice_out_tx.try_send(packet) { Ok(()) => { frames_sent.fetch_add(1, Ordering::Relaxed); } Err(mpsc::error::TrySendError::Full(_)) => on_full(), Err(mpsc::error::TrySendError::Closed(_)) => on_closed(), } }