Skip to main content

speechd_module_sys/
audio.rs

1//! Native audio playback functions
2//!
3//! These functions are only available if you compile and link against
4//! [`spd_audio.c`](https://github.com/brailcom/speechd/blob/master/src/common/spd_audio.c)
5//! from the speech-dispatcher distribution, otherwise using any of the defined
6//! functions will result in linking errors. Most speech-dispatcher modules
7//! should be OK with only supporting server audio and hence do not need these
8//! functions.
9
10use std::os::raw;
11#[cfg(doc)]
12use std::ptr;
13
14
15unsafe fn cstr_eq(a: *const raw::c_char, b: *const raw::c_char) -> bool {
16	if a.is_null() && b.is_null() {
17		true
18	} else if a.is_null() != b.is_null() {
19		false
20	} else {
21		unsafe { libc::strcmp(a, b) == 0 }
22	}
23}
24
25
26#[repr(C)]
27#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
28pub enum Format {
29	LE = 0,
30	BE = 1,
31}
32
33#[expect(clippy::derivable_impls, reason = "Does not take cfg() into account")]
34impl Default for Format {
35	fn default() -> Self {
36		#[cfg(target_endian = "little")]
37		{ Self::LE }
38		#[cfg(target_endian = "big")]
39		{ Self::BE }
40	}
41}
42
43
44#[repr(C)]
45#[derive(Debug)]
46pub struct Track {
47	pub bits: raw::c_int,
48	pub num_channels: raw::c_int,
49	pub sample_rate: raw::c_int,
50	pub num_samples: raw::c_int,
51	pub samples: *mut raw::c_short,
52}
53
54
55#[repr(C)]
56#[derive(Debug, Default, PartialEq, Eq)]
57pub struct ID {
58	pub volume: raw::c_int,
59	pub format: Format,
60	pub callbacks: *const Plugin,
61	pub private_data: *mut raw::c_void,
62	pub working: raw::c_int,
63}
64
65#[repr(C)]
66#[derive(Debug, Default, Eq)]
67#[expect(clippy::partial_pub_fields, reason="Reserved field should never be accessed but is used in FFI")]
68/// Server settable audio output parameters
69pub struct Params {
70	pub oss_device: *const raw::c_char,
71	pub alsa_device: *const raw::c_char,
72	pub nas_server: *const raw::c_char,
73	pub pulse_device: *const raw::c_char,
74	pub pulse_min_length: *const raw::c_char,
75	reserved: [*const raw::c_char; 5],
76}
77
78impl Params {
79	pub const FIELDS: [&'static str; 5] = [
80		"audio_oss_device",
81		"audio_alsa_device",
82		"audio_nas_server",
83		"audio_pulse_device",
84		"audio_pulse_min_length",
85	];
86
87	/// Helper method to update param struct entries by name
88	///
89	/// # Safety
90	///
91	/// Previous value at the field potentially replaced must either be
92	/// [`ptr::null()`](ptr::null) or a pointer allocated by
93	/// [`free`](libc::free). In other words don’t ever store invalid or
94	/// non-[`malloc`](libc::malloc)’d pointers in the struct.
95	pub unsafe fn set_by_name(&mut self, name: &str, value: *const raw::c_char) -> bool {
96		unsafe fn set_field(field: &mut *const raw::c_char, value: *const raw::c_char) -> bool {
97			unsafe { libc::free(*field as *mut _) };  // `free` must ignore calls with `nullptr`
98			*field = value;  // Set new value
99			true  // Expected outer function return value
100		}
101		
102		match name {
103			"audio_oss_device" => unsafe { set_field(&mut self.oss_device, value) },
104			"audio_alsa_device" => unsafe { set_field(&mut self.alsa_device, value) },
105			"audio_nas_server" => unsafe {  set_field(&mut self.nas_server, value) },
106			"audio_pulse_device" => unsafe { set_field(&mut self.pulse_device, value) },
107			"audio_pulse_min_length" => unsafe { set_field(&mut self.pulse_min_length, value) },
108
109			_ => false
110		}
111	}
112
113	
114	/// Helper method to retrieve param struct entries by name
115	#[must_use]
116	pub fn get_by_name(&self, name: &str) -> Option<*const raw::c_char> {
117		match name {
118			"audio_oss_device" => Some(self.oss_device),
119			"audio_alsa_device" => Some(self.alsa_device),
120			"audio_nas_server" => Some(self.nas_server),
121			"audio_pulse_device" => Some(self.pulse_device),
122			"audio_pulse_min_length" => Some(self.pulse_min_length),
123			_ => None,
124		}
125	}
126}
127
128impl PartialEq for Params {
129	fn eq(&self, other: &Self) -> bool {
130		(unsafe { cstr_eq(self.oss_device, other.oss_device) }) &&
131		(unsafe { cstr_eq(self.alsa_device, other.alsa_device) }) &&
132		(unsafe { cstr_eq(self.nas_server, other.nas_server) }) &&
133		(unsafe { cstr_eq(self.pulse_device, other.pulse_device) }) &&
134		(unsafe { cstr_eq(self.pulse_min_length, other.pulse_min_length) })
135	}
136}
137
138
139/// These callbacks are called from a single thread, except `stop` which can be
140/// called from another thread
141/// `open` is called first, which returns an [`ID`], which is later passed to
142/// to all the other callbacks, so plugins can extend the [`ID`] structure as
143/// they see fit to include their own data.
144///
145/// If `feed_sync_overlap` is available, `begin` is called first to set the
146/// format, then several calls to `feed_sync_overlap` are made to feed audio
147/// progressively with overlapping to avoid any underruns. Eventually, `end`
148/// is called.
149///
150/// If `feed_sync_overlap` is not available, `begin` is called first to set
151/// the format, then several calls to `feed_sync` are made to feed audio
152/// progressively, but we don’t overlap so we may have underruns. Eventually,
153/// `end` is again called.
154///
155/// If neither `feed_sync_overlap` nor `feed_sync` are available, `play` is
156/// called with the whole piece. This doesn’t allow pipelining, thus risking
157/// underruns.
158///
159/// When stop is called, the playback currently happening should be stopped as
160/// soon as possible.
161#[repr(C)]
162#[derive(Debug)]
163pub struct Plugin {
164	pub name: *const raw::c_char,
165	
166	pub open: unsafe extern "C" fn(params: *const Params) -> *mut ID,
167
168	/// Play audio track synchonously
169	pub play: Option<unsafe extern "C" fn(id: *mut ID, track: Track) -> raw::c_int>,
170
171	/// Stop audio track immediately
172	pub stop: Option<unsafe extern "C" fn(id: *mut ID) -> raw::c_int>,
173
174	/// Called before plugin terminates
175	pub close: Option<unsafe extern "C" fn(id: *mut ID) -> raw::c_int>,
176
177	
178	pub set_volume: Option<unsafe extern "C" fn(id: *mut ID, volume: raw::c_int) -> raw::c_int>,
179	
180	pub set_loglevel: unsafe extern "C" fn(level: raw::c_int),
181
182	/// Return the CLI command that `sd_generic` modules can use to play sound
183	pub get_playcmd: unsafe extern "C" fn() -> *const raw::c_char,
184
185
186	// Optional
187
188	/// Configure audio for playing this track
189	///
190	/// Only `bits`, `num_channels`, and `sample_rate` should be read.
191	pub begin: Option<unsafe extern "C" fn(id: *mut ID, track: Track) -> raw::c_int>,
192
193	/// Feed track to audio and wait for playback completion
194	///
195	/// `bits`, `num_channels`, and `sample_rate` will be unchanged from
196	/// the [`begin`](Plugin::begin) call.
197	pub feed_sync: Option<unsafe extern "C" fn(id: *mut ID, track: Track) -> raw::c_int>,
198
199	/// Feed track to audio and wait for almost complete playback completion
200	///
201	/// There should be enough playback left in audio buffers for the caller to
202	/// have the time to report a mark and submit the subsequent audio pieces,
203	/// without risking an underrun.
204	///
205	/// `bits`, `num_channels`, and `sample_rate` will be unchanged from
206	/// the [`begin`](Plugin::begin) call.
207	pub feed_sync_overlap: Option<unsafe extern "C" fn(id: *mut ID, track: Track) -> raw::c_int>,
208
209	/// Clean up audio after playback
210	///
211	/// Needs to drain the audio if this wasn’t done already.
212	pub end: Option<unsafe extern "C" fn(id: *mut ID) -> raw::c_int>,
213}
214
215
216unsafe extern "C" {
217	#[must_use]
218	pub unsafe fn spd_audio_open(name: *const raw::c_char, params: *const Params, error: *mut *mut raw::c_char) -> *mut ID;
219
220	#[must_use]
221	pub unsafe fn spd_audio_play(id: *mut ID, track: Track, format: Format) -> raw::c_int;
222
223	#[must_use]
224	pub unsafe fn spd_audio_begin(id: *mut ID, track: Track, format: Format) -> raw::c_int;
225
226	#[must_use]
227	pub unsafe fn spd_audio_feed_sync(id: *mut ID, track: Track, format: Format) -> raw::c_int;
228
229	#[must_use]
230	pub unsafe fn spd_audio_feed_sync_overlap(id: *mut ID, track: Track, format: Format) -> raw::c_int;
231
232	#[must_use]
233	pub unsafe fn spd_audio_end(id: *mut ID) -> raw::c_int;
234
235	#[must_use]
236	pub unsafe fn spd_audio_stop(id: *mut ID) -> raw::c_int;
237
238	#[must_use]
239	pub unsafe fn spd_audio_close(id: *mut ID) -> raw::c_int;
240
241	#[must_use]
242	pub unsafe fn spd_audio_set_volume(id: *mut ID, volume: raw::c_int) -> raw::c_int;
243	
244	pub unsafe fn spd_audio_set_loglevel(id: *mut ID, level: raw::c_int);
245
246	#[must_use]
247	pub unsafe fn spd_audio_get_playcmd(id: *mut ID) -> *const raw::c_char;
248}