libobs_wrapper\encoders/
audio.rs

1use libobs::{audio_output, obs_encoder};
2use std::{
3    ptr,
4    sync::{Arc, RwLock},
5};
6
7use crate::{
8    data::{
9        object::{inner_fn_update_settings, ObsObjectTrait, ObsObjectTraitPrivate},
10        ImmutableObsData, ObsDataPointers,
11    },
12    encoders::{ObsEncoderTrait, _ObsEncoderDropGuard},
13    run_with_obs,
14    runtime::ObsRuntime,
15    unsafe_send::{Sendable, SmartPointerSendable},
16    utils::{AudioEncoderInfo, ObsError, ObsString},
17};
18
19#[derive(Clone, Debug)]
20#[allow(dead_code)]
21/// Represents a audio encoder. If this struct is not referenced anywhere anymore,
22/// this Audio Encoder will get removed. Note: The output internally stores the ObsAudioEncoder, once it was
23/// added to the output
24pub struct ObsAudioEncoder {
25    pub(crate) id: ObsString,
26    pub(crate) name: ObsString,
27    pub(crate) settings: Arc<RwLock<ImmutableObsData>>,
28    pub(crate) hotkey_data: Arc<RwLock<ImmutableObsData>>,
29    pub(crate) runtime: ObsRuntime,
30    pub(crate) encoder: SmartPointerSendable<*mut libobs::obs_encoder_t>,
31}
32
33impl ObsAudioEncoder {
34    /// Info: the handler attribute is no longer needed and kept for compatibility. The `handler` parameter will be removed in a future release.
35    pub fn new_from_info(
36        info: AudioEncoderInfo,
37        mixer_idx: usize,
38        runtime: ObsRuntime,
39    ) -> Result<Arc<Self>, ObsError> {
40        let AudioEncoderInfo {
41            id,
42            name,
43            settings,
44            hotkey_data,
45        } = info;
46
47        let settings_ptr = settings.as_ref().map(|s| s.as_ptr());
48        let hotkey_data_ptr = hotkey_data.as_ref().map(|h| h.as_ptr());
49
50        let encoder = run_with_obs!(
51            runtime,
52            (id, name, settings_ptr, hotkey_data_ptr),
53            move || {
54                let settings_ptr_raw = match settings_ptr {
55                    Some(s) => s.get_ptr(),
56                    None => ptr::null_mut(),
57                };
58
59                let hotkey_data_ptr_raw = match hotkey_data_ptr {
60                    Some(h) => h.get_ptr(),
61                    None => ptr::null_mut(),
62                };
63
64                let ptr = unsafe {
65                    // Safety: All pointers are in the current scope and therefore valid.
66                    libobs::obs_audio_encoder_create(
67                        id.as_ptr().0,
68                        name.as_ptr().0,
69                        settings_ptr_raw,
70                        mixer_idx,
71                        hotkey_data_ptr_raw,
72                    )
73                };
74
75                if ptr.is_null() {
76                    Err(ObsError::NullPointer(None))
77                } else {
78                    Ok(Sendable(ptr))
79                }
80            }
81        )??;
82
83        let encoder = SmartPointerSendable::new(
84            encoder.0,
85            Arc::new(_ObsEncoderDropGuard {
86                encoder,
87                runtime: runtime.clone(),
88            }),
89        );
90
91        let settings = {
92            let settings_ptr = run_with_obs!(runtime, (encoder), move || unsafe {
93                // Safety: We are using a smart pointer to ensure that the encoder pointer is valid
94                Sendable(libobs::obs_encoder_get_settings(encoder.get_ptr()))
95            })?;
96
97            ImmutableObsData::from_raw_pointer(settings_ptr, runtime.clone())
98        };
99
100        let hotkey_data = match hotkey_data {
101            Some(h) => h,
102            None => ImmutableObsData::new(&runtime)?,
103        };
104
105        Ok(Arc::new(Self {
106            encoder,
107            id,
108            name,
109            settings: Arc::new(RwLock::new(settings)),
110            hotkey_data: Arc::new(RwLock::new(hotkey_data)),
111            runtime,
112        }))
113    }
114
115    /// This is only needed once for global audio context
116    /// # Safety
117    /// You must ensure that the `handler` pointer is valid and lives as long as this function call.
118    pub unsafe fn set_audio_context(
119        &mut self,
120        handler: Sendable<*mut audio_output>,
121    ) -> Result<(), ObsError> {
122        let encoder_ptr = self.encoder.clone();
123
124        run_with_obs!(self.runtime, (handler, encoder_ptr), move || {
125            unsafe {
126                // Safety: Caller made sure that handler is valid and encoder_ptr is valid because of a SmartPointer
127                libobs::obs_encoder_set_audio(encoder_ptr.get_ptr(), handler.0)
128            }
129        })
130    }
131}
132
133impl ObsObjectTraitPrivate for ObsAudioEncoder {
134    fn __internal_replace_settings(&self, settings: ImmutableObsData) -> Result<(), ObsError> {
135        self.settings
136            .write()
137            .map_err(|_| {
138                ObsError::LockError(
139                    "Failed to acquire lock for replacing settings in the audio encoder".into(),
140                )
141            })
142            .map(|mut guard| {
143                *guard = settings;
144            })
145    }
146
147    fn __internal_replace_hotkey_data(
148        &self,
149        hotkey_data: ImmutableObsData,
150    ) -> Result<(), ObsError> {
151        self.hotkey_data
152            .write()
153            .map_err(|_| {
154                ObsError::LockError(
155                    "Failed to acquire lock for replacing hotkey data in the audio encoder".into(),
156                )
157            })
158            .map(|mut guard| {
159                *guard = hotkey_data;
160            })
161    }
162}
163
164impl ObsObjectTrait<*mut libobs::obs_encoder> for ObsAudioEncoder {
165    fn runtime(&self) -> &ObsRuntime {
166        &self.runtime
167    }
168
169    fn settings(&self) -> Result<ImmutableObsData, ObsError> {
170        self.settings
171            .read()
172            .map_err(|_| {
173                ObsError::LockError("Failed to acquire read lock on audio encoder settings".into())
174            })
175            .map(|s| s.clone())
176    }
177
178    fn hotkey_data(&self) -> Result<ImmutableObsData, ObsError> {
179        self.hotkey_data
180            .read()
181            .map_err(|_| {
182                ObsError::LockError(
183                    "Failed to acquire read lock on audio encoder hotkey data".into(),
184                )
185            })
186            .map(|h| h.clone())
187    }
188
189    fn id(&self) -> ObsString {
190        self.id.clone()
191    }
192
193    fn name(&self) -> ObsString {
194        self.name.clone()
195    }
196
197    fn update_settings(&self, settings: crate::data::ObsData) -> Result<(), ObsError> {
198        if self.is_active()? {
199            return Err(ObsError::EncoderActive);
200        }
201
202        inner_fn_update_settings!(self, libobs::obs_encoder_update, settings)
203    }
204
205    fn as_ptr(&self) -> SmartPointerSendable<*mut obs_encoder> {
206        self.encoder.clone()
207    }
208}
209
210impl ObsEncoderTrait for ObsAudioEncoder {}