Initialize project and implement decentralized voice chat client (PipeWire, Opus, Iroh, Iced)

This commit is contained in:
2026-05-27 05:18:56 -04:00
commit 1220d94e91
15 changed files with 9177 additions and 0 deletions
+25
View File
@@ -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;
+74
View File
@@ -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)
}
}