owntone / owntone-server

Linux/FreeBSD DAAP (iTunes) and MPD audio server with support for AirPlay 1 and 2 speakers (multiroom), Apple Remote (and compatibles), Chromecast, Spotify and internet radio.
https://owntone.github.io/owntone-server
GNU General Public License v2.0
2.06k stars 236 forks source link

Meta Data Pipe from shairport-sync #278

Closed ABrolund closed 7 years ago

ABrolund commented 8 years ago

Hi ejurgensen, i am using shairport-sync and forked-daapd as airplay distribution system in short one to many at home. It works nice and smoothly, so I really love to say: Great work !!

Well, i have seen that mikebrady implemented a metadata-pipe, to make metadata accessible when you are for example using a pipe as output. It would be great to have the metadata visible in remote app for a pipe. Is this something you could do ?

Thx Andreas

ejurgensen commented 8 years ago

Could you elaborate a bit on the kind of setup you are thinking about? Not sure I quite understand.

ABrolund commented 8 years ago

Hi, I apologize for not being clear. Giving an example: When using the the remote app, I select the pipe from shairport to forward audio to multiple speaker via Airplay. Of course I cannot see any metadata like song title or album picture as the pipe contains only audio data. My understanding is that shairport has now implemented a metadata pipe which contains this information, but only this information and no audio data. So forked-daapd would need to parse two pipes one for audio and one for metadata. This way you could show all informations in Remote app for the pipe, same like playing an MP3 file.

Hope I could make my point clearer.

ejurgensen commented 8 years ago

Ok, I get it now. I'll add it to the list of possible improvements. When it could be done is hard to say.

snizzleorg commented 8 years ago

@ABrolund how exactly do you do that? How do you select the SharePoint pipe? My understanding is that you then can stream to all speakers from an iPhone? Is that correct? Sounds like a interesting application... given that the normal airplay only supports one speaker at a time...

ABrolund commented 8 years ago

@ejurgensen : i found a sample implementation for metadata pipe, maybe it helps implementing it: https://github.com/mikebrady/shairport-sync-metadata-reader @snizzleorg: Use https://github.com/mikebrady/shairport-sync and configure it to output to a pipe. Make sure the pipe is in the path from forked-daapd. You will find it in Apples Remote app. In Apple Remote App you can select more then one Airplay device. Alternatively you can use mpc (a linux command line MPD (Music Player Daemon)) to control playing and outputs via command line (good for scripts).

ABrolund commented 7 years ago

Hi, as have C-Development background, I am thinking about implementing the feature end of fall during my parental leave. Hard to promise, but anyway. To make sure that it is in your interest merging it afterwards, I like to sketch up where and how to implement it with you. Two options in my mind.

Option 1: Implementing metadata reader in forked-daapd and put an additional tag in conf-file for the meta data pipe. Option 2: In case you do not want code from other projects in forked-daapd: saving metadata next to pipe in e.g. JPEG and XML and picking it up from there.
what do you think ?

ejurgensen commented 7 years ago

I'm always interested in contributions, especially if you know your way around C and are willing to maintain your contribution. Of course, I also have opinions on implementation, so it is always good to discuss first, just like you are doing.

I don't mind having this in forked-daapd, in fact I started on it a while ago, but then I had to do some other things instead. Basically, my approach was looking for a pipe in the same dir as the audio pipe, but with a ".metadata" suffix. It wouldn't require a config option, I think.

What needs some careful thought is how to get the metadata to the player (see player.c). The player thread opens and reads the audio pipe (through pipe.c), and the thread must of course not block while playing. I suppose it could also open the metadata pipe, but it would probably not be good use it for reading. One option is having a new, dedicated thread reading the metadata pipe and passing it on, but I'm not really thrilled about more threads. Another option is maybe a combination of polling and the worker thread, like forked-daapd does for radio streams.

ABrolund commented 7 years ago

