ability to create/destroy audio output device on demand

- this commit finalizes adding audio support for android
- fixes #435
This commit is contained in:
Dmitry
2023-03-15 18:05:27 +03:00
parent 4d145f3357
commit 3816ed78f9
22 changed files with 100 additions and 107 deletions
+1 -2
View File
@@ -526,11 +526,10 @@ impl Editor {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
// Editor cannot run on Android so we can safely call `resume` here.
// Editor cannot run on Android so we can safely initialize graphics context here.
engine.initialize_graphics_context(event_loop).unwrap();
let graphics_context = engine.graphics_context.as_initialized_mut();
-1
View File
@@ -203,7 +203,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -35,7 +35,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -121,7 +121,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -305,7 +305,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -159,7 +159,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -93,7 +93,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -138,7 +138,6 @@ impl Game {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
-1
View File
@@ -338,7 +338,6 @@ fn main() {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
+2 -2
View File
@@ -16,7 +16,7 @@ use std::{
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
let hrir_sphere =
HrirSphere::from_file("examples/data/IRC_1002_C.bin", context::SAMPLE_RATE).unwrap();
@@ -24,7 +24,7 @@ fn main() {
// Initialize new sound context with default output device.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Set HRTF renderer instead of default.
context
+2 -2
View File
@@ -14,12 +14,12 @@ use std::{
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Load sound buffer.
let drop_buffer = SoundBufferResource::new_generic(
+2 -2
View File
@@ -9,13 +9,13 @@ use std::{thread, time::Duration};
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Create new context.
let context = SoundContext::new();
// Register context in the engine.
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Load sound buffer.
let door_open_buffer = SoundBufferResource::new_generic(
+2 -2
View File
@@ -14,12 +14,12 @@ use std::{
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Load sound buffer.
let drop_buffer = SoundBufferResource::new_generic(
+2 -2
View File
@@ -8,12 +8,12 @@ use std::{thread, time::Duration};
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Create sine wave.
let sample_rate = 44100;
+2 -2
View File
@@ -52,12 +52,12 @@ impl RawStreamingDataSource for SamplesGenerator {
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Create sine wave generator
let sine_wave = DataSource::RawStreaming(Box::new(SamplesGenerator::new()));
+2 -2
View File
@@ -19,12 +19,12 @@ fn main() {
HrirSphere::from_file("examples/data/IRC_1002_C.bin", context::SAMPLE_RATE).unwrap();
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Set HRTF renderer instead of default for binaural sound.
context
+2 -2
View File
@@ -10,12 +10,12 @@ use std::{thread, time::Duration};
fn main() {
// Initialize sound engine with default output device.
let engine = SoundEngine::new();
let engine = SoundEngine::new().unwrap();
// Initialize new sound context.
let context = SoundContext::new();
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Load sound buffer.
let waterfall_buffer = SoundBufferResource::new_streaming(
+4 -3
View File
@@ -1,3 +1,4 @@
use fyrox_sound::engine::State;
use fyrox_sound::{
buffer::{DataSource, SoundBufferResource},
context::SoundContext,
@@ -14,7 +15,7 @@ fn main() {
let context = SoundContext::new();
// Register context in the engine.
engine.lock().unwrap().add_context(context.clone());
engine.state().add_context(context.clone());
// Load sound buffer.
let door_open_buffer = SoundBufferResource::new_generic(
@@ -46,13 +47,13 @@ fn main() {
let mut wav_writer = hound::WavWriter::create("output.wav", wav_spec).unwrap();
// Create an output buffer.
let buf_len = SoundEngine::render_buffer_len();
let buf_len = State::render_buffer_len();
let mut buf = vec![(0.0f32, 0.0f32); buf_len];
let mut samples_written = 0;
// Wait until sound will play completely.
while samples_written < 3 * fyrox_sound::context::SAMPLE_RATE {
engine.lock().unwrap().render(&mut buf);
engine.state().render(&mut buf);
for &(l, r) in buf.iter() {
wav_writer.write_sample(l).unwrap();
wav_writer.write_sample(r).unwrap();
+66 -54
View File
@@ -6,11 +6,22 @@
use crate::context::{SoundContext, SAMPLE_RATE};
use fyrox_core::visitor::{Visit, VisitResult, Visitor};
use std::sync::{Arc, Mutex};
use std::error::Error;
use std::sync::{Arc, Mutex, MutexGuard};
/// Internal state of sound engine.
#[derive(Default)]
pub struct SoundEngine {
/// Sound engine manages contexts, feeds output device with data. Sound engine instance can be cloned,
/// however this is always a "shallow" clone, because actual sound engine data is wrapped in Arc.
#[derive(Clone)]
pub struct SoundEngine(Arc<Mutex<State>>);
impl Default for SoundEngine {
fn default() -> Self {
Self::without_device()
}
}
/// Internal state of the sound engine.
pub struct State {
contexts: Vec<SoundContext>,
output_device: Option<Box<dyn tinyaudio::BaseAudioOutputDevice>>,
}
@@ -19,63 +30,64 @@ impl SoundEngine {
/// Creates new instance of the sound engine. It is possible to have multiple engines running at
/// the same time, but you shouldn't do this because you can create multiple contexts which
/// should cover 99% of use cases.
pub fn new() -> Arc<Mutex<Self>> {
Self::new_inner(false)
pub fn new() -> Result<Self, Box<dyn Error>> {
let engine = Self::without_device();
engine.initialize_audio_output_device()?;
Ok(engine)
}
/// Like `new()` but won't use any OS sound devices.
/// Can be useful for running on CI where those might not be available.
pub fn new_headless() -> Arc<Mutex<Self>> {
Self::new_inner(true)
}
fn new_inner(headless: bool) -> Arc<Mutex<Self>> {
let engine = Arc::new(Mutex::new(Self {
/// Creates new instance of a sound engine without OS audio output device (so called headless mode).
/// The user should periodically run [`Self::render`] if they want to implement their own sample sending
/// method to an output device (or a file, etc.).
pub fn without_device() -> Self {
Self(Arc::new(Mutex::new(State {
contexts: Default::default(),
output_device: None,
}));
if !headless {
let device = tinyaudio::run_output_device(
tinyaudio::OutputDeviceParameters {
sample_rate: SAMPLE_RATE as usize,
channels_count: 2,
channel_sample_count: SoundContext::SAMPLES_PER_CHANNEL,
},
{
let state = engine.clone();
move |buf| {
// SAFETY: This is safe as long as channels count above is 2.
let data = unsafe {
std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut (f32, f32),
buf.len() / 2,
)
};
if let Ok(mut state) = state.lock() {
state.render(data);
}
}
},
)
.unwrap();
engine.lock().unwrap().output_device = Some(device);
}
engine
})))
}
/// Creates new instance of a sound engine without running a device thread. The user must
/// periodically run [`Self::render`].
pub fn without_device() -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(Self {
contexts: Default::default(),
output_device: None,
}))
/// Tries to initialize default audio output device.
pub fn initialize_audio_output_device(&self) -> Result<(), Box<dyn Error>> {
let state = self.clone();
let device = tinyaudio::run_output_device(
tinyaudio::OutputDeviceParameters {
sample_rate: SAMPLE_RATE as usize,
channels_count: 2,
channel_sample_count: SoundContext::SAMPLES_PER_CHANNEL,
},
{
move |buf| {
// SAFETY: This is safe as long as channels count above is 2.
let data = unsafe {
std::slice::from_raw_parts_mut(
buf.as_mut_ptr() as *mut (f32, f32),
buf.len() / 2,
)
};
state.state().render(data);
}
},
)?;
self.state().output_device = Some(device);
Ok(())
}
/// Destroys current audio output device (if any).
pub fn destroy_audio_output_device(&self) {
self.state().output_device = None;
}
/// Provides direct access to actual engine data.
pub fn state(&self) -> MutexGuard<State> {
self.0.lock().unwrap()
}
}
impl State {
/// Adds new context to the engine. Each context must be added to the engine to emit
/// sounds.
pub fn add_context(&mut self, context: SoundContext) {
@@ -131,7 +143,7 @@ impl SoundEngine {
}
}
impl Visit for SoundEngine {
impl Visit for State {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
if visitor.is_reading() {
self.contexts.clear();
-1
View File
@@ -73,7 +73,6 @@ impl Executor {
graphics_context_params,
resource_manager: ResourceManager::new(serialization_context.clone()),
serialization_context,
headless: false,
})
.unwrap();
+8 -16
View File
@@ -57,7 +57,7 @@ use std::{
ops::Deref,
sync::{
mpsc::{channel, Receiver},
Arc, Mutex,
Arc,
},
time::Duration,
};
@@ -201,7 +201,7 @@ pub struct Engine {
model_events_receiver: Receiver<ResourceEvent<Model>>,
#[allow(dead_code)] // Keep engine instance alive.
sound_engine: Arc<Mutex<SoundEngine>>,
sound_engine: SoundEngine,
// A set of plugin constructors.
plugin_constructors: Vec<Box<dyn PluginConstructor>>,
@@ -705,12 +705,6 @@ pub struct EngineInitParams {
pub serialization_context: Arc<SerializationContext>,
/// A resource manager.
pub resource_manager: ResourceManager,
/// (experimental) Run the engine without opening a window (TODO) and without sound.
/// Useful for dedicated game servers or running on CI.
///
/// Headless support is incomplete, for progress see
/// <https://github.com/FyroxEngine/Fyrox/issues/222>.
pub headless: bool,
}
macro_rules! define_process_node {
@@ -818,7 +812,6 @@ impl Engine {
/// graphics_context_params,
/// resource_manager: ResourceManager::new(serialization_context.clone()),
/// serialization_context,
/// headless: false,
/// })
/// .unwrap();
/// ```
@@ -829,15 +822,8 @@ impl Engine {
graphics_context_params,
serialization_context,
resource_manager,
headless,
} = params;
let sound_engine = if headless {
SoundEngine::new_headless()
} else {
SoundEngine::new()
};
let (rx, tx) = channel();
resource_manager
.state()
@@ -846,6 +832,8 @@ impl Engine {
.event_broadcaster
.add(rx);
let sound_engine = SoundEngine::without_device();
Ok(Self {
graphics_context: GraphicsContext::Uninitialized(graphics_context_params),
model_events_receiver: tx,
@@ -1017,6 +1005,8 @@ impl Engine {
params: params.clone(),
});
self.sound_engine.initialize_audio_output_device()?;
Ok(())
} else {
Err(EngineError::Custom(
@@ -1060,6 +1050,8 @@ impl Engine {
vsync: params.vsync,
});
self.sound_engine.destroy_audio_output_device();
Ok(())
} else {
Err(EngineError::Custom(
+5 -7
View File
@@ -64,7 +64,7 @@ use std::{
fmt::{Display, Formatter},
ops::{Index, IndexMut},
path::Path,
sync::{Arc, Mutex},
sync::Arc,
};
/// A container for navigational meshes.
@@ -741,12 +741,12 @@ impl Scene {
/// Container for scenes in the engine.
pub struct SceneContainer {
pool: Pool<Scene>,
sound_engine: Arc<Mutex<SoundEngine>>,
sound_engine: SoundEngine,
pub(crate) destruction_list: Vec<(Handle<Scene>, Scene)>,
}
impl SceneContainer {
pub(crate) fn new(sound_engine: Arc<Mutex<SoundEngine>>) -> Self {
pub(crate) fn new(sound_engine: SoundEngine) -> Self {
Self {
pool: Pool::new(),
sound_engine,
@@ -795,8 +795,7 @@ impl SceneContainer {
#[inline]
pub fn add(&mut self, scene: Scene) -> Handle<Scene> {
self.sound_engine
.lock()
.unwrap()
.state()
.add_context(scene.graph.sound_context.native.clone());
self.pool.spawn(scene)
}
@@ -811,8 +810,7 @@ impl SceneContainer {
#[inline]
pub fn remove(&mut self, handle: Handle<Scene>) {
self.sound_engine
.lock()
.unwrap()
.state()
.remove_context(self.pool[handle].graph.sound_context.native.clone());
self.destruction_list.push((handle, self.pool.free(handle)));
}