82 lines
2.6 KiB
Rust
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(),
|
|
}
|
|
}
|