Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audio decoder #52

Merged
merged 7 commits into from Jun 19, 2018
Merged

Audio decoder #52

merged 7 commits into from Jun 19, 2018

Conversation

@ferjm
Copy link
Member

ferjm commented Jun 13, 2018

Fixes #41

.map_err(|_| ())
};

if !is_audio || insert_sink().is_err() {

This comment has been minimized.

@sdroege

sdroege Jun 13, 2018

Contributor

You probably also do not want to add a new sink for each audio stream (there could be multiple), but only take one. Or do you want to take them all?

});

let appsrc = appsrc.downcast::<AppSrc>().map_err(|_| ())?;
appsrc.set_property_format(gst::Format::Time);

This comment has been minimized.

@sdroege

sdroege Jun 13, 2018

Contributor

Time format requires you to properly timestamp the data you put into the appsrc for each buffer. You probably can't do that, so set it to bytes instead :)

appsrc_
.push_buffer(buffer)
.into_result()
.expect("pushing buffer failed");

This comment has been minimized.

@sdroege

sdroege Jun 13, 2018

Contributor

This should be cleanly handled instead of just panicking

buffer
.copy_from_slice(0, &data[offset..next_offset])
.expect("copying failed");
offset = next_offset;

This comment has been minimized.

@sdroege

sdroege Jun 13, 2018

Contributor

You could use std::io::Cursor and Read on the Vec to prevent having to keep track of the offset yourself

@ferjm ferjm changed the title [WIP] Audio decoder Audio decoder Jun 18, 2018
@ferjm
Copy link
Member Author

ferjm commented Jun 18, 2018

With these patches I am able to decode and play an .mp3 file, but with very poor quality (lots of noise). I am not sure why yet.

@philn
Copy link
Collaborator

philn commented Jun 18, 2018

Seems like you need a capsfilter before the appsink so that the audio samples are converted to the right format expected by the backend.

@sdroege
Copy link
Contributor

sdroege commented Jun 18, 2018

Seems like you need a capsfilter before the appsink so that the audio samples are converted to the right format expected by the backend.

Or you set the caps property on appsink accordingly. That has the same effect and is fewer elements

)),
);
graph.resume();
thread::sleep(time::Duration::from_millis(5000));

This comment has been minimized.

@sdroege

sdroege Jun 18, 2018

Contributor

Why?

This comment has been minimized.

@ferjm

ferjm Jun 18, 2018

Author Member

Because if the graph is dropped, the rendering thread is closed and we won't hear the audio playback.

@ferjm
Copy link
Member Author

ferjm commented Jun 18, 2018

With the appsink caps set, the sound is perfect. Thank you!

Copy link
Member

Manishearth left a comment

Looks okay. I don't understand gstreamer enough to comment much on the actual decode implementation but Sebastian and Philippe seem to be helping with that 😄


/// Asynchronously decodes the audio file data contained in the given
/// buffer.
pub fn decode_audio_data(&self, data: Vec<u8>, callbacks: AudioDecoderCallbacks) {

This comment has been minimized.

@Manishearth

Manishearth Jun 18, 2018

Member

We should eventually split out the GStreamer stuff as a separate crate with traits, but that's going to be annoying for now so we don't need to do it yet 😄

(it's also low priority)

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

We already have the AudioSink and AudioDecoder traits, so it shouldn't be too hard ™ :)

.extend_from_slice((*buffer).as_ref());
})
.build();
graph.decode_audio_data(bytes.to_vec(), callbacks);

This comment has been minimized.

@Manishearth

Manishearth Jun 18, 2018

Member

Should we eventually make it such that this can be plugged into a source element on the render thread side, or do we want this to be separate to make the network stuff work?

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

For media elements (and MediaElementAudioSourceNodes) we will likely use a different approach and use GstPlayer directly (which handles all the decoding work internally), check #43.

For MediaStream*AudioSourceNodes, I am not so sure yet, I still need to read more of these specs, but in general, I have the feeling that all decoding should happen outside of any WebAudio source element, and certainly off the render thread.

In any case, AudioDecoder and GStreamerAudioDecoder are meant to be reusable.

This comment has been minimized.

@sdroege

sdroege Jun 19, 2018

Contributor

and certainly off the render thread.

Note that this is trivial with GStreamer, so shout when you need to move things to different threads, etc and I'll help :)

println!("Decoding audio");
receiver.recv().unwrap();
println!("Audio decoded");
let buffer_source = graph.create_node(AudioNodeType::AudioBufferSourceNode);

This comment has been minimized.

@Manishearth

Manishearth Jun 18, 2018

Member

How do we plan to make this work with ongoing network fetches? Perhaps a BufferSourceNode you can message extra buffers to?

(not something we need to solve now, just want your thoughts)

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

AFAICT AudioBufferSourceNodes are meant for in-memory audio assets. For network-backed assets the spec suggests the usage of AudioWorkletNodes, which is a completely different war :).

let pipeline_ = pipeline.clone();
let pipeline__ = pipeline.clone();
let callbacks_ = callbacks.clone();
let callbacks__ = callbacks.clone();

This comment has been minimized.

@Manishearth

Manishearth Jun 18, 2018

Member

not too happy about this but this seems to make sense 😄

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

Yeah, I am not too happy either, but I couldn't think of a better approach :\

This comment has been minimized.

@sdroege

sdroege Jun 19, 2018

Contributor

You could use a macro like clone_army! https://docs.rs/closet/0.2.2/closet/macro.clone_army.html

But it's all not very nice, not sure if this can be improved in any way though unless some kind of auto-cloning is added to the language (please no)

@ferjm
Copy link
Member Author

ferjm commented Jun 19, 2018

Thanks for the review and feedback, folks!

@ferjm ferjm merged commit 5f16ef8 into servo:master Jun 19, 2018
@ferjm ferjm deleted the ferjm:decoder branch Jun 19, 2018
.error(|| {
eprintln!("Error decoding audio");
})
.progress(move |buffer| {

This comment has been minimized.

@sdroege

sdroege Jun 19, 2018

Contributor

Is there another user for this already apart from the example? Some code that does not actually copy all the samples directly but reuses the slice?

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

Not yet. I'm working on the DOM bindings using this code.

let appsink = sink.clone().dynamic_cast::<AppSink>().map_err(|_| ())?;

// XXX Issue #18 channels support.
let audio_info = gst_audio::AudioInfo::new(gst_audio::AUDIO_FORMAT_F32, 44100, 1)

This comment has been minimized.

@sdroege

sdroege Jun 19, 2018

Contributor

If 44100 Hz are not required, 48000 might be nicer... or can the sample rate even be signalled somewhere via webaudio so you wouldn't have to constrain it here?

This comment has been minimized.

@ferjm

ferjm Jun 19, 2018

Author Member

Yeah, the sample rate should probably be given by the AudioContext. I'll write a patch for this.

This comment has been minimized.

@sdroege

sdroege Jun 19, 2018

Contributor

You then want to set the caps similar to this (and possibly later also allow a different number of channels): https://github.com/sdroege/gstreamer-rs/blob/db3fe694154c697afdaf3efb6ec65332546942e0/examples/src/bin/appsink.rs#L54-L62

@ferjm ferjm mentioned this pull request Jun 20, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.