Hi, agreed will try to implement with worker thread. Just checking through Mike Brady's shairport-sync-metadata-reader and I am wondering whether I need to decode anything at all, as source metadata is coming from Airplay and finally going to it. Sure I need to check for the start tag, but that's it? What do you think ? But I also have to admit that I do not understand the metadata workflow right now. So might need to do some more digging.

ejurgensen commented 7 years ago

I agree, it doesn't look like much decoding is required. Just base64, but that is already in forked-daapd, (misc.h) so you can just use that. You also have mxml available, if you want to use that for parsing the xml structure, but you could probably do something by hand as well. That's up to you.

Yes, the metadata flow is complex. A good thing to know is that whenever forked-daapd sends metadata to a client it always* comes from the database. This is for historic reasons, because forked-daapd used to only support metadata from a library on a filesystem, which would be pretty static. I never bothered changing this. So your goal is to read the metadata and then write it to the database - from the right thread, which would probably be the worker thread. When the metadata is written to the db, a status_update() must be made from the player thread. This should push the new metadata to the clients.

Note all of the above is from memory, so can't promise it's 100% accurate.

*The artwork, however, is not relayed through the db, so that probably needs special handling. I suggest you begin with the text, and then do artwork afterwards.

chme commented 7 years ago

Just an idea ... If the goal is to turn forked-daapd into an airplay target, another approach could be to modify shairport-sync so that it can be used as a library. forked-daapd could include this library and would have direct access to the received data-packets. (xbmc used the original shairport version as a library for their airplay support before they switched to shairplay. https://github.com/juhovh/shairplay offers a lib afaik, maybe this would also be an option)

noelhibbard commented 7 years ago

I recently wrote a simple node/express shairport-sync now playing webui and it's really easy to get the album/artist/title and art out of it. One thing I didn't mess with was the track length and position.

Here are the "codes" to watch for: asal = album asar = artist minm = title PICT = album art (so far I've only seen png)

In addition to reading the metadata pipe from shairport-sync, you can also configure shairport-sync to stream the metadata over UDP. The only problem with this is it doesn't transfer the album art. I am guessing Mike did this because he didn't feel like building a protocol to packetize the album art. I wrote a UDP version of my metadata reader and to get the album art I simply queried the iTunes website.

snizzleorg commented 7 years ago

@ABrolund How exactly do you name the pipe? does it need to have an mp3 ending? Also Do you need to have something in the pipe to actually be able to play it through forked-daapd?

noelhibbard commented 7 years ago

@ABrolund, there are two pipes that you can output from shairport-sync. This thread is talking about the metadata pipe. This thread has nothing to do with audio playback. It simply contains the track info and album art. I think what you are asking about is the pipe that contains the audio. You don't need a file extension on this pipe for it to playback in forked-daapd. Just make sure you specify a path that is visible in forked-daapd. For example the default path for forked-daapd is /srv/music. So a command line like this would work for you: shairport-sync -a "forked-daapd" -o pipe -- /srv/music/AirPlay

snizzleorg commented 7 years ago

@noelhibbard I think this was addressed to me (@snizzleorg) - Thanks. Thats what I did. I'm not home at the moment so I can't select the pipe as a source via the remote app. I tried through mpd (mpc - actually and that gave me an error). Will have to try when I'm actually home in 2 weeks

noelhibbard commented 7 years ago

@snizzleorg sorry I selected the wrong person in that reply. I also had some difficulty seeing it in the Apple Remote app at first too. If I recall I simply restarted forked-daapd so it did a rescan. It's like the auto scanning isn't triggered properly when the pipe is first created. Once it showed for the first time it has always been on the list.

snizzleorg commented 7 years ago

@noelhibbard Does the pipe needs to be filled or can I just start playback on an empty pipe?

mpc -h 192.168.178.94 play file:/media/shairport

give this. The pipe is empty (since I cannot at the moment put anything into it)

error parsing song numbers from: file:/media/shairport
volume:  1%   repeat: off   random: off   single: off   consume: off
noelhibbard commented 7 years ago

@snizzleorg I can't recall what it does on an empty pipe. I didn't spend much time with forked-daapd/shairport-sync before scrapping the whole idea and creating a project of my own in Node. I really only wanted a way to AirPlay from my phone to the whole house. So forked-daapd and the Apple Remote app was a little over kill. I am discussing my project here: https://github.com/mikebrady/shairport-sync/issues/384

When I get home tonight I will fire shairport-sync/forked-daapd back up and see what I get. I do recall having problems with playback failing if there isn't an output selected in forked-daapd. But in the remote app you can select an output until playback starts so it's a catch 22.

snizzleorg commented 7 years ago

@noelhibbard This project sounds interesting. Probably a good idea to separate the two use-cases and use your Node Thingy for the whole house airplay... Let me know If you have something ready for release...

ejurgensen commented 7 years ago

You should be able to start playback on empty pipes. Otherwise it would indeed be catch 22. The mpd message above looks like a bug. Will try later.

ejurgensen commented 7 years ago

Just tried, I could add the pipe and play from it as expected (even though it was empty). Also, the mpd error above is not a bug. The argument for "mpc play" is not a path, but an (optional) playlist position. So to play a pipe you should do something like "mpc add [path]" and then "mpc play". Maybe there are other ways too, check the man page for mpc.

snizzleorg commented 7 years ago

thanks. I'll give it a try.

ABrolund commented 7 years ago

Sorry for my late answers here: @chme: Thx for your input, but this is looking like a quite complex implementation I am a bit fearing to touch. The metadata pipe read is very limited and the pipe support from forked-daapd is very stable. At the end it is doing the same thing. @snizzleorg: I assume your question was answered, you can give the pipe any name you want. My pipe from shaiport-sync is named "airplay" while the metadata pipe is called airplay.metadata. @noelhibbard: Right I am looking for the metadata pipe. By the way if you compile a newer version of shairport you can configure the pipe in the conf file, you do not need to pass it via command line. @ejurgensen: A small project status for you. I managed to compile a version of forked daapd with pipe metadata support, but it stores the album,artist &title in the log file only. So it is not useful for the community for now. Two open problems I need to solve: -worker thread versus thread. I am reading the pipe currently in blocked mode, which is very convenient for retrieving metadata . The reading stops at the right place for the next track info. But it this is stopping the working thread as well and of course it is causing strange behavior, so for now it seems to me more reasonable to go with a separated thread. I was experimenting with nonblocking reading, but I found that I rather miss data or need to implement a sync mechanism. The code looks cleaner doing blocked reading in my opinion. I did not find any end or begin maker reading an audio track from a pipe, is this correct? -Output of metadata. no plan yet, when point one is stable I will look into it

ejurgensen commented 7 years ago

Thank you for the update. There seems to be good interest in this. Maybe you also saw that the pipe input came up in the discussion of issue #295. My take on that is that an autostart feature would be desirable in many use cases, and that might fit well with what you are doing.

What I'm thinking is:

  1. Use libevent to watch all pipes in the library, whether they are audio or metadata. Libevent is pretty easy to work with, and it means you don't need to worry about poll/select/epoll.
  2. Libevent needs an event base, which is tied to a thread. It might make good sense to have a dedicated thread for this.
  3. When libevent calls back because there is data in a metadata pipe, you read and parse the data, and then use the worker thread to make the database update (don't want too many threads doing db writes).
  4. When libevent calls back because there is data in an audio pipe, autostart playback of the pipe and then keep reading with libevent callbacks.

Does that sound ok? I reckon I can do the audio stuff (no. 4).

Yes, the audio pipe is a continuous pcm stream, and there are no markers in it.

ABrolund commented 7 years ago

Like it :-) Will create a test program to play around with it. But might take some time as my little daughter take a lot. By the way the autostart on the pipe I am currently solving with a mpd script, as shairport can launch a script when it starts or stops streaming.

snizzleorg commented 7 years ago

same here. I'm away from home so I can only try testing in a VM on a mac - let's see how I get this going...

ABrolund commented 7 years ago

Hi, ok then. You can see the changes in https://github.com/ABrolund/forked.daapd.metadata.pipe.git I hope I have done everything right, because I am beginner with Git. Anyway here is what I have done: 1) Create a new thread and use lib event for watching Audio pipe and Metadata pipe. You can find a note in the log file when audio pipe is ready to read. I needed to add a section in the log file because I needed to know what pipe to watch. If you like to start the audio stuff for issue #295 feel free to make changes in branch 2) As soon a pipe is selected I am looking for a metadata pipe (.metadata) If found I am watching it for getting ready to read. The reading is unblocked now. I am writing the metadata to the log file only.

All in all it looks pretty stable at my system, but this does not mean to much right ? :-) On thing I still need your help with. Who is the best way to feed the metadata to the output. I am still hanging here.

ejurgensen commented 7 years ago

Good to hear you have something working. However, I couldn't see any changes in the repo you linked to, and it is also not a fork of this project, as it should be. So I think you will need to learn a bit of git - this is github, after all :-)

It is not complicated, and the guides here on github should have all the info you will need. Maybe you can start here: https://help.github.com/articles/fork-a-repo/

The basic things you need to do: 1) fork this project (the button to the top right), 2) clone it to your machine (git clone), 3) make a branch (git branch), 4) switch to the branch (git checkout), 5) make the changes (since you already have them, just copy the files) , 4) commit the changes (git commit), 5) push the changes to github (git push), 6) make a pull request (the button here on github).

