diff --git a/Cargo.lock b/Cargo.lock index 5d712bc9..ea101c35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,7 +334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -447,7 +447,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "khronos_api 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -785,12 +785,12 @@ name = "log" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -857,7 +857,7 @@ dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1021,7 +1021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1212,6 +1212,7 @@ dependencies = [ "boxfnonce 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byte-slice-cast 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1486,7 +1487,7 @@ dependencies = [ "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "gleam 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "plane-split 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1572,7 +1573,7 @@ dependencies = [ "core-graphics 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1708,7 +1709,7 @@ dependencies = [ "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum lock_api 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "949826a5ccf18c1b3a7c3d57692778d21768b79e46eb9dd07bfc4c2160036c54" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2" +"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" diff --git a/audio/Cargo.toml b/audio/Cargo.toml index 1740bb6e..e30ebf56 100644 --- a/audio/Cargo.toml +++ b/audio/Cargo.toml @@ -14,6 +14,7 @@ serde_derive = "1.0.66" serde = "1.0.66" servo_media_derive = { path = "../servo-media-derive" } smallvec = "0.6.1" +log = "0.4.5" [dependencies.petgraph] version = "0.4.12" diff --git a/audio/src/context.rs b/audio/src/context.rs index 33572020..fe45819f 100644 --- a/audio/src/context.rs +++ b/audio/src/context.rs @@ -26,6 +26,7 @@ pub type StateChangeResult = Result<(), ()>; /// Identify the type of playback, which affects tradeoffs between audio output /// and power consumption. +#[derive(Copy, Clone)] pub enum LatencyCategory { /// Balance audio output latency and power consumption. Balanced, @@ -37,6 +38,7 @@ pub enum LatencyCategory { } /// User-specified options for a real time audio context. +#[derive(Copy, Clone)] pub struct RealTimeAudioContextOptions { /// Number of samples that will play in one second, measured in Hz. pub sample_rate: f32, @@ -54,6 +56,7 @@ impl Default for RealTimeAudioContextOptions { } /// User-specified options for an offline audio context. +#[derive(Copy, Clone)] pub struct OfflineAudioContextOptions { /// The number of channels for this offline audio context. pub channels: u8, @@ -86,6 +89,7 @@ impl From for AudioContextOptions { } /// User-specified options for a real time or offline audio context. +#[derive(Copy, Clone)] pub enum AudioContextOptions { RealTimeAudioContext(RealTimeAudioContextOptions), OfflineAudioContext(OfflineAudioContextOptions), @@ -128,8 +132,9 @@ impl AudioContext { Builder::new() .name("AudioRenderThread".to_owned()) .spawn(move || { - AudioRenderThread::::start(receiver, sender_, sample_rate, graph, options) - .expect("Could not start AudioRenderThread"); + AudioRenderThread::::start(|| B::make_sink(), + receiver, sender_, sample_rate, + graph, options); }) .unwrap(); Self { diff --git a/audio/src/lib.rs b/audio/src/lib.rs index a6f98134..83728e3a 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -3,6 +3,8 @@ extern crate serde_derive; #[macro_use] extern crate servo_media_derive; +#[macro_use] +extern crate log; extern crate boxfnonce; extern crate byte_slice_cast; @@ -36,5 +38,23 @@ pub trait AudioBackend { type Decoder: decoder::AudioDecoder; type Sink: sink::AudioSink; fn make_decoder() -> Self::Decoder; - fn make_sink() -> Result; + fn make_sink() -> Result::Error>; +} + +pub struct DummyBackend {} + +impl AudioBackend for DummyBackend { + type Decoder = decoder::DummyAudioDecoder; + type Sink = sink::DummyAudioSink; + fn make_decoder() -> Self::Decoder { + decoder::DummyAudioDecoder + } + + fn make_sink() -> Result { + Ok(sink::DummyAudioSink) + } +} + +impl DummyBackend { + pub fn init() {} } diff --git a/audio/src/macros.rs b/audio/src/macros.rs index a7ff531c..f8f739bb 100644 --- a/audio/src/macros.rs +++ b/audio/src/macros.rs @@ -34,7 +34,7 @@ macro_rules! make_render_thread_state_change( return Ok(()); } self.state = ProcessingState::$state; - self.sink.$sink_method() + self.sink.$sink_method().map_err(|_| ()) } ); ); diff --git a/audio/src/offline_sink.rs b/audio/src/offline_sink.rs index 606011a4..0cda9879 100644 --- a/audio/src/offline_sink.rs +++ b/audio/src/offline_sink.rs @@ -34,17 +34,22 @@ impl OfflineAudioSink { } } +// replace with ! when it stabilizes +#[derive(Debug)] +pub enum OfflineError {} + impl AudioSink for OfflineAudioSink { - fn init(&self, _: f32, _: Sender) -> Result<(), ()> { + type Error = OfflineError; + fn init(&self, _: f32, _: Sender) -> Result<(), OfflineError> { Ok(()) } - fn play(&self) -> Result<(), ()> { + fn play(&self) -> Result<(), OfflineError> { self.has_enough_data.set(false); Ok(()) } - fn stop(&self) -> Result<(), ()> { + fn stop(&self) -> Result<(), OfflineError> { self.has_enough_data.set(true); Ok(()) } @@ -54,7 +59,7 @@ impl AudioSink for OfflineAudioSink { || (self.rendered_blocks.get() * FRAMES_PER_BLOCK_USIZE >= self.length) } - fn push_data(&self, mut chunk: Chunk) -> Result<(), ()> { + fn push_data(&self, mut chunk: Chunk) -> Result<(), OfflineError> { let offset = self.rendered_blocks.get() * FRAMES_PER_BLOCK_USIZE; let (last, copy_len) = if self.length - offset <= FRAMES_PER_BLOCK_USIZE { (true, self.length - offset) diff --git a/audio/src/render_thread.rs b/audio/src/render_thread.rs index 5e7a1b41..a1b5d875 100644 --- a/audio/src/render_thread.rs +++ b/audio/src/render_thread.rs @@ -11,9 +11,8 @@ use node::{AudioNodeEngine, AudioNodeInit, AudioNodeMessage}; use offline_sink::OfflineAudioSink; use oscillator_node::OscillatorNode; use panner_node::PannerNode; -use sink::AudioSink; +use sink::{AudioSink, DummyAudioSink}; use std::sync::mpsc::{Receiver, Sender}; -use AudioBackend; pub enum AudioRenderThreadMsg { CreateNode(AudioNodeInit, Sender, ChannelInfo), @@ -35,30 +34,32 @@ pub enum AudioRenderThreadMsg { SetSinkEosCallback(Box>) + Send + Sync + 'static>), } -pub enum Sink { - RealTime(B::Sink), +pub enum Sink { + RealTime(S), Offline(OfflineAudioSink), } -impl AudioSink for Sink { - fn init(&self, sample_rate: f32, sender: Sender) -> Result<(), ()> { +impl AudioSink for Sink { + type Error = S::Error; + + fn init(&self, sample_rate: f32, sender: Sender) -> Result<(), Self::Error> { match *self { Sink::RealTime(ref sink) => sink.init(sample_rate, sender), - Sink::Offline(ref sink) => sink.init(sample_rate, sender), + Sink::Offline(ref sink) => Ok(sink.init(sample_rate, sender).unwrap()), } } - fn play(&self) -> Result<(), ()> { + fn play(&self) -> Result<(), Self::Error> { match *self { Sink::RealTime(ref sink) => sink.play(), - Sink::Offline(ref sink) => sink.play(), + Sink::Offline(ref sink) => Ok(sink.play().unwrap()), } } - fn stop(&self) -> Result<(), ()> { + fn stop(&self) -> Result<(), Self::Error> { match *self { Sink::RealTime(ref sink) => sink.stop(), - Sink::Offline(ref sink) => sink.stop(), + Sink::Offline(ref sink) => Ok(sink.stop().unwrap()), } } @@ -69,10 +70,10 @@ impl AudioSink for Sink { } } - fn push_data(&self, chunk: Chunk) -> Result<(), ()> { + fn push_data(&self, chunk: Chunk) -> Result<(), Self::Error> { match *self { Sink::RealTime(ref sink) => sink.push_data(chunk), - Sink::Offline(ref sink) => sink.push_data(chunk), + Sink::Offline(ref sink) => Ok(sink.push_data(chunk).unwrap()), } } @@ -84,44 +85,85 @@ impl AudioSink for Sink { } } -pub struct AudioRenderThread { + +pub struct AudioRenderThread { pub graph: AudioGraph, - pub sink: Sink, + pub sink: Sink, pub state: ProcessingState, pub sample_rate: f32, pub current_time: f64, pub current_frame: Tick, } -impl AudioRenderThread { - /// Start the audio render thread - pub fn start( - event_queue: Receiver, +impl AudioRenderThread { + /// Initializes the AudioRenderThread object + /// + /// You must call .event_loop() on this to run it! + fn prepare_thread( + make_sink: F, sender: Sender, sample_rate: f32, graph: AudioGraph, options: AudioContextOptions, - ) -> Result<(), ()> { + ) -> Result + where F: FnOnce() -> Result + { let sink = match options { - AudioContextOptions::RealTimeAudioContext(_) => Sink::RealTime(B::make_sink()?), + AudioContextOptions::RealTimeAudioContext(_) => { + let sink = match make_sink() { + Ok(s) => s, + Err(e) => return Err((graph, e)) + }; + + Sink::RealTime(sink) + }, AudioContextOptions::OfflineAudioContext(options) => Sink::Offline( OfflineAudioSink::new(options.channels as usize, options.length), ), }; - let mut graph = Self { + + if let Err(e) = sink.init(sample_rate, sender) { + return Err((graph, e)) + } + + Ok(Self { graph, sink, state: ProcessingState::Suspended, sample_rate, current_time: 0., current_frame: Tick(0), - }; + }) + } - graph.sink.init(sample_rate, sender)?; - graph.event_loop(event_queue); + /// Start the audio render thread + /// + /// In case something fails, it will instead start a thread with a dummy backend + pub fn start( + make_sink: F, + event_queue: Receiver, + sender: Sender, + sample_rate: f32, + graph: AudioGraph, + options: AudioContextOptions, + ) + where F: FnOnce() -> Result + { + let thread = Self::prepare_thread(make_sink, sender.clone(), sample_rate, graph, options); + match thread { + Ok(mut thread) => thread.event_loop(event_queue), + Err((graph, e)) => { + error!("Could not start audio render thread due to error `{:?}`, \ + falling back to dummy backend", e); + let mut thread = AudioRenderThread:: + ::prepare_thread(|| Ok(DummyAudioSink), + sender, sample_rate, graph, options) + .map_err(|_| ()).unwrap(); + thread.event_loop(event_queue) - Ok(()) + } + } } make_render_thread_state_change!(resume, Running, play); diff --git a/audio/src/sink.rs b/audio/src/sink.rs index 03af31aa..87dbebec 100644 --- a/audio/src/sink.rs +++ b/audio/src/sink.rs @@ -1,23 +1,26 @@ use block::Chunk; use render_thread::AudioRenderThreadMsg; +use std::fmt::Debug; use std::sync::mpsc::Sender; pub trait AudioSink { + type Error: Debug; fn init( &self, sample_rate: f32, render_thread_channel: Sender, - ) -> Result<(), ()>; - fn play(&self) -> Result<(), ()>; - fn stop(&self) -> Result<(), ()>; + ) -> Result<(), Self::Error>; + fn play(&self) -> Result<(), Self::Error>; + fn stop(&self) -> Result<(), Self::Error>; fn has_enough_data(&self) -> bool; - fn push_data(&self, chunk: Chunk) -> Result<(), ()>; + fn push_data(&self, chunk: Chunk) -> Result<(), Self::Error>; fn set_eos_callback(&self, callback: Box>) + Send + Sync + 'static>); } pub struct DummyAudioSink; impl AudioSink for DummyAudioSink { + type Error = (); fn init(&self, _: f32, _: Sender) -> Result<(), ()> { Ok(()) } diff --git a/backends/gstreamer/src/audio_sink.rs b/backends/gstreamer/src/audio_sink.rs index a24a5e7a..5095960e 100644 --- a/backends/gstreamer/src/audio_sink.rs +++ b/backends/gstreamer/src/audio_sink.rs @@ -23,15 +23,27 @@ pub struct GStreamerAudioSink { sample_offset: Cell, } +#[derive(Debug)] +pub enum BackendError { + Gstreamer(gst::Error), + Flow(gst::FlowError), + ElementCreationFailed(&'static str), + AudioInfoFailed, + PipelineFailed(&'static str), + StateChangeFailed, +} + + impl GStreamerAudioSink { - pub fn new() -> Result { + pub fn new() -> Result { if let Some(category) = gst::DebugCategory::get("openslessink") { category.set_threshold(gst::DebugLevel::Trace); } - gst::init().map_err(|_| ())?; + gst::init().map_err(BackendError::Gstreamer)?; - let appsrc = gst::ElementFactory::make("appsrc", None).ok_or(())?; - let appsrc = appsrc.downcast::().map_err(|_| ())?; + let appsrc = gst::ElementFactory::make("appsrc", None) + .ok_or(BackendError::ElementCreationFailed("appsrc"))?; + let appsrc = appsrc.downcast::().unwrap(); Ok(Self { pipeline: gst::Pipeline::new(None), appsrc: Arc::new(appsrc), @@ -43,19 +55,19 @@ impl GStreamerAudioSink { } impl GStreamerAudioSink { - fn set_audio_info(&self, sample_rate: f32, channels: u8) -> Result<(), ()> { + fn set_audio_info(&self, sample_rate: f32, channels: u8) -> Result<(), BackendError> { let audio_info = gst_audio::AudioInfo::new( gst_audio::AUDIO_FORMAT_F32, sample_rate as u32, channels.into(), ).build() - .ok_or(())?; + .ok_or(BackendError::AudioInfoFailed)?; self.appsrc.set_caps(&audio_info.to_caps().unwrap()); *self.audio_info.borrow_mut() = Some(audio_info); Ok(()) } - fn set_channels_if_changed(&self, channels: u8) -> Result<(), ()> { + fn set_channels_if_changed(&self, channels: u8) -> Result<(), BackendError> { let curr_channels = if let Some(ch) = self.audio_info.borrow().as_ref() { ch.channels() } else { @@ -69,11 +81,12 @@ impl GStreamerAudioSink { } impl AudioSink for GStreamerAudioSink { + type Error = BackendError; fn init( &self, sample_rate: f32, graph_thread_channel: Sender, - ) -> Result<(), ()> { + ) -> Result<(), BackendError> { self.sample_rate.set(sample_rate); self.set_audio_info(sample_rate, 2)?; self.appsrc.set_property_format(gst::Format::Time); @@ -95,38 +108,42 @@ impl AudioSink for GStreamerAudioSink { .unwrap(); let appsrc = self.appsrc.as_ref().clone().upcast(); - let resample = gst::ElementFactory::make("audioresample", None).ok_or(())?; - let convert = gst::ElementFactory::make("audioconvert", None).ok_or(())?; - let sink = gst::ElementFactory::make("autoaudiosink", None).ok_or(())?; + let resample = gst::ElementFactory::make("audioresample", None) + .ok_or(BackendError::ElementCreationFailed("audioresample"))?; + let convert = gst::ElementFactory::make("audioconvert", None) + .ok_or(BackendError::ElementCreationFailed("audioconvert"))?; + let sink = gst::ElementFactory::make("autoaudiosink", None) + .ok_or(BackendError::ElementCreationFailed("autoaudiosink"))?; self.pipeline .add_many(&[&appsrc, &resample, &convert, &sink]) - .map_err(|_| ())?; - gst::Element::link_many(&[&appsrc, &resample, &convert, &sink]).map_err(|_| ())?; + .map_err(|e| BackendError::PipelineFailed(e.0))?; + gst::Element::link_many(&[&appsrc, &resample, &convert, &sink]) + .map_err(|e| BackendError::PipelineFailed(e.0))?; Ok(()) } - fn play(&self) -> Result<(), ()> { + fn play(&self) -> Result<(), BackendError> { self.pipeline .set_state(gst::State::Playing) .into_result() .map(|_| ()) - .map_err(|_| ()) + .map_err(|_| BackendError::StateChangeFailed) } - fn stop(&self) -> Result<(), ()> { + fn stop(&self) -> Result<(), BackendError> { self.pipeline .set_state(gst::State::Paused) .into_result() .map(|_| ()) - .map_err(|_| ()) + .map_err(|_| BackendError::StateChangeFailed) } fn has_enough_data(&self) -> bool { self.appsrc.get_current_level_bytes() >= self.appsrc.get_max_bytes() } - fn push_data(&self, mut chunk: Chunk) -> Result<(), ()> { + fn push_data(&self, mut chunk: Chunk) -> Result<(), BackendError> { if let Some(block) = chunk.blocks.get(0) { self.set_channels_if_changed(block.chan_count())?; } @@ -179,7 +196,7 @@ impl AudioSink for GStreamerAudioSink { .push_buffer(buffer) .into_result() .map(|_| ()) - .map_err(|_| ()) + .map_err(BackendError::Flow) } fn set_eos_callback(&self, _: Box>) + Send + Sync + 'static>) {} diff --git a/backends/gstreamer/src/lib.rs b/backends/gstreamer/src/lib.rs index fb6ff4ce..0662aec4 100644 --- a/backends/gstreamer/src/lib.rs +++ b/backends/gstreamer/src/lib.rs @@ -11,6 +11,7 @@ extern crate servo_media_audio; extern crate servo_media_player; use servo_media_audio::AudioBackend; +use servo_media_audio::sink::AudioSink; use servo_media_player::PlayerBackend; pub mod audio_decoder; @@ -25,7 +26,7 @@ impl AudioBackend for GStreamerBackend { fn make_decoder() -> Self::Decoder { audio_decoder::GStreamerAudioDecoder::new() } - fn make_sink() -> Result { + fn make_sink() -> Result::Error> { audio_sink::GStreamerAudioSink::new() } }