use std::collections::BTreeMap;
use std::sync::{Arc, Mutex};
use shiguredo_webrtc::{
AudioCodecInfo, AudioCodecSpec, AudioCodecType, AudioDecoder, AudioDecoderHandler,
AudioEncoder, AudioEncoderEncodedInfo, AudioEncoderFactoryOptions, AudioEncoderHandler,
AudioSpeechType, BufferRefMut, EnvironmentRef, RawBufferWriter, SdpAudioFormat,
SdpAudioFormatRef, SdpVideoFormat, SdpVideoFormatRef, VideoCodecType, VideoDecoder,
VideoDecoderHandler, VideoEncoder, VideoEncoderHandler,
};
use crate::audio_codec_capability::{AudioCodecCapability, AudioCodecImplementation};
use crate::codec_direction::CodecDirection;
use crate::video_codec_capability::{VideoCodecCapability, VideoCodecImplementation};
pub(crate) struct NoopVideoEncoder;
impl VideoEncoderHandler for NoopVideoEncoder {}
pub(crate) struct NoopVideoDecoder;
impl VideoDecoderHandler for NoopVideoDecoder {}
pub(crate) struct TestVideoCodecCapability {
implementation: VideoCodecImplementation,
encoder_formats: Vec<VideoCodecType>,
decoder_formats: Vec<VideoCodecType>,
resolves_sdp_format: bool,
}
impl TestVideoCodecCapability {
pub(crate) fn new(
implementation: VideoCodecImplementation,
encoder_formats: Vec<VideoCodecType>,
decoder_formats: Vec<VideoCodecType>,
) -> Self {
Self {
implementation,
encoder_formats,
decoder_formats,
resolves_sdp_format: true,
}
}
pub(crate) fn without_sdp_format_resolution(mut self) -> Self {
self.resolves_sdp_format = false;
self
}
fn formats(&self, direction: CodecDirection) -> &[VideoCodecType] {
match direction {
CodecDirection::Encoder => &self.encoder_formats,
CodecDirection::Decoder => &self.decoder_formats,
}
}
}
impl VideoCodecCapability for TestVideoCodecCapability {
fn get_implementation(&self) -> VideoCodecImplementation {
self.implementation.clone()
}
fn get_supported_formats(&self, direction: CodecDirection) -> Vec<SdpVideoFormat> {
self.formats(direction)
.iter()
.filter_map(|codec_type| codec_type.as_str().map(SdpVideoFormat::new))
.collect()
}
fn is_supported(&self, direction: CodecDirection, codec_type: VideoCodecType) -> bool {
self.formats(direction).contains(&codec_type)
}
fn resolve_sdp_format(
&self,
direction: CodecDirection,
format: SdpVideoFormatRef<'_>,
) -> Option<SdpVideoFormat> {
if !self.resolves_sdp_format {
return None;
}
let codec_type = format
.name()
.ok()
.and_then(|name| VideoCodecType::try_from(name.as_str()).ok())?;
if !self.is_supported(direction, codec_type) {
return None;
}
let codec_name = codec_type.as_str()?;
let mut resolved = SdpVideoFormat::new(codec_name);
if codec_type == VideoCodecType::H264 {
resolved.parameters_mut().set("packetization-mode", "1");
}
Some(resolved)
}
fn create_video_encoder(
&self,
_env: EnvironmentRef<'_>,
format: SdpVideoFormatRef<'_>,
) -> Option<VideoEncoder> {
let codec_type = format
.name()
.ok()
.and_then(|name| VideoCodecType::try_from(name.as_str()).ok())?;
if self.is_supported(CodecDirection::Encoder, codec_type) {
Some(VideoEncoder::new_with_handler(Box::new(NoopVideoEncoder)))
} else {
None
}
}
fn create_video_decoder(
&self,
_env: EnvironmentRef<'_>,
format: SdpVideoFormatRef<'_>,
) -> Option<VideoDecoder> {
let codec_type = format
.name()
.ok()
.and_then(|name| VideoCodecType::try_from(name.as_str()).ok())?;
if self.is_supported(CodecDirection::Decoder, codec_type) {
Some(VideoDecoder::new_with_handler(Box::new(NoopVideoDecoder)))
} else {
None
}
}
}
pub(crate) struct TestAudioEncoder {
payload_type: i32,
}
impl TestAudioEncoder {
pub(crate) fn with_payload_type(payload_type: i32) -> Self {
Self { payload_type }
}
}
impl AudioEncoderHandler for TestAudioEncoder {
fn sample_rate_hz(&mut self) -> i32 {
48000
}
fn num_channels(&mut self) -> usize {
2
}
fn num_10ms_frames_in_next_packet(&mut self) -> usize {
1
}
fn max_10ms_frames_in_a_packet(&mut self) -> usize {
1
}
fn get_target_bitrate(&mut self) -> i32 {
32000
}
fn encode(
&mut self,
_rtp_timestamp: u32,
_audio: &[i16],
encoded: &mut BufferRefMut<'_>,
) -> AudioEncoderEncodedInfo {
encoded.append_data(&[0x01, 0x02, 0x03]);
let mut info = AudioEncoderEncodedInfo::new();
info.set_encoded_bytes(encoded.size());
info.set_payload_type(self.payload_type);
info
}
fn reset(&mut self) {}
fn get_frame_length_range(&mut self) -> Option<(i64, i64)> {
None
}
}
pub(crate) struct TestAudioDecoder;
impl AudioDecoderHandler for TestAudioDecoder {
fn sample_rate_hz(&mut self) -> i32 {
48000
}
fn channels(&mut self) -> usize {
2
}
fn decode(
&mut self,
_encoded: &[u8],
_sample_rate_hz: i32,
decoded: &mut RawBufferWriter<'_, i16>,
) -> (i32, AudioSpeechType) {
unsafe {
decoded.write(&[0x1111i16; 160]);
}
(160, AudioSpeechType::Speech)
}
fn reset(&mut self) {}
}
fn test_audio_codec_info() -> AudioCodecInfo {
AudioCodecInfo::new(48000, 2, 32000, 6000, 510000)
}
fn collect_audio_format_parameters(format: SdpAudioFormatRef<'_>) -> BTreeMap<String, String> {
format.to_owned().parameters_mut().iter().collect()
}
#[derive(Clone)]
pub(crate) struct TestingAudioCodecRecorders {
codec_pair_id: Arc<Mutex<Option<u64>>>,
encoder_format_parameters: Arc<Mutex<Option<BTreeMap<String, String>>>>,
decoder_format_parameters: Arc<Mutex<Option<BTreeMap<String, String>>>>,
}
impl TestingAudioCodecRecorders {
pub(crate) fn new() -> Self {
Self {
codec_pair_id: Arc::new(Mutex::new(None)),
encoder_format_parameters: Arc::new(Mutex::new(None)),
decoder_format_parameters: Arc::new(Mutex::new(None)),
}
}
pub(crate) fn codec_pair_id(&self) -> Option<u64> {
*self
.codec_pair_id
.lock()
.expect("codec_pair_id は poison しないはず")
}
pub(crate) fn encoder_format_parameters(&self) -> BTreeMap<String, String> {
self.encoder_format_parameters
.lock()
.expect("encoder_format_parameters は poison しないはず")
.clone()
.expect("create_audio_encoder が呼ばれたはずです")
}
pub(crate) fn decoder_format_parameters(&self) -> BTreeMap<String, String> {
self.decoder_format_parameters
.lock()
.expect("decoder_format_parameters は poison しないはず")
.clone()
.expect("create_audio_decoder が呼ばれたはずです")
}
}
pub(crate) struct TestAudioCodecCapability {
implementation: AudioCodecImplementation,
encoder_formats: Vec<AudioCodecType>,
decoder_formats: Vec<AudioCodecType>,
recorders: TestingAudioCodecRecorders,
}
impl TestAudioCodecCapability {
pub(crate) fn new(
implementation: AudioCodecImplementation,
encoder_formats: Vec<AudioCodecType>,
decoder_formats: Vec<AudioCodecType>,
) -> Self {
Self::new_with_recorders(
implementation,
encoder_formats,
decoder_formats,
TestingAudioCodecRecorders::new(),
)
}
pub(crate) fn new_with_recorders(
implementation: AudioCodecImplementation,
encoder_formats: Vec<AudioCodecType>,
decoder_formats: Vec<AudioCodecType>,
recorders: TestingAudioCodecRecorders,
) -> Self {
Self {
implementation,
encoder_formats,
decoder_formats,
recorders,
}
}
fn formats(&self, direction: CodecDirection) -> &[AudioCodecType] {
match direction {
CodecDirection::Encoder => &self.encoder_formats,
CodecDirection::Decoder => &self.decoder_formats,
}
}
}
impl AudioCodecCapability for TestAudioCodecCapability {
fn get_implementation(&self) -> AudioCodecImplementation {
self.implementation.clone()
}
fn get_supported_codec_specs(&self, direction: CodecDirection) -> Vec<AudioCodecSpec> {
self.formats(direction)
.iter()
.filter_map(|codec_type| {
let name = codec_type.as_str()?;
Some(AudioCodecSpec::new(
SdpAudioFormat::new(name, 48000, 2),
test_audio_codec_info(),
))
})
.collect()
}
fn is_supported(&self, direction: CodecDirection, codec_type: AudioCodecType) -> bool {
self.formats(direction).contains(&codec_type)
}
fn query(
&self,
direction: CodecDirection,
format: SdpAudioFormatRef<'_>,
) -> Option<AudioCodecInfo> {
let codec_type = format
.name()
.ok()
.and_then(|name| AudioCodecType::try_from(name.as_str()).ok())?;
if self.is_supported(direction, codec_type) {
Some(test_audio_codec_info())
} else {
None
}
}
fn create_audio_encoder(
&self,
_env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
options: &AudioEncoderFactoryOptions,
) -> Option<AudioEncoder> {
let codec_type = format
.name()
.ok()
.and_then(|name| AudioCodecType::try_from(name.as_str()).ok())?;
if self.is_supported(CodecDirection::Encoder, codec_type) {
*self
.recorders
.codec_pair_id
.lock()
.expect("codec_pair_id は poison しないはず") = options
.codec_pair_id()
.map(|id| id.numeric_representation());
*self
.recorders
.encoder_format_parameters
.lock()
.expect("encoder_format_parameters は poison しないはず") =
Some(collect_audio_format_parameters(format));
Some(AudioEncoder::new_with_handler(Box::new(
TestAudioEncoder::with_payload_type(options.payload_type()),
)))
} else {
None
}
}
fn create_audio_decoder(
&self,
_env: EnvironmentRef<'_>,
format: SdpAudioFormatRef<'_>,
) -> Option<AudioDecoder> {
let codec_type = format
.name()
.ok()
.and_then(|name| AudioCodecType::try_from(name.as_str()).ok())?;
if self.is_supported(CodecDirection::Decoder, codec_type) {
*self
.recorders
.decoder_format_parameters
.lock()
.expect("decoder_format_parameters は poison しないはず") =
Some(collect_audio_format_parameters(format));
Some(AudioDecoder::new_with_handler(Box::new(TestAudioDecoder)))
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_methods_share_the_same_format_list() {
let capability = TestVideoCodecCapability::new(
VideoCodecImplementation::new("test", "Test Codec"),
vec![VideoCodecType::Vp9, VideoCodecType::H264],
Vec::new(),
);
assert!(
capability.is_supported(CodecDirection::Encoder, VideoCodecType::Vp9),
"公開フォーマットに含まれる VP9 は is_supported で true になるべきです"
);
let vp9 = SdpVideoFormat::new("VP9");
assert!(
capability
.resolve_sdp_format(CodecDirection::Encoder, vp9.as_ref())
.is_some(),
"公開フォーマットに含まれる VP9 は resolve_sdp_format で解決できるべきです"
);
assert!(
capability
.create_video_encoder(shiguredo_webrtc::Environment::new().as_ref(), vp9.as_ref())
.is_some(),
"公開フォーマットに含まれる VP9 はエンコーダーを生成できるべきです"
);
}
}