Edit: This is probably also useful https://guides.github.com/introduction/flow/

ABrolund commented 7 years ago

Thx for your help! You should now be able to see the changes under: https://github.com/ABrolund/forked-daapd.git I did not make a pull request yet, because I am writing metadata to log file only. But if you like I can do of course. Any hint were to add the metadata ? I am still hanging here.

ejurgensen commented 7 years ago

Yes, I can see the changes now. There are a few things I would like to be done differently, and since I am working on input modules anyway, I think I will try to adapt your work a bit and then add it into forked-daapd. I will also implement that the metadata gets used.

ABrolund commented 7 years ago

Cool, thx. I have learned in the meantime that Spotify puts the metadata in the SQLite database. So I have done now. Still they are not visible to the user, but if I switch to a different source and come back they are finally.

But this implies that you cannot find the pipe via MPD afterwards. Regarding the few things you would have done differently. Just let me know, I am still a beginner in Linux development ( I developed for windows only in the past) and I am eager to learn .

Is there something you want me to concentrate on ?

ejurgensen commented 7 years ago

Apologies for not getting back to you, but when I looked at this, one thing ended up taking another, and I ended up significantly reworking how audio and metadata input is handled by forked-daapd. So there wasn't really anything you could do.

Anyway, these changes are now merged into the master branch here, and they also include the ability to read metadata from a Shairport-sync pipe. Only album, artist, title and progress - not artwork.

