use shiguredo_webrtc::{
AudioCodecInfo, AudioCodecSpec, AudioCodecType, AudioDecoder, AudioEncoder,
AudioEncoderFactoryOptions, EnvironmentRef, SdpAudioFormatRef,
};
use nojson::{DisplayJson, JsonFormatter, JsonParseError, RawJsonValue};
use crate::codec_direction::CodecDirection;
#[derive(Debug, Clone, Eq)]
pub struct AudioCodecImplementation {
name: String,
description: String,
}
impl AudioCodecImplementation {
pub fn new(name: &'static str, description: &'static str) -> Self {
Self {
name: name.to_string(),
description: description.to_string(),
}
}
fn new_internal(name: String, description: String) -> Self {
Self { name, description }
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn description(&self) -> &str {
self.description.as_str()
}
}
impl PartialEq for AudioCodecImplementation {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
pub trait AudioCodecCapability: Send {
fn get_implementation(&self) -> AudioCodecImplementation;
fn get_supported_codec_specs(&self, direction: CodecDirection) -> Vec<AudioCodecSpec>;
fn is_supported(&self, direction: CodecDirection, codec_type: AudioCodecType) -> bool {
let Some(codec_name) = codec_type.as_str() else {
return false;
};
self.get_supported_codec_specs(direction)
.iter()
.any(|spec| spec.format().name().ok().as_deref() == Some(codec_name))
}
fn query(
&self,
direction: CodecDirection,
format: SdpAudioFormatRef<'_>,
) -> Option<AudioCodecInfo>;
#[expect(unused_variables)]
fn create_audio_encoder(
&self,
env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
options: &AudioEncoderFactoryOptions,
) -> Option<AudioEncoder> {
None
}
#[expect(unused_variables)]
fn create_audio_decoder(
&self,
env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
) -> Option<AudioDecoder> {
None
}
}
impl DisplayJson for AudioCodecImplementation {
fn fmt(&self, f: &mut JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("name", &self.name)?;
f.member("description", &self.description)
})
}
}
impl<'text, 'raw> TryFrom<RawJsonValue<'text, 'raw>> for AudioCodecImplementation {
type Error = JsonParseError;
fn try_from(value: RawJsonValue<'text, 'raw>) -> std::result::Result<Self, Self::Error> {
let name: String = value.to_member("name")?.required()?.try_into()?;
let description: String = value.to_member("description")?.required()?.try_into()?;
Ok(Self::new_internal(name, description))
}
}
pub(crate) fn find_audio_capability<'a>(
capabilities: &'a [Box<dyn AudioCodecCapability>],
implementation: &AudioCodecImplementation,
) -> Option<&'a dyn AudioCodecCapability> {
let implementation_name = implementation.name();
capabilities
.iter()
.map(|capability| capability.as_ref())
.find(|capability| capability.get_implementation().name() == implementation_name)
}
#[cfg(test)]
mod tests {
use super::*;
use nojson::Json;
use shiguredo_webrtc::{AudioCodecType, SdpAudioFormat};
use crate::testing::{TestAudioCodecCapability, TestingAudioCodecRecorders};
#[test]
fn audio_codec_implementation_round_trip() {
let value = AudioCodecImplementation::new("internal", "WebRTC built-in");
let json_text = Json(&value).to_string();
let parsed: Json<AudioCodecImplementation> =
json_text.parse().expect("JSON のパースに失敗しました");
assert_eq!(parsed.0, value);
}
#[test]
fn audio_trait_works_with_trait_object() {
let capability: Box<dyn AudioCodecCapability> = Box::new(TestAudioCodecCapability::new(
AudioCodecImplementation::new("test", "Test Codec"),
vec![AudioCodecType::Opus],
vec![AudioCodecType::Opus],
));
assert_eq!(capability.get_implementation().name(), "test");
assert!(capability.is_supported(CodecDirection::Encoder, AudioCodecType::Opus));
assert!(capability.is_supported(CodecDirection::Decoder, AudioCodecType::Opus));
let opus = SdpAudioFormat::new("opus", 48000, 2);
let env = shiguredo_webrtc::Environment::new();
let mut options = shiguredo_webrtc::AudioEncoderFactoryOptions::new();
options.set_payload_type(111);
assert!(
capability
.create_audio_encoder(env.as_ref(), opus.as_ref(), &options)
.is_some()
);
assert!(
capability
.create_audio_decoder(env.as_ref(), opus.as_ref())
.is_some()
);
}
#[test]
fn audio_encoder_create_forwards_codec_pair_id_in_options() {
let recorders = TestingAudioCodecRecorders::new();
let capability = TestAudioCodecCapability::new_with_recorders(
AudioCodecImplementation::new("test", "Test Codec"),
vec![AudioCodecType::Opus],
vec![AudioCodecType::Opus],
recorders.clone(),
);
let env = shiguredo_webrtc::Environment::new();
let opus = SdpAudioFormat::new("opus", 48000, 2);
let mut options = shiguredo_webrtc::AudioEncoderFactoryOptions::new();
options.set_payload_type(111);
let pair_id = shiguredo_webrtc::AudioCodecPairId::create();
options.set_codec_pair_id(Some(&pair_id));
assert!(
capability
.create_audio_encoder(env.as_ref(), opus.as_ref(), &options)
.is_some()
);
assert_eq!(
recorders.codec_pair_id(),
Some(pair_id.numeric_representation()),
"codec_pair_id が create_audio_encoder まで素通しされていません"
);
}
}