Files
chanora/crates/chanora_audio/src/opus_voice.rs
T

82 lines
2.6 KiB
Rust

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<OpusEncoder, AudioError> {
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<F, G>(
voice_out_tx: &mpsc::Sender<OutPacket>,
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(),
}
}