Skip to content

Comments

Refactored to have apply_volume in a specifix mixer#148

Merged
plietar merged 18 commits intoplietar:masterfrom
romerod:feature/mixer
Feb 21, 2017
Merged

Refactored to have apply_volume in a specifix mixer#148
plietar merged 18 commits intoplietar:masterfrom
romerod:feature/mixer

Conversation

@romerod
Copy link
Contributor

@romerod romerod commented Jan 20, 2017

This is still work in progress, are you interested in having multiple mixer support?

I started by refactoring the code to have a Mixer trait which is passed to Player.

I verified the code on rpi2 and it works, i'm a complete rust newb so don't hang me for weird code.

TODO:

  • call init(), inuse() and release on the mixer
  • add possibility for the mixer to notify the player that the volume changed.
  • add mixer implementation which allows external process to be responsible for mixing (any ideas for IPC?)

@plietar
Copy link
Owner

plietar commented Jan 20, 2017

Thanks for working on this, this is an often requested feature.

I don't think the API as it is now would allow for hardware mixers. The apply_volume doesn't make any sense in that case.

The best is probably to merge AudioBackend and Mixer. They are closely linked. AudioBackend's which don't support hardware mixing can just fallback to software as it is now.

@romerod
Copy link
Contributor Author

romerod commented Jan 20, 2017

I know that apply_volume doesn't make sense in any but the SoftwareMixer case, that's way the default implementation just returns the unaltered data.

I don't like the idea of putting the audio backend and the mixer together. I've the same use case as @herrernst in #43 where volume will be controlled on an amplifier which I'll remote control with CEC.

Having them separated makes it possible to run different mixers with different backend.

Having the possibility to delegate the "mixing" to an external process would make it possible to use amixer for example without the need to manually implement that.

@plietar
Copy link
Owner

plietar commented Jan 22, 2017

Right, I hadn't thought of that use case and it makes more sense now. Thanks for clarifying. I think the API is reasonable like that.

It may make sense the split the Mixer trait into a VolumeControl and a VolumeApplier (terrible names), which share an Arc<AtomicUsize>. The former is owned by SpircManager and the later by the Player. This way the player does not have to worry about volume control, and this can be left to the SpircManager.

  • add possibility for the mixer to notify the player that the volume changed.
  • add mixer implementation which allows external process to be responsible for mixing (any ideas for IPC?)

I think this should be left for other PRs. I also have a refactor coming up which would impact the way these are done.

I don't have a candidate for IPC. Is there a precedent for volume control ? Is writing the volume to the process' stdin enough ?

src/mixer/mod.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Call that set_volume

src/mixer/mod.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary ? Initialisation can happen when constructing the Mixer object

src/mixer/mod.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start/stop seem like better names

@romerod
Copy link
Contributor Author

romerod commented Jan 26, 2017

I've started splitting it up a bit differently. I introduced the StreamEditor trait which can be optionally passed to the Player. The current volume is owned by the Mixer. Are you ok with this? Currently the state of the device isn't read anymore by the SpircManager [WIP].

Copy link
Owner

@plietar plietar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good, couple of nits.
AudioFilter sounds like a better name than StreamEditor. Maybe one day we can have other type of filters.

I'm confused about the changes in src/spirc.rs and why they're needed, beyond just adding a mixer: Box<Mixer + Send> to the SpircManager and calling it when appropriate.

Finally, a device can disable volume changing (probably by not advertising the kVolumeSteps in spirc). This could be done if none is used as a mixer.
That can be left for another PR though.

src/main.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mixer::find should take an Option<&str>, and take care of choosing a default rather than picking it here, similar to how the audio backend is done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/main.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use .expect("Invalid mixer") so it is more obvious in case of an error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/main.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move mixer.get_stream_editor() to a variable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/mixer/mod.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brace should go at the end of the line.
If the line is too long, use

