feat(voice): add iOS VAD runtime support
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user