use std::sync::{Arc, Mutex};
use shiguredo_webrtc::{
AudioCodecInfo, AudioCodecSpec, AudioCodecType, AudioDecoder, AudioDecoderFactoryHandler,
AudioEncoder, AudioEncoderFactoryHandler, AudioEncoderFactoryOptions, EnvironmentRef,
SdpAudioFormatRef,
};
use crate::audio_codec_capability::{AudioCodecCapability, find_audio_capability};
use crate::audio_codec_preference::AudioCodecPreference;
use crate::codec_direction::CodecDirection;
type AudioCodecCapabilities = Vec<Box<dyn AudioCodecCapability>>;
type SharedAudioCodecCapabilities = Arc<Mutex<AudioCodecCapabilities>>;
pub(crate) struct SoraAudioEncoderFactory {
preference: AudioCodecPreference,
capabilities: SharedAudioCodecCapabilities,
}
pub(crate) struct SoraAudioDecoderFactory {
preference: AudioCodecPreference,
capabilities: SharedAudioCodecCapabilities,
}
impl SoraAudioEncoderFactory {
pub(crate) fn new(
preference: AudioCodecPreference,
capabilities: SharedAudioCodecCapabilities,
) -> Self {
Self {
preference,
capabilities,
}
}
}
impl SoraAudioDecoderFactory {
pub(crate) fn new(
preference: AudioCodecPreference,
capabilities: SharedAudioCodecCapabilities,
) -> Self {
Self {
preference,
capabilities,
}
}
}
impl AudioEncoderFactoryHandler for SoraAudioEncoderFactory {
fn get_supported_encoders(&mut self) -> Vec<AudioCodecSpec> {
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
collect_audio_codec_specs(&self.preference, &capabilities, CodecDirection::Encoder)
}
fn query_audio_encoder(&mut self, format: SdpAudioFormatRef<'_>) -> Option<AudioCodecInfo> {
let format_name = format.name().ok()?;
let codec_type = AudioCodecType::try_from(format_name.as_str()).ok()?;
let preference = self.preference.find(CodecDirection::Encoder, codec_type)?;
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
let capability = find_audio_capability(&capabilities, preference.implementation())?;
capability.query(CodecDirection::Encoder, format)
}
fn create(
&mut self,
env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
options: &AudioEncoderFactoryOptions,
) -> Option<AudioEncoder> {
let format_name = format.name().ok()?;
let codec_type = AudioCodecType::try_from(format_name.as_str()).ok()?;
let preference = self.preference.find(CodecDirection::Encoder, codec_type)?;
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
let capability = find_audio_capability(&capabilities, preference.implementation())?;
capability.create_audio_encoder(env, format, options)
}
}
impl AudioDecoderFactoryHandler for SoraAudioDecoderFactory {
fn get_supported_decoders(&mut self) -> Vec<AudioCodecSpec> {
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
collect_audio_codec_specs(&self.preference, &capabilities, CodecDirection::Decoder)
}
fn is_supported_decoder(&mut self, format: SdpAudioFormatRef<'_>) -> bool {
let Ok(format_name) = format.name() else {
return false;
};
let Ok(codec_type) = AudioCodecType::try_from(format_name.as_str()) else {
return false;
};
let Some(preference) = self.preference.find(CodecDirection::Decoder, codec_type) else {
return false;
};
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
let Some(capability) = find_audio_capability(&capabilities, preference.implementation())
else {
return false;
};
capability.query(CodecDirection::Decoder, format).is_some()
}
fn create(
&mut self,
env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
) -> Option<AudioDecoder> {
let format_name = format.name().ok()?;
let codec_type = AudioCodecType::try_from(format_name.as_str()).ok()?;
let preference = self.preference.find(CodecDirection::Decoder, codec_type)?;
let capabilities = self
.capabilities
.lock()
.expect("capabilities should not be poisoned");
let capability = find_audio_capability(&capabilities, preference.implementation())?;
capability.create_audio_decoder(env, format)
}
}
fn collect_audio_codec_specs(
preference: &AudioCodecPreference,
capabilities: &[Box<dyn AudioCodecCapability>],
direction: CodecDirection,
) -> Vec<AudioCodecSpec> {
let mut specs = Vec::new();
for codec in preference.codecs() {
if codec.direction() != direction {
continue;
}
let Some(capability) = find_audio_capability(capabilities, codec.implementation()) else {
continue;
};
for spec in capability.get_supported_codec_specs(codec.direction()) {
let format = spec.format();
let format_codec_type = format
.name()
.ok()
.and_then(|name| AudioCodecType::try_from(name.as_str()).ok());
if format_codec_type != Some(codec.codec_type()) {
continue;
}
if specs
.iter()
.any(|existing: &AudioCodecSpec| existing.format().is_equal(format.as_ref()))
{
continue;
}
specs.push(AudioCodecSpec::new(format, spec.info()));
}
}
specs
}
#[cfg(test)]
mod tests {
use super::*;
use crate::audio_codec_capability::{AudioCodecCapability, AudioCodecImplementation};
use crate::audio_codec_preference::AudioPreferenceCodec;
use crate::codec_direction::CodecDirection;
use crate::testing::{TestAudioCodecCapability, TestingAudioCodecRecorders};
use shiguredo_webrtc::{AudioCodecType, AudioSpeechType, Buffer, Environment, SdpAudioFormat};
#[test]
fn encoder_factory_uses_preference_order() {
let preference = AudioCodecPreference::new(vec![AudioPreferenceCodec::new(
CodecDirection::Encoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("impl-a", "Implementation A"),
)]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> =
vec![Box::new(TestAudioCodecCapability::new(
AudioCodecImplementation::new("impl-a", "Implementation A"),
vec![AudioCodecType::Opus],
Vec::new(),
))];
let shared = Arc::new(Mutex::new(capabilities));
let mut factory = SoraAudioEncoderFactory::new(preference, shared);
let specs = AudioEncoderFactoryHandler::get_supported_encoders(&mut factory);
assert_eq!(specs.len(), 1);
assert_eq!(specs[0].format().name().expect("name 取得失敗"), "opus");
}
#[test]
fn encoder_factory_ignores_resolve_when_capability_formats_missing_codec() {
let preference = AudioCodecPreference::new(vec![AudioPreferenceCodec::new(
CodecDirection::Encoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("impl-a", "Implementation A"),
)]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> =
vec![Box::new(TestAudioCodecCapability::new(
AudioCodecImplementation::new("impl-a", "Implementation A"),
vec![AudioCodecType::Isac],
Vec::new(),
))];
let shared = Arc::new(Mutex::new(capabilities));
let mut factory = SoraAudioEncoderFactory::new(preference, shared);
let specs = AudioEncoderFactoryHandler::get_supported_encoders(&mut factory);
assert!(specs.is_empty());
}
#[test]
fn decoder_factory_create_requires_supported_codec_type() {
let preference = AudioCodecPreference::new(vec![AudioPreferenceCodec::new(
CodecDirection::Decoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("impl-a", "Implementation A"),
)]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> =
vec![Box::new(TestAudioCodecCapability::new(
AudioCodecImplementation::new("impl-a", "Implementation A"),
Vec::new(),
vec![AudioCodecType::Opus],
))];
let shared = Arc::new(Mutex::new(capabilities));
let mut factory = SoraAudioDecoderFactory::new(preference, shared);
let env = shiguredo_webrtc::Environment::new();
let opus = SdpAudioFormat::new("opus", 48000, 2);
assert!(
AudioDecoderFactoryHandler::create(&mut factory, env.as_ref(), opus.as_ref()).is_some()
);
let isac = SdpAudioFormat::new("ISAC", 16000, 1);
assert!(
AudioDecoderFactoryHandler::create(&mut factory, env.as_ref(), isac.as_ref()).is_none()
);
}
#[test]
fn encoder_decoder_round_trip_through_capability() {
let preference = AudioCodecPreference::new(vec![
AudioPreferenceCodec::new(
CodecDirection::Encoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("roundtrip", "Roundtrip Codec"),
),
AudioPreferenceCodec::new(
CodecDirection::Decoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("roundtrip", "Roundtrip Codec"),
),
]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> =
vec![Box::new(TestAudioCodecCapability::new(
AudioCodecImplementation::new("roundtrip", "Roundtrip Codec"),
vec![AudioCodecType::Opus],
vec![AudioCodecType::Opus],
))];
let shared = Arc::new(Mutex::new(capabilities));
let env = Environment::new();
let format = SdpAudioFormat::new("opus", 48000, 2);
let mut encoder_factory = SoraAudioEncoderFactory::new(preference.clone(), shared.clone());
let mut options = AudioEncoderFactoryOptions::new();
options.set_payload_type(120);
let mut encoder = AudioEncoderFactoryHandler::create(
&mut encoder_factory,
env.as_ref(),
format.as_ref(),
&options,
)
.expect("カスタムエンコーダーの生成に失敗しました");
let mut out = Buffer::new();
let info = encoder.encode(0, &[0i16; 960], &mut out);
assert_eq!(out.size(), 3, "エンコード結果が書き込まれていません");
assert_eq!(info.encoded_bytes(), 3);
assert_eq!(
info.payload_type(),
120,
"options.payload_type() が create_audio_encoder からエンコーダーへ伝播していません"
);
let mut decoder_factory = SoraAudioDecoderFactory::new(preference, shared);
let mut decoder =
AudioDecoderFactoryHandler::create(&mut decoder_factory, env.as_ref(), format.as_ref())
.expect("カスタムデコーダーの生成に失敗しました");
let mut decoded = [0x7FFFi16; 320];
let (samples, speech) = decoder.decode(out.data(), 48000, &mut decoded);
assert_eq!(samples, 160);
assert_eq!(speech, AudioSpeechType::Speech);
assert!(
decoded[..160].iter().all(|&v| v == 0x1111),
"デコード結果が書き込まれていません"
);
assert!(
decoded[160..].iter().all(|&v| v == 0x7FFF),
"未書き込み領域の番兵が破壊されました"
);
}
#[test]
fn encoder_create_forwards_negotiated_format_parameters() {
let recorders = TestingAudioCodecRecorders::new();
let capability = TestAudioCodecCapability::new_with_recorders(
AudioCodecImplementation::new("recording", "Recording Codec"),
vec![AudioCodecType::Opus],
Vec::new(),
recorders.clone(),
);
let preference = AudioCodecPreference::new(vec![AudioPreferenceCodec::new(
CodecDirection::Encoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("recording", "Recording Codec"),
)]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> = vec![Box::new(capability)];
let shared = Arc::new(Mutex::new(capabilities));
let mut factory = SoraAudioEncoderFactory::new(preference, shared);
let env = Environment::new();
let mut format = SdpAudioFormat::new("opus", 48000, 2);
format.parameters_mut().set("stereo", "1");
format.parameters_mut().set("ptime", "20");
let options = AudioEncoderFactoryOptions::new();
assert!(
AudioEncoderFactoryHandler::create(
&mut factory,
env.as_ref(),
format.as_ref(),
&options,
)
.is_some(),
"エンコーダーの生成に失敗しました"
);
let recorded = recorders.encoder_format_parameters();
assert_eq!(
recorded.get("stereo").map(String::as_str),
Some("1"),
"交渉で決まった stereo=1 が create_audio_encoder に届いていません"
);
assert_eq!(
recorded.get("ptime").map(String::as_str),
Some("20"),
"交渉で決まった ptime=20 が create_audio_encoder に届いていません"
);
}
#[test]
fn decoder_create_forwards_negotiated_format_parameters() {
let recorders = TestingAudioCodecRecorders::new();
let capability = TestAudioCodecCapability::new_with_recorders(
AudioCodecImplementation::new("recording", "Recording Codec"),
Vec::new(),
vec![AudioCodecType::Opus],
recorders.clone(),
);
let preference = AudioCodecPreference::new(vec![AudioPreferenceCodec::new(
CodecDirection::Decoder,
AudioCodecType::Opus,
AudioCodecImplementation::new("recording", "Recording Codec"),
)]);
let capabilities: Vec<Box<dyn AudioCodecCapability>> = vec![Box::new(capability)];
let shared = Arc::new(Mutex::new(capabilities));
let mut factory = SoraAudioDecoderFactory::new(preference, shared);
let env = Environment::new();
let mut format = SdpAudioFormat::new("opus", 48000, 2);
format.parameters_mut().set("stereo", "1");
assert!(
AudioDecoderFactoryHandler::create(&mut factory, env.as_ref(), format.as_ref())
.is_some(),
"デコーダーの生成に失敗しました"
);
let recorded = recorders.decoder_format_parameters();
assert_eq!(
recorded.get("stereo").map(String::as_str),
Some("1"),
"交渉で決まった stereo=1 が create_audio_decoder に届いていません"
);
}
}