Included in the changes is pipe playback autostart and autostop, so any pipe in the library will be watched for new data. So if you configure Shairport to send audio to /your/library/Shairport-pipe, and metadata to /your/library/Shairport-pipe.metadata (must have the .metadata suffix), then you don't need to do anything else, except start streaming to Shairport. Forked-daapd should start playback by itself. I hope this feature will also be useful for e.g. librespot.

The metadata parser is now in src/inputs/pipe.c, and as you can see I decided to base it on mxml, since it makes it less sensitive to newlines and tag order than using sscanf.

ABrolund commented 7 years ago

Great, building up the Rasp Pi 3 in the moment will test it afterwards :-)

ejurgensen commented 7 years ago

A heads up: I've made a bunch of changes to the pipe module, because there were some issues with thread safety. So you might want pull the latest master branch.

jbieling commented 7 years ago

Might this solution lead to a feedback loop, when selecting the piped shairport Airplay speaker for audio output?

ejurgensen commented 7 years ago

Yes, it might. I can't think of a way to avoid it programmatically, but for the user it should be easy to avoid, just select the output actually wanted.

jbieling commented 7 years ago

A blacklist for device names would work. I just had a look at the code and it seems like ignoring a device passed to 'device_add', whenever the device name is on a configured blacklist, will ensure such input/output selections are not available to the user. What do you think?

