use crate::proxy::proxy_call::media_peer::MediaPeer;
use audio_codec::{CodecType, Encoder};
use std::sync::Arc;
use tracing::debug;
pub struct MixerOutput {
pub id: String,
peer: Arc<dyn MediaPeer>,
codec: CodecType,
encoder: Option<Box<dyn Encoder + Send>>,
track_id: Option<String>,
gain: f32,
}
impl MixerOutput {
pub fn new(id: String, peer: Arc<dyn MediaPeer>, codec: CodecType) -> Self {
Self {
id,
peer,
codec,
encoder: None,
track_id: None,
gain: 1.0,
}
}
pub fn codec(&self) -> CodecType {
self.codec
}
pub fn with_track(mut self, track_id: String) -> Self {
self.track_id = Some(track_id);
self
}
pub fn with_gain(mut self, gain: f32) -> Self {
self.gain = gain.clamp(0.0, 2.0);
self
}
fn ensure_encoder(&mut self) {
if self.encoder.is_none() {
self.encoder = Some(audio_codec::create_encoder(self.codec));
}
}
pub fn peer(&self) -> Arc<dyn MediaPeer> {
self.peer.clone()
}
pub fn track_id(&self) -> Option<&str> {
self.track_id.as_deref()
}
pub async fn write_frame(&mut self, samples: &[i16]) -> Result<(), String> {
self.ensure_encoder();
let samples_with_gain: Vec<i16> = if (self.gain - 1.0).abs() > 0.01 {
samples
.iter()
.map(|&s| {
let scaled = s as f32 * self.gain;
scaled.clamp(i16::MIN as f32, i16::MAX as f32) as i16
})
.collect()
} else {
samples.to_vec()
};
if let Some(ref mut encoder) = self.encoder {
let encoded = encoder.encode(&samples_with_gain);
debug!(
"MixerOutput {}: encoded {} bytes from {} samples (gain: {})",
self.id,
encoded.len(),
samples_with_gain.len(),
self.gain
);
}
Ok(())
}
pub fn encode(&mut self, samples: &[i16]) -> Option<Vec<u8>> {
self.ensure_encoder();
self.encoder.as_mut().map(|encoder| encoder.encode(samples))
}
pub fn sample_rate(&self) -> u32 {
self.encoder
.as_ref()
.map(|e| e.sample_rate())
.unwrap_or(8000)
}
pub fn create_encoder(codec: CodecType) -> Box<dyn Encoder + Send> {
audio_codec::create_encoder(codec)
}
}
#[derive(Debug, Clone)]
pub struct MixerOutputConfig {
pub id: String,
pub peer_id: String,
pub codec: CodecType,
pub gain: f32,
pub inputs: Vec<String>,
}
impl MixerOutputConfig {
pub fn new(id: String, peer_id: String) -> Self {
Self {
id,
peer_id,
codec: CodecType::PCMU, gain: 1.0,
inputs: Vec::new(),
}
}
pub fn with_codec(mut self, codec: CodecType) -> Self {
self.codec = codec;
self
}
pub fn with_gain(mut self, gain: f32) -> Self {
self.gain = gain;
self
}
pub fn with_inputs(mut self, inputs: Vec<String>) -> Self {
self.inputs = inputs;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mixer_output_config() {
let config = MixerOutputConfig::new("output-1".to_string(), "peer-1".to_string())
.with_codec(CodecType::PCMU)
.with_gain(0.8)
.with_inputs(vec!["input-1".to_string(), "input-2".to_string()]);
assert_eq!(config.id, "output-1");
assert_eq!(config.peer_id, "peer-1");
assert_eq!(config.codec, CodecType::PCMU);
assert_eq!(config.gain, 0.8);
assert_eq!(config.inputs.len(), 2);
}
#[test]
fn test_gain_clamping() {
let output = MixerOutput::new(
"test".to_string(),
Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new()),
CodecType::PCMU,
);
assert!(output.gain >= 0.0 && output.gain <= 2.0);
}
#[test]
fn test_mixer_output_creation() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let output = MixerOutput::new("output-1".to_string(), peer.clone(), CodecType::PCMU);
assert_eq!(output.id, "output-1");
assert_eq!(output.codec(), CodecType::PCMU);
}
#[test]
fn test_mixer_output_with_track() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let output = MixerOutput::new("output-1".to_string(), peer, CodecType::PCMU)
.with_track("audio-0".to_string())
.with_gain(0.5);
assert_eq!(output.track_id(), Some("audio-0"));
assert_eq!(output.gain, 0.5);
}
#[test]
fn test_mixer_output_ensure_encoder() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let mut output = MixerOutput::new("output-1".to_string(), peer, CodecType::PCMU);
assert!(output.encoder.is_none());
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
output.write_frame(&[0i16; 160]).await.unwrap();
});
assert!(output.encoder.is_some());
}
#[test]
fn test_mixer_output_encode() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let mut output = MixerOutput::new("output-1".to_string(), peer, CodecType::PCMU);
let samples: Vec<i16> = (0..160).map(|i| i as i16).collect();
let encoded = output.encode(&samples);
assert!(encoded.is_some());
assert!(encoded.unwrap().len() < samples.len() * 2);
}
#[test]
fn test_mixer_output_sample_rate() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let output = MixerOutput::new("output-1".to_string(), peer, CodecType::PCMU);
assert_eq!(output.sample_rate(), 8000);
}
#[test]
fn test_mixer_output_peer_getter() {
let peer: Arc<dyn crate::proxy::proxy_call::media_peer::MediaPeer> =
Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let output = MixerOutput::new("output-1".to_string(), peer.clone(), CodecType::PCMU);
assert!(Arc::ptr_eq(&output.peer(), &peer));
}
#[test]
fn test_mixer_output_gain_application() {
let peer = Arc::new(crate::proxy::proxy_call::test_util::tests::MockMediaPeer::new());
let mut output =
MixerOutput::new("output-1".to_string(), peer, CodecType::PCMU).with_gain(2.0);
let samples: Vec<i16> = vec![1000i16; 160];
let encoded = output.encode(&samples);
assert!(encoded.is_some());
}
}