use ff_format::{AudioCodec, VideoCodec};
use crate::error::StreamError;
use crate::srt_output_inner::SrtInner;
pub struct SrtOutput {
url: String,
video_width: Option<u32>,
video_height: Option<u32>,
fps: Option<f64>,
sample_rate: u32,
channels: u32,
video_codec: VideoCodec,
audio_codec: AudioCodec,
video_bitrate: u64,
audio_bitrate: u64,
inner: Option<SrtInner>,
finished: bool,
}
impl SrtOutput {
#[must_use]
pub fn new(url: &str) -> Self {
Self {
url: url.to_owned(),
video_width: None,
video_height: None,
fps: None,
sample_rate: 44100,
channels: 2,
video_codec: VideoCodec::H264,
audio_codec: AudioCodec::Aac,
video_bitrate: 4_000_000,
audio_bitrate: 128_000,
inner: None,
finished: false,
}
}
pub fn build(mut self) -> Result<Self, StreamError> {
if !ff_sys::avformat::srt_available() {
return Err(StreamError::ProtocolUnavailable {
reason: "FFmpeg was built without libsrt; recompile FFmpeg with --enable-libsrt"
.into(),
});
}
if !self.url.starts_with("srt://") {
return Err(StreamError::InvalidConfig {
reason: "SrtOutput URL must start with srt://".into(),
});
}
let (Some(width), Some(height), Some(fps)) =
(self.video_width, self.video_height, self.fps)
else {
return Err(StreamError::InvalidConfig {
reason: "video parameters not set; call .video(width, height, fps) before .build()"
.into(),
});
};
if self.video_codec != VideoCodec::H264 {
return Err(StreamError::UnsupportedCodec {
codec: format!("{:?}", self.video_codec),
reason: "SRT/MPEG-TS output requires H.264 video".into(),
});
}
if self.audio_codec != AudioCodec::Aac {
return Err(StreamError::UnsupportedCodec {
codec: format!("{:?}", self.audio_codec),
reason: "SRT/MPEG-TS output requires AAC audio".into(),
});
}
#[allow(clippy::cast_possible_truncation)]
let fps_int = fps.round().max(1.0) as i32;
let inner = SrtInner::open(
&self.url,
width.cast_signed(),
height.cast_signed(),
fps_int,
self.video_bitrate,
self.sample_rate.cast_signed(),
self.channels.cast_signed(),
self.audio_bitrate.cast_signed(),
)?;
self.inner = Some(inner);
Ok(self)
}
}
impl_live_stream_setters!(SrtOutput, required_audio);
impl_frame_push_stream_output!(SrtOutput);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_without_srt_scheme_should_return_invalid_config() {
if !ff_sys::avformat::srt_available() {
println!("Skipping: libsrt not available in linked FFmpeg");
return;
}
let result = SrtOutput::new("rtmp://example.com/live")
.video(1280, 720, 30.0)
.build();
assert!(matches!(result, Err(StreamError::InvalidConfig { .. })));
}
#[test]
fn build_without_video_should_return_invalid_config() {
if !ff_sys::avformat::srt_available() {
println!("Skipping: libsrt not available in linked FFmpeg");
return;
}
let result = SrtOutput::new("srt://127.0.0.1:9000").build();
assert!(matches!(result, Err(StreamError::InvalidConfig { .. })));
}
#[test]
fn build_with_non_h264_video_codec_should_return_unsupported_codec() {
if !ff_sys::avformat::srt_available() {
println!("Skipping: libsrt not available in linked FFmpeg");
return;
}
let result = SrtOutput::new("srt://127.0.0.1:9000")
.video(1280, 720, 30.0)
.video_codec(VideoCodec::Vp9)
.build();
assert!(matches!(result, Err(StreamError::UnsupportedCodec { .. })));
}
#[test]
fn build_with_non_aac_audio_codec_should_return_unsupported_codec() {
if !ff_sys::avformat::srt_available() {
println!("Skipping: libsrt not available in linked FFmpeg");
return;
}
let result = SrtOutput::new("srt://127.0.0.1:9000")
.video(1280, 720, 30.0)
.audio_codec(AudioCodec::Mp3)
.build();
assert!(matches!(result, Err(StreamError::UnsupportedCodec { .. })));
}
#[test]
fn build_without_libsrt_should_return_protocol_unavailable() {
if ff_sys::avformat::srt_available() {
println!("Skipping: libsrt is available; cannot test ProtocolUnavailable path");
return;
}
let result = SrtOutput::new("srt://127.0.0.1:9000")
.video(1280, 720, 30.0)
.build();
assert!(matches!(
result,
Err(StreamError::ProtocolUnavailable { .. })
));
}
#[test]
fn video_bitrate_default_should_be_four_megabits() {
let out = SrtOutput::new("srt://127.0.0.1:9000");
assert_eq!(out.video_bitrate, 4_000_000);
}
#[test]
fn audio_defaults_should_be_44100hz_stereo() {
let out = SrtOutput::new("srt://127.0.0.1:9000");
assert_eq!(out.sample_rate, 44100);
assert_eq!(out.channels, 2);
}
}