use std::sync::{Arc, Mutex};
use shiguredo_webrtc::{
AudioDecoderFactory, AudioDeviceModule, AudioDeviceModuleAudioLayer, AudioEncoderFactory,
AudioOptions, AudioProcessingBuilder, AudioTrack, AudioTrackSource, ConnectionContext,
Environment, PeerConnectionFactory, PeerConnectionFactoryDependencies, RtcEventLogFactory,
Thread, VideoDecoderFactory, VideoEncoderFactory, VideoTrack, VideoTrackSource, random_string,
};
use crate::audio_codec::{SoraAudioDecoderFactory, SoraAudioEncoderFactory};
use crate::audio_codec_capability::AudioCodecCapability;
use crate::audio_codec_preference::{AudioCodecPreference, validate_audio_codec_preference};
use crate::audio_codecs::internal::InternalAudioCodecCapability;
use crate::error::Result;
use crate::video_codec::{SoraVideoDecoderFactory, SoraVideoEncoderFactory};
use crate::video_codec_capability::VideoCodecCapability;
use crate::video_codec_preference::{VideoCodecPreference, validate_video_codec_preference};
use crate::video_codecs::internal::InternalVideoCodecCapability;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use crate::video_codecs::internal_apple::InternalAppleVideoCodecCapability;
#[derive(Clone, Default)]
pub enum AdmConfig {
#[default]
NoAudioDevice,
UseBuiltIn,
UseExternal(shiguredo_webrtc::AudioDeviceModule),
}
pub struct SoraConnectionContextConfig {
pub adm_config: AdmConfig,
pub environment: Option<Environment>,
pub video_codec_preference: VideoCodecPreference,
pub video_codec_capabilities: Vec<Box<dyn VideoCodecCapability>>,
pub audio_codec_preference: AudioCodecPreference,
pub audio_codec_capabilities: Vec<Box<dyn AudioCodecCapability>>,
}
impl Default for SoraConnectionContextConfig {
fn default() -> Self {
let mut video_codec_preference = VideoCodecPreference::default();
let mut video_codec_capabilities = Vec::new();
let internal_capability: Box<dyn VideoCodecCapability> =
Box::new(InternalVideoCodecCapability::new());
video_codec_preference.merge(&VideoCodecPreference::new_from_capability(
internal_capability.as_ref(),
));
video_codec_capabilities.push(internal_capability);
#[cfg(any(target_os = "macos", target_os = "ios"))]
if let Some(internal_apple_capability) = InternalAppleVideoCodecCapability::new() {
let internal_apple_capability: Box<dyn VideoCodecCapability> =
Box::new(internal_apple_capability);
video_codec_preference.merge(&VideoCodecPreference::new_from_capability(
internal_apple_capability.as_ref(),
));
video_codec_capabilities.push(internal_apple_capability);
}
let mut audio_codec_preference = AudioCodecPreference::default();
let mut audio_codec_capabilities: Vec<Box<dyn AudioCodecCapability>> = Vec::new();
let internal_audio_capability: Box<dyn AudioCodecCapability> =
Box::new(InternalAudioCodecCapability::new());
audio_codec_preference.merge(&AudioCodecPreference::new_from_capability(
internal_audio_capability.as_ref(),
));
audio_codec_capabilities.push(internal_audio_capability);
Self {
adm_config: AdmConfig::default(),
environment: None,
video_codec_preference,
video_codec_capabilities,
audio_codec_preference,
audio_codec_capabilities,
}
}
}
pub struct SoraConnectionContext {
connection_context: ConnectionContext,
factory: PeerConnectionFactory,
_network: Thread,
_signaling: Thread,
}
impl SoraConnectionContext {
pub fn new() -> Result<Arc<Self>> {
Self::new_with_config(SoraConnectionContextConfig::default())
}
pub fn new_with_config(config: SoraConnectionContextConfig) -> Result<Arc<Self>> {
let SoraConnectionContextConfig {
adm_config,
environment,
video_codec_preference,
video_codec_capabilities,
audio_codec_preference,
audio_codec_capabilities,
} = config;
validate_video_codec_preference(&video_codec_preference, &video_codec_capabilities)?;
validate_audio_codec_preference(&audio_codec_preference, &audio_codec_capabilities)?;
let shared_video_codec_capabilities = Arc::new(Mutex::new(video_codec_capabilities));
let video_encoder_factory =
VideoEncoderFactory::new_with_handler(Box::new(SoraVideoEncoderFactory::new(
video_codec_preference.clone(),
shared_video_codec_capabilities.clone(),
)));
let video_decoder_factory = VideoDecoderFactory::new_with_handler(Box::new(
SoraVideoDecoderFactory::new(video_codec_preference, shared_video_codec_capabilities),
));
let shared_audio_codec_capabilities = Arc::new(Mutex::new(audio_codec_capabilities));
let audio_encoder_factory =
AudioEncoderFactory::new_with_handler(Box::new(SoraAudioEncoderFactory::new(
audio_codec_preference.clone(),
shared_audio_codec_capabilities.clone(),
)));
let audio_decoder_factory = AudioDecoderFactory::new_with_handler(Box::new(
SoraAudioDecoderFactory::new(audio_codec_preference, shared_audio_codec_capabilities),
));
let environment = environment.unwrap_or_else(Environment::new);
let mut network = Thread::new_with_socket_server();
let mut signaling = Thread::new();
network.start();
signaling.start();
let mut deps = PeerConnectionFactoryDependencies::new();
deps.set_network_thread(&network);
deps.set_worker_thread(&network);
deps.set_signaling_thread(&signaling);
deps.set_env(Some(environment.clone()));
let event_log = RtcEventLogFactory::new();
deps.set_event_log_factory(event_log);
match adm_config {
AdmConfig::NoAudioDevice => {
let adm = AudioDeviceModule::new(&environment, AudioDeviceModuleAudioLayer::Dummy)?;
deps.set_audio_device_module(&adm);
}
AdmConfig::UseBuiltIn => {
let adm = AudioDeviceModule::new(
&environment,
AudioDeviceModuleAudioLayer::PlatformDefault,
)?;
deps.set_audio_device_module(&adm);
}
AdmConfig::UseExternal(external_adm) => {
deps.set_audio_device_module(&external_adm);
}
};
deps.set_audio_encoder_factory(&audio_encoder_factory);
deps.set_audio_decoder_factory(&audio_decoder_factory);
deps.set_video_encoder_factory(video_encoder_factory);
deps.set_video_decoder_factory(video_decoder_factory);
let apb = AudioProcessingBuilder::new_builtin();
deps.set_audio_processing_builder(apb);
deps.enable_media();
let (factory, connection_context) =
PeerConnectionFactory::create_modular_with_context(deps)?;
Ok(Arc::new(Self {
factory,
connection_context,
_network: network,
_signaling: signaling,
}))
}
pub fn create_audio_source(&self) -> Result<AudioTrackSource> {
let options = AudioOptions::new();
Ok(self.factory.create_audio_source(&options)?)
}
pub fn create_audio_track(&self, source: &AudioTrackSource) -> Result<AudioTrack> {
let track_id = random_string(16);
Ok(self.factory.create_audio_track(source, &track_id)?)
}
pub fn create_video_track(&self, source: &VideoTrackSource) -> Result<VideoTrack> {
let track_id = random_string(16);
Ok(self.factory.create_video_track(source, &track_id)?)
}
pub(crate) fn factory(&self) -> &PeerConnectionFactory {
&self.factory
}
pub(crate) fn connection_context(&self) -> &ConnectionContext {
&self.connection_context
}
}
unsafe impl Send for SoraConnectionContext {}
unsafe impl Sync for SoraConnectionContext {}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec_direction::CodecDirection;
use shiguredo_webrtc::AudioCodecType;
#[test]
fn default_config_contains_internal_audio_capability() {
let config = SoraConnectionContextConfig::default();
let internal = config
.audio_codec_capabilities
.iter()
.find(|cap| cap.get_implementation().name() == "internal")
.expect("デフォルト構成に内部音声 capability が含まれる必要があります");
for direction in [CodecDirection::Encoder, CodecDirection::Decoder] {
if !internal.is_supported(direction, AudioCodecType::Opus) {
continue;
}
let preference = config
.audio_codec_preference
.find(direction, AudioCodecType::Opus)
.expect("preference エントリは必ず存在する");
assert_eq!(
preference.implementation().name(),
"internal",
"internal が {direction:?} Opus で使われなければなりません",
);
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
#[test]
fn default_config_prefers_internal_apple_for_supported_codecs() {
use shiguredo_webrtc::VideoCodecType;
let config = SoraConnectionContextConfig::default();
let Some(internal_apple_capability) = config
.video_codec_capabilities
.iter()
.find(|cap| cap.get_implementation().name() == "internal-apple")
else {
return;
};
let internal_apple_implementation = internal_apple_capability.get_implementation();
for codec_type in [
VideoCodecType::Vp8,
VideoCodecType::Vp9,
VideoCodecType::H264,
VideoCodecType::H265,
VideoCodecType::Av1,
] {
for direction in [CodecDirection::Encoder, CodecDirection::Decoder] {
if !internal_apple_capability.is_supported(direction, codec_type) {
continue;
}
let preference = config
.video_codec_preference
.find(direction, codec_type)
.expect("preference エントリは必ず存在する");
assert_eq!(
preference.implementation(),
&internal_apple_implementation,
"internal-apple が {direction:?} {codec_type:?} で優先されなければなりません",
);
}
}
}
}