ejurgensen commented 7 years ago

Yes, that could be done. I would like to understand a bit better what is the situation when you get feedbacks. Is it the auto-selection of outputs that is causing you issues? FYI there is an option to disable that in the options.

jbieling commented 7 years ago

From my point of view, it is more of a usability issue, specifically for less tech-savvy people in the house :-) But more generally, I believe that an option is better not offered, if it leads to undesired behaviour.

In case this is rather low priority for you, I would just give it a shot and let you merge.

ejurgensen commented 7 years ago

Ok, I get it, and yes I agree best not to offer bad choices at all.

Thanks for the offer to do it, but in this case it is quicker I do it myself. In fact, here is a branch where the change is made. I put the option in the AirPlay section of the config. Let me know if it works.

jbieling commented 7 years ago

Sorry for the delay. I tried the branch and it works flawlessly. iOS shows the multi room AirPlay "speaker", while iTunes Remote only shows the actual rooms. Thanks!

One more little thing though, which I already came to ignore .. until now :-) But it is also somehow related: iTunes Remote shows "Computer" as an available output, even though local audio output is using the "dummy" type. Selecting it does no harm, but it also does not make sense. It might as well be excluded from the list of outputs.

For some background: I am running shairport-sync in all rooms, including the box where forked-daapd is running on. This allows me to select each room individually (when streaming straight from iOS) as well as select speakers from the same list of rooms inside iTunes Remote. So forked-daapd effectively passes through a local shairport-sync instance, when playing audio locally.

ejurgensen commented 7 years ago

I have merged the branch into master now. I have also added a remark to the config file about a previously undocumented "feature": You can also set the type of local audio to "disabled" (actually, any value other than alsa, pulseaudio or dummy). If you do that then "Computer" won't be in the list of outputs.

Really glad to hear you have forked-daapd working with shairport-sync like that. Here's a shoutout to @mikebrady to let him know that his work on pipe outputs is much appreciated.

jbieling commented 7 years ago

Nice, thanks for that! Now things are perfect :-D Well, actually .. apart from the pipe currently not working, but I'll dig into that later. Thanks again!

mikebrady commented 7 years ago

Thanks for the mention, guys. What you're doing is very interesting!

jbieling commented 7 years ago

Pipe problem solved: the pipe was not writable by shairport-sync. Everything is working now as it should.

Thanks to both of you for you projects!

jshep321 commented 6 years ago

Hi, First of all... These projects (forked-daapd and shairport-sync) are awesome.

This works well, but the pipe volume is low compared to file playback. Any ideas on how to change this?

ejurgensen commented 6 years ago

You might look into disabling Shairport-sync volume control (in its config file). It makes sense if you are using a metadata pipe, because then the volume is obtained from the source (your iPhone or whatever) and passed as a value like this: source -> Shairport-sync -> metadata pipe -> forked-daapd -> destination (Airplay speaker). In that chain, the only place the volume will actually be adjusted is in the destination, and that is "the right way". It also means that volume adjustments should not lag.

By default Shairport-sync does do volume adjustment itself, and if you also use the metadata pipe you will get double volume adjustment. You will also have lagging volume adjustments, and the audio quality will also be slightly worse.

Ulrar commented 4 years ago

Hi,

I'm assuming you mean the "ignore_volume_control" in general, but with that set to no audio is fairly low and only changes when adjusted on the source (phone), and with it set to yes volume is just 100% regardless. Is there anyway to get forked-daapd to ignore the volume coming from the metadata to still be able to adjust it the usual way ?