Skip to main content

libobs_wrapper\encoders/
audio.rs

1use libobs::audio_output;
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::{_ObsEncoderDropGuard, ObsEncoderTrait},
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            runtime.native_registry(),
90        );
91
92        let settings = {
93            let settings_ptr = run_with_obs!(runtime, (encoder), move || unsafe {
94                // Safety: We are using a smart pointer to ensure that the encoder pointer is valid
95                Sendable(libobs::obs_encoder_get_settings(encoder.get_ptr()))
96            })?;
97
98            ImmutableObsData::from_raw_pointer(settings_ptr, runtime.clone())
99        };
100
101        let hotkey_data = match hotkey_data {
102            Some(h) => h,
103            None => ImmutableObsData::new(&runtime)?,
104        };
105
106        Ok(Arc::new(Self {
107            encoder,
108            id,
109            name,
110            settings: Arc::new(RwLock::new(settings)),
111            hotkey_data: Arc::new(RwLock::new(hotkey_data)),
112            runtime,
113        }))
114    }
115
116    /// This is only needed once for global audio context
117    /// # Safety
118    /// You must ensure that the `handler` pointer is valid and lives as long as this function call.
119    pub unsafe fn set_audio_context(&self, handler: *mut audio_output) -> Result<(), ObsError> {
120        let handler = Sendable(handler);
121        let encoder_ptr = self.encoder.clone();
122
123        run_with_obs!(self.runtime, (handler, encoder_ptr), move || {
124            unsafe {
125                // Safety: Caller made sure that handler is valid and encoder_ptr is valid because of a SmartPointer
126                libobs::obs_encoder_set_audio(encoder_ptr.get_ptr(), handler.0)
127            }
128        })
129    }
130}
131
132impl ObsObjectTraitPrivate for ObsAudioEncoder {
133    fn __internal_replace_settings(&self, settings: ImmutableObsData) -> Result<(), ObsError> {
134        self.settings
135            .write()
136            .map_err(|_| {
137                ObsError::LockError(
138                    "Failed to acquire lock for replacing settings in the audio encoder".into(),
139                )
140            })
141            .map(|mut guard| {
142                *guard = settings;
143            })
144    }
145
146    fn __internal_replace_hotkey_data(
147        &self,
148        hotkey_data: ImmutableObsData,
149    ) -> Result<(), ObsError> {
150        self.hotkey_data
151            .write()
152            .map_err(|_| {
153                ObsError::LockError(
154                    "Failed to acquire lock for replacing hotkey data in the audio encoder".into(),
155                )
156            })
157            .map(|mut guard| {
158                *guard = hotkey_data;
159            })
160    }
161}
162
163impl ObsObjectTrait for ObsAudioEncoder {
164    type Native = *mut libobs::obs_encoder;
165
166    fn __native_handle(&self) -> SmartPointerSendable<Self::Native> {
167        self.encoder.clone()
168    }
169
170    fn runtime(&self) -> &ObsRuntime {
171        &self.runtime
172    }
173
174    fn settings(&self) -> Result<ImmutableObsData, ObsError> {
175        self.settings
176            .read()
177            .map_err(|_| {
178                ObsError::LockError("Failed to acquire read lock on audio encoder settings".into())
179            })
180            .map(|s| s.clone())
181    }
182
183    fn hotkey_data(&self) -> Result<ImmutableObsData, ObsError> {
184        self.hotkey_data
185            .read()
186            .map_err(|_| {
187                ObsError::LockError(
188                    "Failed to acquire read lock on audio encoder hotkey data".into(),
189                )
190            })
191            .map(|h| h.clone())
192    }
193
194    fn id(&self) -> ObsString {
195        self.id.clone()
196    }
197
198    fn name(&self) -> ObsString {
199        self.name.clone()
200    }
201
202    fn update_settings(&self, settings: crate::data::ObsData) -> Result<(), ObsError> {
203        if self.is_active()? {
204            return Err(ObsError::EncoderActive);
205        }
206
207        inner_fn_update_settings!(self, libobs::obs_encoder_update, settings)
208    }
209}
210
211impl ObsEncoderTrait for ObsAudioEncoder {}