fn get_stream_editor(&self)
    -> Option<Box<StreamEditor + Send>>
{

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/mixer/mod.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is init needed ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just store the Arc<RwLock<u16>> in there, no need for a closure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason to allow the SoftVolumeApplier to modify the value, i was looking to be able to get a ReadLock from the RwLock but didn't find one. That's why i decided to use the closure

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The privacy boundary is usually the module. As long as it isn't possible to modify the volume unexpectedly from outside the module, that's fine.
I find the closure a lot more confusing than storing the Arc<RwLock<u16>>

Copy link
Contributor Author

@romerod romerod Feb 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That line is too long

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/player.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you remove this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's tied to the changes i made in spirc, there is a new struct "State" which contains the whole system state (Player and Mixer) so I thought the update_time belongs to the State not the PlayerState

src/player.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be editor.modify_stream(&mut packet.data) to modify in place.
(See my comment above)

src/spirc.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my previous answer about why I removed the update_time, I think when I continue with the changes it will become clear

Copy link
Owner

@plietar plietar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need a channel to send volume change updates back to the SpircManager ?
Volume change is only done by spirc anyway, so you can just do self.notify(false, None); when a kMessageTypeVolume message has been received.

There shouldn't be any changes needed to mercury, and it should definitely not depend on the spirc module.
I'm also not sure what the implement_sender macro does and why it's needed.
I'm also still not convinced about why the State struct in player is needed, and why this existing code wasn't sufficient.

Sorry for requesting all these changes, but I found the implementation very confusing and overly complicated.

src/player.rs Outdated
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, using Some(OK(mut packet)) should be enough to avoid the copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try that, thanks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@romerod
Copy link
Contributor Author

romerod commented Feb 3, 2017

There are multiple reasons I used a channel to send volume change notifications:

  • The end goal I still have (not for this PR) is to be able to create a mixer which controls an external amplifier, the external amplifier is controllable by other means too, like the remote control

  • I didn't want to alter the current way it works. Until now the player was called from the spirc to change the volume, and the player informed the spirc thru the add_observer functionality.

  • I didn't want to introduce an additional thread to listen to this changes and I like the way you used the channel to communicate between the spirc and mercury. I extended this approach to be able to share the channel between the spirc and multiple senders, with multiple message types. By following this approach it would be possible to get rid of the Arc<Mutex<>> arround the SpircInternal and just keep a reference to a "CommandSender" and pass a "PlayerStateSender" to the player without week pointers. If you want to take a look at how I came up with the macro take a look at https://repl.it/F1PY/9

I know I modified quite a lot of things, but I didn't want to introduce additional threads and weak pointers.

The dependencies between spirc and mercury were less than ideal, I know, i'd suggest something like the last commit, what do you think? It might be cleaner to move the SpircMessage enum back to spirc, but then the macro would need to create a trait which would need to get boxed to be passed arround etc. I thought it is simpler like that.

Don't worry about asking to much changes, the idea is to get something good out of it, right?

The state part is not finished, TODO on line 435 of spirc.

@plietar
Copy link
Owner

plietar commented Feb 3, 2017

Ok the changes to channel stuff make more sense. It's a shame the select! macro isn't stable.
I have a refactor in progress to use tokio and future-rs which would make this a lot simpler though.

I suggest leaving out the volume updates from this PR, especially since it is not needed for software volume. It can be implemented in a much cleaner way once I'm done with the refactor.

@romerod
Copy link
Contributor Author

romerod commented Feb 3, 2017

Hmm, looked at the select! macro, but I don't think it serializes incoming messages as the solution does so you would still have to use locks, which make it much more difficult to know at what point who has to wait on what. Do you see the possible simplification of passing a PlayerStateSender to the instead of the observable stuff and removing the Arc<Mutex<>>?

@romerod
Copy link
Contributor Author

romerod commented Feb 3, 2017

I removed that part of the code (it hurts a bit ;-) ).

As long as the player is playing changes in the mixer will still be reflected as updates are sent to the server, so I can live with that.

@romerod romerod changed the title [WIP] Refactored to have apply_volume in a specifix mixer Refactored to have apply_volume in a specifix mixer Feb 3, 2017
@romerod
Copy link
Contributor Author

romerod commented Feb 19, 2017

Is there something missing?

@sashahilton00
Copy link
Contributor

@plietar pinging incase of forgotten PR.

Copy link
Owner

@plietar plietar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I thought this was still in WIP.
I still don't understand why you need an extra State type ?


pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<Box<Mixer + Send>> {
match name {
_ => Some(Box::new(SoftMixer::new())),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only return SoftMixer if soft was given as an argument (or None for default).


impl Player {
pub fn new<F>(session: Session, sink_builder: F) -> Player
pub fn new<F>(session: Session, stream_editor: Option<Box<AudioFilter + Send>>, sink_builder: F) -> Player
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename the variable to audio_filter


impl PlayerInternal {
fn run(self, mut sink: Box<Sink>) {
fn run(self, mut sink: Box<Sink>, stream_editor: Option<Box<AudioFilter + Send>>) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, change variable name

@plietar plietar merged commit 10f9da4 into plietar:master Feb 21, 2017
plietar added a commit that referenced this pull request Feb 21, 2017
@plietar
Copy link
Owner

plietar commented Feb 21, 2017

I've made a few fixes and merged. Thanks @romerod

@romerod
Copy link
Contributor Author

romerod commented Feb 22, 2017

Cool, thank you

@plietar plietar mentioned this pull request Mar 6, 2017
mjaggard pushed a commit to mjaggard/librespot that referenced this pull request Mar 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants