mirror of
https://github.com/servo/servo.git
synced 2025-06-19 14:48:59 +01:00
decodeAudioData
This commit is contained in:
parent
e0e1141e74
commit
b87fc17b4b
3 changed files with 125 additions and 20 deletions
|
@ -38,13 +38,25 @@ impl AudioBuffer {
|
||||||
pub fn new_inherited(cx: *mut JSContext,
|
pub fn new_inherited(cx: *mut JSContext,
|
||||||
number_of_channels: u32,
|
number_of_channels: u32,
|
||||||
length: u32,
|
length: u32,
|
||||||
sample_rate: f32) -> AudioBuffer {
|
sample_rate: f32,
|
||||||
let initial_data = vec![0.; length as usize];
|
initial_data: Option<&[f32]>) -> AudioBuffer {
|
||||||
|
let initial_data = match initial_data {
|
||||||
|
Some(initial_data) => {
|
||||||
|
let mut data = vec![];
|
||||||
|
data.extend_from_slice(initial_data);
|
||||||
|
data
|
||||||
|
},
|
||||||
|
None => vec![0.; (length * number_of_channels) as usize]
|
||||||
|
};
|
||||||
let mut js_channels: Vec<JSAudioChannel> = Vec::with_capacity(number_of_channels as usize);
|
let mut js_channels: Vec<JSAudioChannel> = Vec::with_capacity(number_of_channels as usize);
|
||||||
for _ in 0..number_of_channels {
|
for channel in 0..number_of_channels {
|
||||||
rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
|
rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
|
||||||
|
let offset = (channel * length) as usize;
|
||||||
let _ = unsafe {
|
let _ = unsafe {
|
||||||
Float32Array::create(cx, CreateWith::Slice(initial_data.as_slice()), array.handle_mut())
|
Float32Array::create(
|
||||||
|
cx,
|
||||||
|
CreateWith::Slice(&initial_data.as_slice()[offset..offset + (length as usize)]),
|
||||||
|
array.handle_mut())
|
||||||
};
|
};
|
||||||
let js_channel = Heap::default();
|
let js_channel = Heap::default();
|
||||||
js_channel.set(array.get());
|
js_channel.set(array.get());
|
||||||
|
@ -65,8 +77,9 @@ impl AudioBuffer {
|
||||||
pub fn new(global: &Window,
|
pub fn new(global: &Window,
|
||||||
number_of_channels: u32,
|
number_of_channels: u32,
|
||||||
length: u32,
|
length: u32,
|
||||||
sample_rate: f32) -> DomRoot<AudioBuffer> {
|
sample_rate: f32,
|
||||||
let buffer = AudioBuffer::new_inherited(global.get_cx(), number_of_channels, length, sample_rate);
|
initial_data: Option<&[f32]>) -> DomRoot<AudioBuffer> {
|
||||||
|
let buffer = AudioBuffer::new_inherited(global.get_cx(), number_of_channels, length, sample_rate, initial_data);
|
||||||
reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap)
|
reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +88,7 @@ impl AudioBuffer {
|
||||||
if options.numberOfChannels > MAX_CHANNEL_COUNT {
|
if options.numberOfChannels > MAX_CHANNEL_COUNT {
|
||||||
return Err(Error::NotSupported);
|
return Err(Error::NotSupported);
|
||||||
}
|
}
|
||||||
Ok(AudioBuffer::new(window, options.numberOfChannels, options.length, *options.sampleRate))
|
Ok(AudioBuffer::new(window, options.numberOfChannels, options.length, *options.sampleRate, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
|
|
@ -6,12 +6,15 @@ use dom::audiobuffer::AudioBuffer;
|
||||||
use dom::audiobuffersourcenode::AudioBufferSourceNode;
|
use dom::audiobuffersourcenode::AudioBufferSourceNode;
|
||||||
use dom::audiodestinationnode::AudioDestinationNode;
|
use dom::audiodestinationnode::AudioDestinationNode;
|
||||||
use dom::audionode::MAX_CHANNEL_COUNT;
|
use dom::audionode::MAX_CHANNEL_COUNT;
|
||||||
|
use dom::bindings::callback::ExceptionHandling;
|
||||||
use dom::bindings::cell::DomRefCell;
|
use dom::bindings::cell::DomRefCell;
|
||||||
use dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions;
|
use dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions;
|
||||||
use dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions;
|
use dom::bindings::codegen::Bindings::AudioNodeBinding::AudioNodeOptions;
|
||||||
use dom::bindings::codegen::Bindings::AudioNodeBinding::{ChannelCountMode, ChannelInterpretation};
|
use dom::bindings::codegen::Bindings::AudioNodeBinding::{ChannelCountMode, ChannelInterpretation};
|
||||||
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextMethods;
|
|
||||||
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState;
|
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::AudioContextState;
|
||||||
|
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::BaseAudioContextMethods;
|
||||||
|
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::DecodeErrorCallback;
|
||||||
|
use dom::bindings::codegen::Bindings::BaseAudioContextBinding::DecodeSuccessCallback;
|
||||||
use dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
|
use dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
|
||||||
use dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
|
use dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
|
||||||
use dom::bindings::error::{Error, ErrorResult, Fallible};
|
use dom::bindings::error::{Error, ErrorResult, Fallible};
|
||||||
|
@ -20,6 +23,7 @@ use dom::bindings::num::Finite;
|
||||||
use dom::bindings::refcounted::Trusted;
|
use dom::bindings::refcounted::Trusted;
|
||||||
use dom::bindings::reflector::DomObject;
|
use dom::bindings::reflector::DomObject;
|
||||||
use dom::bindings::root::DomRoot;
|
use dom::bindings::root::DomRoot;
|
||||||
|
use dom::domexception::{DOMErrorName, DOMException};
|
||||||
use dom::eventtarget::EventTarget;
|
use dom::eventtarget::EventTarget;
|
||||||
use dom::gainnode::GainNode;
|
use dom::gainnode::GainNode;
|
||||||
use dom::globalscope::GlobalScope;
|
use dom::globalscope::GlobalScope;
|
||||||
|
@ -28,20 +32,33 @@ use dom::promise::Promise;
|
||||||
use dom::window::Window;
|
use dom::window::Window;
|
||||||
use dom_struct::dom_struct;
|
use dom_struct::dom_struct;
|
||||||
use servo_media::ServoMedia;
|
use servo_media::ServoMedia;
|
||||||
|
use js::rust::CustomAutoRooterGuard;
|
||||||
|
use js::typedarray::ArrayBuffer;
|
||||||
use servo_media::audio::context::{AudioContext, ProcessingState};
|
use servo_media::audio::context::{AudioContext, ProcessingState};
|
||||||
use servo_media::audio::context::{OfflineAudioContextOptions, RealTimeAudioContextOptions};
|
use servo_media::audio::context::{OfflineAudioContextOptions, RealTimeAudioContextOptions};
|
||||||
|
use servo_media::audio::decoder::AudioDecoderCallbacks;
|
||||||
use servo_media::audio::graph::NodeId;
|
use servo_media::audio::graph::NodeId;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::collections::VecDeque;
|
use std::collections::{HashMap, VecDeque};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use task_source::TaskSource;
|
use task_source::TaskSource;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub enum BaseAudioContextOptions {
|
pub enum BaseAudioContextOptions {
|
||||||
AudioContext(RealTimeAudioContextOptions),
|
AudioContext(RealTimeAudioContextOptions),
|
||||||
OfflineAudioContext(OfflineAudioContextOptions),
|
OfflineAudioContext(OfflineAudioContextOptions),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable)]
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
struct DecodeResolver {
|
||||||
|
pub promise: Rc<Promise>,
|
||||||
|
pub success_callback: Option<Rc<DecodeSuccessCallback>>,
|
||||||
|
pub error_callback: Option<Rc<DecodeErrorCallback>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[dom_struct]
|
#[dom_struct]
|
||||||
pub struct BaseAudioContext {
|
pub struct BaseAudioContext {
|
||||||
eventtarget: EventTarget,
|
eventtarget: EventTarget,
|
||||||
|
@ -55,6 +72,8 @@ pub struct BaseAudioContext {
|
||||||
/// https://webaudio.github.io/web-audio-api/#pendingresumepromises
|
/// https://webaudio.github.io/web-audio-api/#pendingresumepromises
|
||||||
#[ignore_malloc_size_of = "promises are hard"]
|
#[ignore_malloc_size_of = "promises are hard"]
|
||||||
pending_resume_promises: DomRefCell<Vec<Rc<Promise>>>,
|
pending_resume_promises: DomRefCell<Vec<Rc<Promise>>>,
|
||||||
|
#[ignore_malloc_size_of = "promises are hard"]
|
||||||
|
decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
|
||||||
/// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate
|
/// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate
|
||||||
sample_rate: f32,
|
sample_rate: f32,
|
||||||
/// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state
|
/// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state
|
||||||
|
@ -85,6 +104,7 @@ impl BaseAudioContext {
|
||||||
destination: None,
|
destination: None,
|
||||||
in_flight_resume_promises_queue: Default::default(),
|
in_flight_resume_promises_queue: Default::default(),
|
||||||
pending_resume_promises: Default::default(),
|
pending_resume_promises: Default::default(),
|
||||||
|
decode_resolvers: Default::default(),
|
||||||
sample_rate,
|
sample_rate,
|
||||||
state: Cell::new(AudioContextState::Suspended),
|
state: Cell::new(AudioContextState::Suspended),
|
||||||
};
|
};
|
||||||
|
@ -296,7 +316,7 @@ impl BaseAudioContextMethods for BaseAudioContext {
|
||||||
return Err(Error::NotSupported);
|
return Err(Error::NotSupported);
|
||||||
}
|
}
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
Ok(AudioBuffer::new(&global.as_window(), number_of_channels, length, *sample_rate))
|
Ok(AudioBuffer::new(&global.as_window(), number_of_channels, length, *sample_rate, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unsafe_code)]
|
#[allow(unsafe_code)]
|
||||||
|
@ -306,6 +326,78 @@ impl BaseAudioContextMethods for BaseAudioContext {
|
||||||
let options = unsafe { AudioBufferSourceOptions::empty(global.get_cx()) };
|
let options = unsafe { AudioBufferSourceOptions::empty(global.get_cx()) };
|
||||||
AudioBufferSourceNode::new(&global.as_window(), &self, &options)
|
AudioBufferSourceNode::new(&global.as_window(), &self, &options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(unrooted_must_root)]
|
||||||
|
fn DecodeAudioData(&self,
|
||||||
|
audio_data: CustomAutoRooterGuard<ArrayBuffer>,
|
||||||
|
decode_success_callback: Option<Rc<DecodeSuccessCallback>>,
|
||||||
|
decode_error_callback: Option<Rc<DecodeErrorCallback>>)
|
||||||
|
-> Rc<Promise> {
|
||||||
|
// Step 1.
|
||||||
|
let promise = Promise::new(&self.global());
|
||||||
|
|
||||||
|
if audio_data.len() > 0 {
|
||||||
|
// Step 2.
|
||||||
|
// XXX detach array buffer.
|
||||||
|
let uuid = Uuid::new_v4().simple().to_string();
|
||||||
|
let uuid_ = uuid.clone();
|
||||||
|
self.decode_resolvers.borrow_mut().insert(uuid.clone(), DecodeResolver {
|
||||||
|
promise: promise.clone(),
|
||||||
|
success_callback: decode_success_callback,
|
||||||
|
error_callback: decode_error_callback,
|
||||||
|
});
|
||||||
|
let audio_data = audio_data.to_vec();
|
||||||
|
let decoded_audio = Arc::new(Mutex::new(Vec::new()));
|
||||||
|
let decoded_audio_ = decoded_audio.clone();
|
||||||
|
let this = Trusted::new(self);
|
||||||
|
let this_ = this.clone();
|
||||||
|
let callbacks = AudioDecoderCallbacks::new()
|
||||||
|
.eos(move || {
|
||||||
|
let this = this_.root();
|
||||||
|
let decoded_audio = decoded_audio.lock().unwrap();
|
||||||
|
let buffer = AudioBuffer::new(
|
||||||
|
&this.global().as_window(),
|
||||||
|
1, // XXX servo-media should provide this info
|
||||||
|
decoded_audio.len() as u32,
|
||||||
|
this.sample_rate,
|
||||||
|
Some(decoded_audio.as_slice()));
|
||||||
|
let mut resolvers = this.decode_resolvers.borrow_mut();
|
||||||
|
assert!(resolvers.contains_key(&uuid_));
|
||||||
|
let resolver = resolvers.remove(&uuid_).unwrap();
|
||||||
|
if let Some(callback) = resolver.success_callback {
|
||||||
|
let _ = callback.Call__(&buffer, ExceptionHandling::Report);
|
||||||
|
}
|
||||||
|
resolver.promise.resolve_native(&buffer);
|
||||||
|
})
|
||||||
|
.error(move || {
|
||||||
|
let this = this.root();
|
||||||
|
let mut resolvers = this.decode_resolvers.borrow_mut();
|
||||||
|
assert!(resolvers.contains_key(&uuid));
|
||||||
|
let resolver = resolvers.remove(&uuid).unwrap();
|
||||||
|
if let Some(callback) = resolver.error_callback {
|
||||||
|
let _ = callback.Call__(
|
||||||
|
&DOMException::new(&this.global(), DOMErrorName::DataCloneError),
|
||||||
|
ExceptionHandling::Report);
|
||||||
|
}
|
||||||
|
resolver.promise.reject_error(Error::Type("Audio decode error".to_owned()));
|
||||||
|
})
|
||||||
|
.progress(move |buffer| {
|
||||||
|
decoded_audio_
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.extend_from_slice((*buffer).as_ref());
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
self.audio_context_impl.decode_audio_data(audio_data, callbacks);
|
||||||
|
} else {
|
||||||
|
// Step 3.
|
||||||
|
promise.reject_error(Error::DataClone);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4.
|
||||||
|
promise
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ProcessingState> for AudioContextState {
|
impl From<ProcessingState> for AudioContextState {
|
||||||
|
|
|
@ -12,8 +12,8 @@ enum AudioContextState {
|
||||||
"closed"
|
"closed"
|
||||||
};
|
};
|
||||||
|
|
||||||
// callback DecodeErrorCallback = void (DOMException error);
|
callback DecodeErrorCallback = void (DOMException error);
|
||||||
// callback DecodeSuccessCallback = void (AudioBuffer decodedData);
|
callback DecodeSuccessCallback = void (AudioBuffer decodedData);
|
||||||
|
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
interface BaseAudioContext : EventTarget {
|
interface BaseAudioContext : EventTarget {
|
||||||
|
@ -27,9 +27,9 @@ interface BaseAudioContext : EventTarget {
|
||||||
[Throws] AudioBuffer createBuffer(unsigned long numberOfChannels,
|
[Throws] AudioBuffer createBuffer(unsigned long numberOfChannels,
|
||||||
unsigned long length,
|
unsigned long length,
|
||||||
float sampleRate);
|
float sampleRate);
|
||||||
// Promise<AudioBuffer> decodeAudioData(ArrayBuffer audioData,
|
Promise<AudioBuffer> decodeAudioData(ArrayBuffer audioData,
|
||||||
// optional DecodeSuccessCallback successCallback,
|
optional DecodeSuccessCallback successCallback,
|
||||||
// optional DecodeErrorCallback errorCallback);
|
optional DecodeErrorCallback errorCallback);
|
||||||
AudioBufferSourceNode createBufferSource();
|
AudioBufferSourceNode createBufferSource();
|
||||||
// ConstantSourceNode createConstantSource();
|
// ConstantSourceNode createConstantSource();
|
||||||
// ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0,
|
// ScriptProcessorNode createScriptProcessor(optional unsigned long bufferSize = 0,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue