Skip to content

Commit d2e782c

Browse files
authored
Merge pull request #419 from orottier/feature/device-readiness
Set BaseAudioContext::state by render thread to signal device readiness
2 parents 6d496c5 + 0644fe2 commit d2e782c

File tree

11 files changed

+358
-61
lines changed

11 files changed

+358
-61
lines changed

src/context/concrete_base.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ struct ConcreteBaseAudioContextInner {
9191
listener_params: Option<AudioListenerParams>,
9292
/// Denotes if this AudioContext is offline or not
9393
offline: bool,
94-
/// Describes the current state of the `ConcreteBaseAudioContext`
95-
state: AtomicU8,
94+
/// Current state of the `ConcreteBaseAudioContext`, shared with the RenderThread
95+
state: Arc<AtomicU8>,
9696
/// Stores the event handlers
9797
event_loop: EventLoop,
9898
/// Sender for events that will be handled by the EventLoop
@@ -147,9 +147,11 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
147147

148148
impl ConcreteBaseAudioContext {
149149
/// Creates a `BaseAudioContext` instance
150+
#[allow(clippy::too_many_arguments)] // TODO refactor with builder pattern
150151
pub(super) fn new(
151152
sample_rate: f32,
152153
max_channel_count: usize,
154+
state: Arc<AtomicU8>,
153155
frames_played: Arc<AtomicU64>,
154156
render_channel: Sender<ControlMessage>,
155157
event_channel: Option<(Sender<EventDispatch>, Receiver<EventDispatch>)>,
@@ -175,7 +177,7 @@ impl ConcreteBaseAudioContext {
175177
queued_audio_listener_msgs: Mutex::new(Vec::new()),
176178
listener_params: None,
177179
offline,
178-
state: AtomicU8::new(AudioContextState::Suspended as u8),
180+
state,
179181
event_loop: event_loop.clone(),
180182
event_send,
181183
};
@@ -263,7 +265,6 @@ impl ConcreteBaseAudioContext {
263265
if self.state() != AudioContextState::Closed {
264266
let result = self.inner.render_channel.read().unwrap().send(msg);
265267
if result.is_err() {
266-
self.set_state(AudioContextState::Closed);
267268
log::warn!("Discarding control message - render thread is closed");
268269
}
269270
}
@@ -333,14 +334,14 @@ impl ConcreteBaseAudioContext {
333334
/// Returns state of current context
334335
#[must_use]
335336
pub(super) fn state(&self) -> AudioContextState {
336-
self.inner.state.load(Ordering::SeqCst).into()
337+
self.inner.state.load(Ordering::Acquire).into()
337338
}
338339

339340
/// Updates state of current context
340341
pub(super) fn set_state(&self, state: AudioContextState) {
341342
let current_state = self.state();
342343
if current_state != state {
343-
self.inner.state.store(state as u8, Ordering::SeqCst);
344+
self.inner.state.store(state as u8, Ordering::Release);
344345
let _ = self.send_event(EventDispatch::state_change());
345346
}
346347
}

src/context/offline.rs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
//! The `OfflineAudioContext` type
22
3-
use std::sync::atomic::AtomicU64;
3+
use std::sync::atomic::{AtomicU64, AtomicU8};
44
use std::sync::{Arc, Mutex};
55

66
use crate::buffer::AudioBuffer;
7-
use crate::context::{BaseAudioContext, ConcreteBaseAudioContext};
7+
use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext};
88
use crate::render::RenderThread;
99
use crate::{assert_valid_sample_rate, RENDER_QUANTUM_SIZE};
1010

@@ -71,19 +71,23 @@ impl OfflineAudioContext {
7171
// track number of frames - synced from render thread to control thread
7272
let frames_played = Arc::new(AtomicU64::new(0));
7373
let frames_played_clone = Arc::clone(&frames_played);
74+
let state = Arc::new(AtomicU8::new(AudioContextState::Suspended as u8));
75+
let state_clone = Arc::clone(&state);
7476

7577
// setup the render 'thread', which will run inside the control thread
7678
let renderer = RenderThread::new(
7779
sample_rate,
7880
number_of_channels,
7981
receiver,
82+
state_clone,
8083
frames_played_clone,
8184
);
8285

8386
// first, setup the base audio context
8487
let base = ConcreteBaseAudioContext::new(
8588
sample_rate,
8689
number_of_channels,
90+
state,
8791
frames_played,
8892
sender,
8993
None,
@@ -131,7 +135,11 @@ impl OfflineAudioContext {
131135
..
132136
} = renderer;
133137

134-
renderer.render_audiobuffer_sync(self.length, suspend_callbacks, self)
138+
self.base.set_state(AudioContextState::Running);
139+
let result = renderer.render_audiobuffer_sync(self.length, suspend_callbacks, self);
140+
self.base.set_state(AudioContextState::Closed);
141+
142+
result
135143
}
136144

137145
/// Given the current connections and scheduled changes, starts rendering audio.
@@ -159,9 +167,15 @@ impl OfflineAudioContext {
159167
..
160168
} = renderer;
161169

162-
renderer
170+
self.base.set_state(AudioContextState::Running);
171+
172+
let result = renderer
163173
.render_audiobuffer(self.length, suspend_promises, resume_receiver)
164-
.await
174+
.await;
175+
176+
self.base.set_state(AudioContextState::Closed);
177+
178+
result
165179
}
166180

167181
/// get the length of rendering audio buffer
@@ -253,7 +267,8 @@ impl OfflineAudioContext {
253267
.insert(insert_pos, (quantum, sender));
254268
} // lock is dropped
255269

256-
receiver.await.unwrap()
270+
receiver.await.unwrap();
271+
self.base().set_state(AudioContextState::Suspended);
257272
}
258273

259274
/// Schedules a suspension of the time progression in the audio context at the specified time
@@ -313,9 +328,15 @@ impl OfflineAudioContext {
313328
"InvalidStateError: cannot suspend multiple times at the same render quantum",
314329
);
315330

331+
let boxed_callback = Box::new(|ctx: &mut OfflineAudioContext| {
332+
ctx.base().set_state(AudioContextState::Suspended);
333+
(callback)(ctx);
334+
ctx.base().set_state(AudioContextState::Running);
335+
});
336+
316337
renderer
317338
.suspend_callbacks
318-
.insert(insert_pos, (quantum, Box::new(callback)));
339+
.insert(insert_pos, (quantum, boxed_callback));
319340
}
320341

321342
/// Resumes the progression of the OfflineAudioContext's currentTime when it has been suspended
@@ -324,6 +345,7 @@ impl OfflineAudioContext {
324345
///
325346
/// Panics when the context is closed or rendering has not started
326347
pub async fn resume(&self) {
348+
self.base().set_state(AudioContextState::Running);
327349
self.resume_sender.clone().send(()).await.unwrap()
328350
}
329351
}
@@ -339,6 +361,7 @@ mod tests {
339361
#[test]
340362
fn render_empty_graph() {
341363
let mut context = OfflineAudioContext::new(2, 555, 44_100.);
364+
assert_eq!(context.state(), AudioContextState::Suspended);
342365
let buffer = context.start_rendering_sync();
343366

344367
assert_eq!(context.length(), 555);
@@ -347,6 +370,8 @@ mod tests {
347370
assert_eq!(buffer.length(), 555);
348371
assert_float_eq!(buffer.get_channel_data(0), &[0.; 555][..], abs_all <= 0.);
349372
assert_float_eq!(buffer.get_channel_data(1), &[0.; 555][..], abs_all <= 0.);
373+
374+
assert_eq!(context.state(), AudioContextState::Closed);
350375
}
351376

352377
#[test]
@@ -365,12 +390,14 @@ mod tests {
365390
let mut context = OfflineAudioContext::new(1, len, sample_rate as f32);
366391

367392
context.suspend_sync(RENDER_QUANTUM_SIZE as f64 / sample_rate, |context| {
393+
assert_eq!(context.state(), AudioContextState::Suspended);
368394
let mut src = context.create_constant_source();
369395
src.connect(&context.destination());
370396
src.start();
371397
});
372398

373399
context.suspend_sync((3 * RENDER_QUANTUM_SIZE) as f64 / sample_rate, |context| {
400+
assert_eq!(context.state(), AudioContextState::Suspended);
374401
context.destination().disconnect();
375402
});
376403

0 commit comments

Comments
 (0)