Skip to main content

libobs_wrapper\encoders/
mod.rs

1//! This module contains video- and audio-encoders.
2//! In simple terms, you need a video and audio encoder, so a proper mp4
3//! file can get written to disk (with video and audio).
4//! If you are unsure which encoder to use, you might as well
5//! take a look at the [libobs-simple](https://crates.io/crates/libobs-simple) crate for
6//! a output that has all needed encoders attached already.
7
8use crate::{
9    context::ObsContext,
10    enums::{ObsEncoderType, OsEnumType},
11    impl_obs_drop, run_with_obs,
12    runtime::ObsRuntime,
13    unsafe_send::Sendable,
14    utils::{ObsDropGuard, ObsError, ENCODER_HIDE_FLAGS},
15};
16use std::{ffi::CStr, os::raw::c_char};
17
18pub mod audio;
19mod enums;
20mod traits;
21pub use traits::*;
22mod property_helper;
23pub use property_helper::*;
24pub mod video;
25pub use enums::*;
26
27/// Legacy encoder-builder discovery. Prefer [`ObsContext::capabilities`] plus the
28/// capability selectors, which expose codec/capability metadata and deterministic ranking.
29pub trait ObsContextEncoders {
30    #[deprecated = "Use ObsContext::capabilities()?.select_video_encoder()"]
31    fn best_video_encoder(&self) -> Result<ObsVideoEncoderBuilder, ObsError>;
32
33    #[deprecated = "Use ObsContext::capabilities()?.select_audio_encoder()"]
34    fn best_audio_encoder(&self) -> Result<ObsAudioEncoderBuilder, ObsError>;
35
36    #[deprecated = "Use ObsContext::capabilities()?.select_audio_encoder().matches()"]
37    fn available_audio_encoders(&self) -> Result<Vec<ObsAudioEncoderBuilder>, ObsError>;
38
39    #[deprecated = "Use ObsContext::capabilities()?.select_video_encoder().matches()"]
40    fn available_video_encoders(&self) -> Result<Vec<ObsVideoEncoderBuilder>, ObsError>;
41}
42
43fn get_encoders_raw(
44    encoder_type: ObsEncoderType,
45    runtime: &ObsRuntime,
46) -> Result<Vec<String>, ObsError> {
47    let type_primitive = encoder_type as OsEnumType;
48
49    run_with_obs!(runtime, move || {
50        let mut n = 0;
51        let mut encoders = Vec::new();
52
53        let mut ptr: *const c_char = unsafe {
54            // Safety: We are not dereferencing this pointer yet, it is first set by the method below
55            // and we are in the runtime
56            std::mem::zeroed()
57        };
58        while unsafe {
59            // Safety: We initialized the memory above and are modifying the pointer in the loop
60            libobs::obs_enum_encoder_types(n, &mut ptr)
61        } {
62            n += 1;
63            if ptr.is_null() {
64                continue;
65            }
66
67            let cstring = unsafe {
68                // Safety: We made sure that the pointer is not null, so it must be valid
69                CStr::from_ptr(ptr)
70            };
71            if let Ok(enc) = cstring.to_str() {
72                unsafe {
73                    // Safety: We know know that the pointer is valid, therefore we can use it again
74                    let is_hidden = libobs::obs_get_encoder_caps(ptr) & ENCODER_HIDE_FLAGS != 0;
75                    if is_hidden || libobs::obs_get_encoder_type(ptr) != type_primitive {
76                        continue;
77                    }
78                }
79
80                log::debug!("Found encoder: {}", enc);
81                encoders.push(enc.into());
82            }
83        }
84
85        encoders.sort_unstable();
86        encoders
87    })
88}
89
90impl ObsContextEncoders for ObsContext {
91    fn best_video_encoder(&self) -> Result<ObsVideoEncoderBuilder, ObsError> {
92        get_encoders_raw(ObsEncoderType::Video, self.runtime())?
93            .into_iter()
94            .next()
95            .map(|id| ObsVideoEncoderBuilder::new(self.clone(), &id))
96            .ok_or(ObsError::NoAvailableEncoders)
97    }
98
99    fn best_audio_encoder(&self) -> Result<ObsAudioEncoderBuilder, ObsError> {
100        get_encoders_raw(ObsEncoderType::Audio, self.runtime())?
101            .into_iter()
102            .next()
103            .map(|id| ObsAudioEncoderBuilder::new(self.clone(), &id))
104            .ok_or(ObsError::NoAvailableEncoders)
105    }
106
107    fn available_audio_encoders(&self) -> Result<Vec<ObsAudioEncoderBuilder>, ObsError> {
108        Ok(get_encoders_raw(ObsEncoderType::Audio, self.runtime())?
109            .into_iter()
110            .map(|x| ObsAudioEncoderBuilder::new(self.clone(), &x))
111            .collect::<Vec<_>>())
112    }
113
114    fn available_video_encoders(&self) -> Result<Vec<ObsVideoEncoderBuilder>, ObsError> {
115        Ok(get_encoders_raw(ObsEncoderType::Video, self.runtime())?
116            .into_iter()
117            .map(|x| ObsVideoEncoderBuilder::new(self.clone(), &x))
118            .collect::<Vec<_>>())
119    }
120}
121
122#[derive(Debug)]
123pub(super) struct _ObsEncoderDropGuard {
124    encoder: Sendable<*mut libobs::obs_encoder_t>,
125    runtime: ObsRuntime,
126}
127
128impl ObsDropGuard for _ObsEncoderDropGuard {}
129
130impl_obs_drop!(_ObsEncoderDropGuard, (encoder), move || unsafe {
131    // Safety: The pointer is valid because we are in the runtime and the guard is alive.
132    libobs::obs_encoder_release(encoder.0);
133});