mikebrady / shairport-sync

AirPlay and AirPlay 2 audio player
Other
7.2k stars 571 forks source link

Manual volume control / local volume control #983

Closed sfeakes closed 3 years ago

sfeakes commented 4 years ago

First, thanks for a great project. I wanted local volume control via the use of a rotary encoder connected to the pi, so I've created a small daemon to read the encoder through the GPIO ports and it publishes MQTT messages to the Shairport-sync /remote topic. (volume up/down & mute) https://github.com/sfeakes/RotaryEncoder/

All works fine but obviously a little bit of a delay over MQTT, I was just wondering if there was a better way to do this. Had a quick look over the Shairport-sync code as I was thinking of forking & modifying that, but seems the MQTT interface simply send a DACP command to the server (is that right?). If so it would seem I can cut out a lot (of stuff in the middle) by simply connecting to the DACP server directly and sending the command(s). But I'm also not sure on the DACP protocol but I'm sure it would probably mean registering as a remote and a huge hassle that comes with that, and not sure it there would be a huge gain. Anyway any help would be much appreciated.

mikebrady commented 4 years ago

Thanks for the interesting post. You are correct that Shairport Sync relays MQTT to DACP commands. Can you say what commands you are using please?

As to where there might be delays, I don't think there's a huge delay in the DACP-to-player part, but I don't know about the MQTT system. So a possible approach might be to look at the D-Bus interface, to see if it's any quicker.

More broadly, what would be great, but exceedingly tricky, would be to get your encoder to directly control the ALSA mixer (that would have no lag) and then to get that back to the player. Very tricky to do right.

sfeakes commented 4 years ago

Thanks for the reply and direction. The commands I'm using are simply posting 'volumeup' 'voluomedown' & 'mutetoggle' to the MQTT Topic '...../remote'. I haven't dug enough into the code to see what DACP commands they get translated to. There really isn't any noticable lag on the first command, but spinning the rotaryencoder several clicks will trigger multiple volume commands and then there is a little lag for the last to be executed. I'm sure this is because it triggers a lot of synchronous calls considering all the different protocols / connections in the loop, MQTT doesn't seem to be the bottle neck here, I can see those getting executed and received instantly.

There is a lot in the whole chain local player->MQTT server->local player->music server->local player.

Looks like forked-daapd has some REST API's to call, that may make it a touch quicker, but then implementation is very specific to using forked-daapd as server, and I'm not really sure

I'll take a look at the ALSA mixer, never played with that before so not sure now to interact with it.

mikebrady commented 4 years ago

Thanks. Actually there has been a development that might be of interest. Recently a new command was added that allows one to set the volume of the player directly, rather than use the VolumeUp/VolumeDown stuff. Let me dig in and see how to expose it in MQTT. It might simplify and speed up the control.

sfeakes commented 4 years ago

Thanks, I did made a quick modification to hit forked-daapd API's directly and it's much quicker. I'll have to do some more digging to find out exactly were the latency resides.

The volume up / down actually works well, as a rotary encoder has no upper / lower limit like a traditional potentiometer style volume control. So I've coded it to increases and decreases relative to the volume current value, meaning I can also set from all the other options and don't need to worry about the dial/knob looking funny as it's position is out of sync with the actual volume.

Still need to look into the ALSA mixer option, but at the moment with my network & setup you really see no difference (in lag) between it and a traditional volume control.

mikebrady commented 4 years ago

Thanks. Can you expand a little on what you mean when you write "to hit forked-daapd API's" please?

It sounds like VolumeUp/VolumeDown is exactly the right thing for the rotary encoder alright.

sfeakes commented 4 years ago

forked-daapd offers some REST API's that you can use to set volume as well, and since I'm using that as my DAAP server I figured I hit them directly.

ie, a PUT request to the below URL.
http://<daapd-server>/api/player/volume?step=+5&output_id=11259997263078
does the same as posting volumeup on the Shairport-sync/remote MQTT topic

< where output_id is the shairport-sync clientID>

This takes a few protocols & hops out of the chain of commands. MQTT is. encoder->MQTT server->shairport-sync->forked-daapd->shairport-sync vs encoder->forked-daapd->shairport-sync.

I prefer the MQTT method as it's far more resilliant than API's and will work on any system (irreverent of DAAP server software).

I'll need to start digging through the code to see exactly how volume is set in shairport-sync, as at the moment I have not read up on the DACP/DAAP protocol. It seems when shairport-sync receives MQTT volumeup, it posts that to the server using DACP, then waits for the server to send a separate message back (DACP I assume) before it actually sets the volume. Would be interesting to see if the (DACP) protocol supports shairport-sync setting the volume as soon as it received the MQTT message, then sending a message (DACP) with some from of "I've already set the volume to X" message.

Again, I'm guessing on how this is working as I haven't dome my homework on DACP/DAAP protocols yet and only skimmed over the shairport-sync code.
Again, thanks for this great work, I cannot believe I haven't tried this sooner.

mikebrady commented 4 years ago

Many thanks for the information. Your understanding of how volume is set in Shairport Sync via MQTT is exactly right. And your idea of setting the volume directly while telling the player via DACP is more or less what I was thinking of, except that you can't stop the player from sending the new volume back to Shairport Sync. The trick is to figure out how and when to ignore it. Unfortunately, we can't rely on the new volume being exactly what we asked for, so there'll have to be some tolerance of a small difference. Tricky.

github-actions[bot] commented 3 years ago

This issue has been inactive for 60 days so will be closed 7 days from now. To prevent this, please remove the "stale" label or post a comment.