Initialize project and implement decentralized voice chat client (PipeWire, Opus, Iroh, Iced)
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum CodecError {
|
||||
#[error("Failed to initialize codec: {0}")]
|
||||
Init(String),
|
||||
#[error("Encoding failed: {0}")]
|
||||
Encode(String),
|
||||
#[error("Decoding failed: {0}")]
|
||||
Decode(String),
|
||||
}
|
||||
|
||||
pub trait AudioEncoder: Send {
|
||||
/// Encodes raw PCM samples into compressed bytes.
|
||||
fn encode(&mut self, pcm: &[i16]) -> Result<Vec<u8>, CodecError>;
|
||||
}
|
||||
|
||||
pub trait AudioDecoder: Send {
|
||||
/// Decodes compressed bytes back into raw PCM samples.
|
||||
/// If `compressed` is `None` (or `Some(&[])`), it indicates packet loss,
|
||||
/// enabling the decoder to perform packet loss concealment (PLC).
|
||||
fn decode(&mut self, compressed: Option<&[u8]>) -> Result<Vec<i16>, CodecError>;
|
||||
}
|
||||
|
||||
pub mod opus_impl;
|
||||
@@ -0,0 +1,74 @@
|
||||
use crate::codec::{AudioEncoder, AudioDecoder, CodecError};
|
||||
use opus::{Encoder, Decoder, Application, Channels};
|
||||
|
||||
pub struct OpusEncoder {
|
||||
encoder: Encoder,
|
||||
}
|
||||
|
||||
impl OpusEncoder {
|
||||
/// Creates a new Opus encoder.
|
||||
/// Standard voice parameters: sample_rate = 48000, channels = Channels::Mono, application = Application::Voip
|
||||
pub fn new(sample_rate: u32, channels: Channels, application: Application) -> Result<Self, CodecError> {
|
||||
let encoder = Encoder::new(sample_rate, channels, application)
|
||||
.map_err(|e| CodecError::Init(format!("Failed to create Opus encoder: {}", e)))?;
|
||||
Ok(Self { encoder })
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioEncoder for OpusEncoder {
|
||||
fn encode(&mut self, pcm: &[i16]) -> Result<Vec<u8>, CodecError> {
|
||||
// We allocate a buffer for the compressed output.
|
||||
// A maximum packet size of 4000 bytes is more than enough for a single voice frame.
|
||||
let mut compressed = vec![0u8; 4000];
|
||||
let len = self.encoder.encode(pcm, &mut compressed)
|
||||
.map_err(|e| CodecError::Encode(format!("Opus encoding failed: {}", e)))?;
|
||||
|
||||
compressed.truncate(len);
|
||||
Ok(compressed)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OpusDecoder {
|
||||
decoder: Decoder,
|
||||
channels: Channels,
|
||||
}
|
||||
|
||||
impl OpusDecoder {
|
||||
/// Creates a new Opus decoder.
|
||||
/// Standard voice parameters: sample_rate = 48000, channels = Channels::Mono
|
||||
pub fn new(sample_rate: u32, channels: Channels) -> Result<Self, CodecError> {
|
||||
let decoder = Decoder::new(sample_rate, channels)
|
||||
.map_err(|e| CodecError::Init(format!("Failed to create Opus decoder: {}", e)))?;
|
||||
Ok(Self { decoder, channels })
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioDecoder for OpusDecoder {
|
||||
fn decode(&mut self, compressed: Option<&[u8]>) -> Result<Vec<i16>, CodecError> {
|
||||
// Maximum Opus frame size is 120ms. At 48kHz, this is 5760 samples per channel.
|
||||
let channels_count = match self.channels {
|
||||
Channels::Mono => 1,
|
||||
Channels::Stereo => 2,
|
||||
};
|
||||
let max_samples = 5760 * channels_count;
|
||||
let mut pcm = vec![0i16; max_samples];
|
||||
|
||||
let decoded_samples_per_channel = match compressed {
|
||||
Some(data) if !data.is_empty() => {
|
||||
// Normal decode
|
||||
self.decoder.decode(data, &mut pcm, false)
|
||||
.map_err(|e| CodecError::Decode(format!("Opus decoding failed: {}", e)))?
|
||||
}
|
||||
_ => {
|
||||
// Packet Loss Concealment (PLC)
|
||||
// In opus-rs, passing an empty slice triggers PLC.
|
||||
self.decoder.decode(&[], &mut pcm, false)
|
||||
.map_err(|e| CodecError::Decode(format!("Opus PLC decoding failed: {}", e)))?
|
||||
}
|
||||
};
|
||||
|
||||
let total_samples = decoded_samples_per_channel * channels_count;
|
||||
pcm.truncate(total_samples);
|
||||
Ok(pcm)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user