use libobs_wrapper::{
context::ObsContext,
data::{
output::{ObsOutputRef, ObsOutputTrait},
ObsData, ObsDataSetters,
},
encoders::{ObsAudioEncoderType, ObsContextEncoders, ObsVideoEncoderType},
utils::{AudioEncoderInfo, ObsError, ObsPath, ObsString, OutputInfo, VideoEncoderInfo},
};
#[derive(Debug, Clone, Copy)]
pub enum X264Preset {
UltraFast,
SuperFast,
VeryFast,
Faster,
Fast,
Medium,
Slow,
Slower,
}
impl X264Preset {
pub fn as_str(&self) -> &'static str {
match self {
X264Preset::UltraFast => "ultrafast",
X264Preset::SuperFast => "superfast",
X264Preset::VeryFast => "veryfast",
X264Preset::Faster => "faster",
X264Preset::Fast => "fast",
X264Preset::Medium => "medium",
X264Preset::Slow => "slow",
X264Preset::Slower => "slower",
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum HardwarePreset {
Speed,
Balanced,
Quality,
}
impl HardwarePreset {
pub fn as_str(&self) -> &'static str {
match self {
HardwarePreset::Speed => "speed",
HardwarePreset::Balanced => "balanced",
HardwarePreset::Quality => "quality",
}
}
}
#[derive(Debug, Clone)]
pub enum VideoEncoder {
X264(X264Preset),
Hardware {
codec: HardwareCodec,
preset: HardwarePreset,
},
Custom(ObsVideoEncoderType),
}
#[derive(Debug, Clone, Copy)]
pub enum HardwareCodec {
H264,
HEVC,
AV1,
}
#[derive(Debug, Clone)]
pub enum AudioEncoder {
AAC,
Opus,
Custom(ObsAudioEncoderType),
}
#[derive(Debug, Clone, Copy, Default)]
pub enum OutputFormat {
FlashVideo,
MatroskaVideo,
Mpeg4,
QuickTime,
#[default]
HybridMP4,
HybridMov,
FragmentedMP4,
FragmentedMOV,
MpegTs,
}
#[derive(Debug)]
pub struct OutputSettings {
name: ObsString,
video_bitrate: u32,
audio_bitrate: u32,
video_encoder: VideoEncoder,
audio_encoder: AudioEncoder,
custom_encoder_settings: Option<String>,
path: ObsPath,
format: OutputFormat,
custom_muxer_settings: Option<String>,
}
impl OutputSettings {
pub fn with_video_bitrate(mut self, bitrate: u32) -> Self {
self.video_bitrate = bitrate;
self
}
pub fn with_audio_bitrate(mut self, bitrate: u32) -> Self {
self.audio_bitrate = bitrate;
self
}
pub fn with_x264_encoder(mut self, preset: X264Preset) -> Self {
self.video_encoder = VideoEncoder::X264(preset);
self
}
pub fn with_hardware_encoder(mut self, codec: HardwareCodec, preset: HardwarePreset) -> Self {
self.video_encoder = VideoEncoder::Hardware { codec, preset };
self
}
pub fn with_custom_video_encoder(mut self, encoder: ObsVideoEncoderType) -> Self {
self.video_encoder = VideoEncoder::Custom(encoder);
self
}
pub fn with_custom_settings<S: Into<String>>(mut self, settings: S) -> Self {
self.custom_encoder_settings = Some(settings.into());
self
}
pub fn with_path<P: Into<ObsPath>>(mut self, path: P) -> Self {
self.path = path.into();
self
}
pub fn with_format(mut self, format: OutputFormat) -> Self {
self.format = format;
self
}
pub fn with_custom_muxer_settings<S: Into<String>>(mut self, settings: S) -> Self {
self.custom_muxer_settings = Some(settings.into());
self
}
pub fn with_audio_encoder(mut self, encoder: AudioEncoder) -> Self {
self.audio_encoder = encoder;
self
}
}
#[derive(Debug)]
pub struct SimpleOutputBuilder {
settings: OutputSettings,
context: ObsContext,
}
pub trait ObsContextSimpleExt {
fn simple_output_builder<K: Into<ObsPath>, T: Into<ObsString>>(
&self,
name: T,
path: K,
) -> SimpleOutputBuilder;
}
impl ObsContextSimpleExt for ObsContext {
fn simple_output_builder<K: Into<ObsPath>, T: Into<ObsString>>(
&self,
name: T,
path: K,
) -> SimpleOutputBuilder {
SimpleOutputBuilder::new(self.clone(), name, path)
}
}
impl SimpleOutputBuilder {
pub fn new<K: Into<ObsPath>, T: Into<ObsString>>(
context: ObsContext,
name: T,
path: K,
) -> Self {
SimpleOutputBuilder {
settings: OutputSettings {
video_bitrate: 6000,
audio_bitrate: 160,
video_encoder: VideoEncoder::X264(X264Preset::VeryFast),
audio_encoder: AudioEncoder::AAC,
custom_encoder_settings: None,
path: path.into(),
format: OutputFormat::default(),
custom_muxer_settings: None,
name: name.into(),
},
context,
}
}
pub fn settings(mut self, settings: OutputSettings) -> Self {
self.settings = settings;
self
}
pub fn video_bitrate(mut self, bitrate: u32) -> Self {
self.settings.video_bitrate = bitrate;
self
}
pub fn audio_bitrate(mut self, bitrate: u32) -> Self {
self.settings.audio_bitrate = bitrate;
self
}
pub fn path<P: Into<ObsPath>>(mut self, path: P) -> Self {
self.settings.path = path.into();
self
}
pub fn format(mut self, format: OutputFormat) -> Self {
self.settings.format = format;
self
}
pub fn x264_encoder(mut self, preset: X264Preset) -> Self {
self.settings.video_encoder = VideoEncoder::X264(preset);
self
}
pub fn hardware_encoder(mut self, codec: HardwareCodec, preset: HardwarePreset) -> Self {
self.settings.video_encoder = VideoEncoder::Hardware { codec, preset };
self
}
pub fn build(mut self) -> Result<ObsOutputRef, ObsError> {
let output_id = match self.settings.format {
OutputFormat::HybridMP4 => "mp4_output",
OutputFormat::HybridMov => "mov_output",
_ => "ffmpeg_muxer",
};
let mut output_settings = self.context.data()?;
output_settings.set_string("path", self.settings.path.clone().build())?;
if let Some(ref muxer_settings) = self.settings.custom_muxer_settings {
output_settings.set_string("muxer_settings", muxer_settings.as_str())?;
}
let output_info = OutputInfo::new(
output_id,
self.settings.name.clone(),
Some(output_settings),
None,
);
let mut output = self.context.output(output_info)?;
let video_encoder_type = self.select_video_encoder_type(&self.settings.video_encoder)?;
let mut video_settings = self.context.data()?;
self.configure_video_encoder(&mut video_settings)?;
let video_encoder_info = VideoEncoderInfo::new(
video_encoder_type,
format!("{}_video_encoder", self.settings.name),
Some(video_settings),
None,
);
output.create_and_set_video_encoder(video_encoder_info)?;
let audio_encoder_type = match &self.settings.audio_encoder {
AudioEncoder::AAC => ObsAudioEncoderType::FFMPEG_AAC,
AudioEncoder::Opus => ObsAudioEncoderType::FFMPEG_OPUS,
AudioEncoder::Custom(encoder_type) => encoder_type.clone(),
};
log::trace!("Selected audio encoder: {:?}", audio_encoder_type);
let mut audio_settings = self.context.data()?;
audio_settings.set_string("rate_control", "CBR")?;
audio_settings.set_int("bitrate", self.settings.audio_bitrate as i64)?;
let audio_encoder_info = AudioEncoderInfo::new(
audio_encoder_type,
format!("{}_audio_encoder", self.settings.name),
Some(audio_settings),
None,
);
log::trace!("Creating audio encoder with info: {:?}", audio_encoder_info);
output.create_and_set_audio_encoder(audio_encoder_info, 0)?;
Ok(output)
}
fn select_video_encoder_type(
&self,
encoder: &VideoEncoder,
) -> Result<ObsVideoEncoderType, ObsError> {
match encoder {
VideoEncoder::X264(_) => Ok(ObsVideoEncoderType::OBS_X264),
VideoEncoder::Custom(t) => Ok(t.clone()),
VideoEncoder::Hardware { codec, .. } => {
let candidates = self.hardware_candidates(*codec);
let available = self
.context
.available_video_encoders()?
.into_iter()
.map(|b| b.get_encoder_id().clone())
.collect::<Vec<_>>();
for cand in candidates {
if available.iter().any(|a| a == &cand) {
return Ok(cand);
}
}
Ok(ObsVideoEncoderType::OBS_X264)
}
}
}
fn hardware_candidates(&self, codec: HardwareCodec) -> Vec<ObsVideoEncoderType> {
match codec {
HardwareCodec::H264 => vec![
ObsVideoEncoderType::OBS_NVENC_H264_TEX,
ObsVideoEncoderType::H264_TEXTURE_AMF,
ObsVideoEncoderType::OBS_QSV11_V2,
ObsVideoEncoderType::OBS_NVENC_H264_SOFT,
ObsVideoEncoderType::OBS_QSV11_SOFT_V2,
],
HardwareCodec::HEVC => vec![
ObsVideoEncoderType::OBS_NVENC_HEVC_TEX,
ObsVideoEncoderType::H265_TEXTURE_AMF,
ObsVideoEncoderType::OBS_QSV11_HEVC,
ObsVideoEncoderType::OBS_NVENC_HEVC_SOFT,
ObsVideoEncoderType::OBS_QSV11_HEVC_SOFT,
],
HardwareCodec::AV1 => vec![
ObsVideoEncoderType::OBS_NVENC_AV1_TEX,
ObsVideoEncoderType::AV1_TEXTURE_AMF,
ObsVideoEncoderType::OBS_QSV11_AV1,
ObsVideoEncoderType::OBS_NVENC_AV1_SOFT,
ObsVideoEncoderType::OBS_QSV11_AV1_SOFT,
],
}
}
fn get_encoder_preset(&self, encoder: &VideoEncoder) -> Option<&str> {
match encoder {
VideoEncoder::X264(preset) => Some(preset.as_str()),
VideoEncoder::Hardware { preset, .. } => Some(preset.as_str()),
VideoEncoder::Custom(_) => None,
}
}
fn configure_video_encoder(&self, settings: &mut ObsData) -> Result<(), ObsError> {
settings.set_string("rate_control", "CBR")?;
settings.set_int("bitrate", self.settings.video_bitrate as i64)?;
if let Some(preset) = self.get_encoder_preset(&self.settings.video_encoder) {
settings.set_string("preset", preset)?;
}
if let Some(ref custom) = self.settings.custom_encoder_settings {
settings.set_string("x264opts", custom.as_str())?;
}
Ok(())
}
}