Closed postlund closed 3 years ago
I have potential fixes for #1061 and #1062. With those applied, I've managed to play a six minute long file with no problems what so ever. So they seem solid. Will prepare and commit them later tonight.
I'm getting a miniaudio
installation error when running pip3 install --upgrade git+https://github.com/postlund/pyatv.git
in my venv.
Some searches reveal it to be an issue with M1 Macs. I'm running an M1 Mac mini. I'll try to install it on my Raspberry Pi.
Output:
...
Installing collected packages: miniaudio, bitarray
Running setup.py install for miniaudio ... error
ERROR: Command errored out with exit status 1:
command: /Users/nehal/Documents/Projects/AppleTVMITM/pyatv_venv/bin/python3.9 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de/setup.py'"'"'; __file__='"'"'/private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-record-tk0tk3f5/install-record.txt --single-version-externally-managed --compile --install-headers /Users/nehal/Documents/Projects/AppleTVMITM/pyatv_venv/include/site/python3.9/miniaudio
cwd: /private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de/
Complete output (15 lines):
running install
running build
running build_py
creating build
creating build/lib.macosx-11-arm64-3.9
copying miniaudio.py -> build/lib.macosx-11-arm64-3.9
running build_ext
generating cffi module 'build/temp.macosx-11-arm64-3.9/_miniaudio.c'
creating build/temp.macosx-11-arm64-3.9
building '_miniaudio' extension
creating build/temp.macosx-11-arm64-3.9/build
creating build/temp.macosx-11-arm64-3.9/build/temp.macosx-11-arm64-3.9
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -DMA_NO_GENERATION=1 -I/private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de -I/opt/homebrew/include -I/opt/homebrew/opt/openssl@1.1/include -I/opt/homebrew/opt/sqlite/include -I/Users/nehal/Documents/Projects/AppleTVMITM/pyatv_venv/include -I/opt/homebrew/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/include/python3.9 -c build/temp.macosx-11-arm64-3.9/_miniaudio.c -o build/temp.macosx-11-arm64-3.9/build/temp.macosx-11-arm64-3.9/_miniaudio.o -g1 -O3 -ffast-math -mtune=native -march=native
clang: error: the clang compiler does not support '-march=native'
error: command '/usr/bin/clang' failed with exit code 1
----------------------------------------
ERROR: Command errored out with exit status 1: /Users/nehal/Documents/Projects/AppleTVMITM/pyatv_venv/bin/python3.9 -u -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de/setup.py'"'"'; __file__='"'"'/private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-install-225s2xt4/miniaudio_e86f1908e234422c88c0e0217c4fb1de/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record /private/var/folders/8h/vtzgh1z92lvcxv52vk974k400000gn/T/pip-record-tk0tk3f5/install-record.txt --single-version-externally-managed --compile --install-headers /Users/nehal/Documents/Projects/AppleTVMITM/pyatv_venv/include/site/python3.9/miniaudio Check the logs for full command output.
Just tested it on my Raspberry Pi with this file, and it worked!
That is great news, thanks for testing! 👍 It's good for me to know the limitations regarding miniaudio, I suspected it would backfire somewhere. Not sure yet if I'm gonna make it optional, i.e. use it only if it's installed. I'll think about that.
Maybe it could be raised as an issue with pyminiaudio? It seems like they would want to fix this installation error.
Yeah, sounds like a good idea to me. Definitely worth opening an issue to see if they are aware of the problem.
Hi I love the idea of adding native RAOP support to pyatv. I already tried to implement RAOP /AirTunes v2 (not AirPlay2) as a standalone project in python a couple of years ago. It was somehow working. The pause function was still buggy and a lot of other things were broken. Last time I check (about a month ago) sending audio to multiple receivers, including metadata information was working. ATV pin pairing, password protected Apple TVs and remote controlling the sender are working as well (there seems to be a problem with iPhone and ATV 3 controls, shairport-sync controls are working fine... strange). At some point I decided, it is too much work to finish and cleanup the code all by myself and I decided to abandon the project. Instead I put my effort into providing python bindings for RAOP-Player. The fork includes a couple of new features. Better DMAP support, password protected Airplay devices, ATV device verification. You can find them here: https://github.com/Schlaubischlump/RAOP-Player-bindings
You can find the code of my original python implementation here: https://github.com/Schlaubischlump/raopy_old I made the repo public so that you can access it. It was private before. Feel free to use whatever code you need. If I can somehow help to make this pyatv implementation a reality let me know !
I got a couple of test devices: Apple TV 3, Apple TV 4K, original Homepod, as well if I should test something.
Hi and thanks for the feedback! 😄 Yeah, it's quite the task to take on and requires quite a bit of time. That's basically why I've set a pretty low bar for what I'm implementing so far. With the goal of adding more features over time. Supporting multiple receivers would be great for instance, but I haven't made any research into that so I have no idea how that works. Would also require a bit of fiddling to figure out a decent way to add group devices as pyatv is very "single device"-designed.
I will have a look at the code and see if there's anything that can help me 😄 If you feel like collaborating that would be very appreciated as well, not sure how much time I will have now when summer is coming up. But I'm gonna try to put a few hours into the project every now and then.
I saw you posted a few other issues, I'll have a look at those as well!
I've implemented most of the things here and even more than I expected, so I feel this issue can be closed. Will open additional ones for other features and perhaps also AirPlay v2 in the future.
TL;DR Audio streaming (AirPlay) is coming to
pyatv
. Keep on reading if you are interested (instructions at the end).So... I'm pretty excited about this! Soon after opening this issue, I will push a PR (#1060) with an MVP implementation of Remote Audio Output Protocol (RAOP) support in
pyatv
. For those not familiar with RAOP, it's the audio streaming part of AirPlay (previously also called AirTunes). It's basically what makes it possible to stream audio to an AirPlay receiver, e.g. an AirPort Express or a HomePod. Something able to send a stream to a receiver is called a sender, so this makespyatv
an AirPlay sender.There are several AirPlay senders out there today, both open source and proprietary. One example of a great open source sender is owntone (formerly known as forked-daapd). A lot of inspiration has been taken from owntone and also other open source senders when creating this, so a big thank you to all involved! A list of sources is available at the end.
Support for streaming audio to AirPort Expresses and HomePods have been requested for a while. As there doesn't seem to exist any working RAOP implementations in python, I have deferred that until later as there's a lot of work involved in writing that code. Python is maybe not the ideal language either, mainly for performance reasons. But I took on the challenge and I have written a tiny implemetation to get started. There are lots and lots of issues and lack in functionality, so don't expect to have anything useful in a while. But I decided that I want to merge something and put it out there for people to try out. So please, give it a spin and let me know if it works for you. It's interesting to know what kind of AirPlay receivers you stream to and what kind of device you stream from (performance reasons).
My main driver (and what I'm prioritizing) is support for the HomePod mini. Basically, getting good enough support in
pyatv
so that it can be used for TTS in Home Assistant. I was generously donated a HomePod mini (thank you!) for this particular usecase, so I'm gonna honor that and do what I can to make it happen! This doesn't mean that it won't work with other receivers (it should), but it's not something I will give additional time investigating for now. I have tried my current implementation with success on my HomePod mini and Yamaha RX-V773 receiver.As I mentioned, the support is currently very limited, the design isn't very good and there are lots of issues beneath the surface. But better with something than nothing. I'm gonna use this issue as sort of an "epic" for work to be done. Below is a list of limitations and other tasks that needs to be dealt with. The main idea is to create a new issue with a more detailed description for each task as work progresses. I'm very open to external help! If you would like to help out, feel free to pick something below, open and issue and send a PR. Any questions can be asked here, I'll try to answer as good as I can. Streaming and working with audio (i.e. signal processing) is completely new to me, so I'm learning as I go along. Likely lots of beginner mistakes. So please challenge me!
Current state
Things to do
1061:
The audio will only play for a short while, typically around 10s on my computer. This is basically bescause my "send loop" isn't compensating for being late (i.e. it takes too much time to process audio frames). Each second, sample rate (typically 441000) amount of packets containing 352 frames are to be sent (each frame contains two samples in case of stereo). I divide the time into 1/44100 slots, send 352 frames and then sleep until the next slot. This however adds a bit of extra time for each slot, so it takes longer than one second to send all packets making the receiver underflow and stops playing. Depending on the computer performance, this might differ a bit though.1062:
Some times, playback actually works longer than I described above. If that's the case, then playback will stop around 30s in. This seems to be a known behavior with the Apple TV and HomePod and a workaround is to send metadata every 25s. See here and here.There are some minor distortions in the sound that can be heard sometimes. Usually at the end and with sound files that are mostly quiet (I have some random test clips I found on the net with various musical instruments). Not sure why that is.I believe this has to do with the source file and not my implementation.1079:
Packets are just blindly sent out. A ring buffer should be added and packets buffered in case retranmission is needed.1075:
Metadata (title, album and artist) are just hardcoded to dummy values. Ideally, this would be fetched from the audio file but also support manual override. miniaudio however does not support reading metadata, so that would mean an additional library is needed. Maybe worth it?1094:
Media length is hardcoded to 3s (as demo). When playing, it seems like the play state remains in "pause" and I'm not entirely sure how to fix that.1068: Media controls doesn not work. Maybe this?
1067:
The code is written to respect sampling rate, sample size and number of channels of the source file as much as possible. I have however only succeeded to play anything in 44100/2/16bit, so that's what is considered supported.stream_file
has been added to theStream
interface. This kind of interface is however very limited as it does not give any real opportunities to the user for further interaction (e.g. play something and still allow pause, rewind, etc) without the use of a task. Maybe spawn in background and allow some kind of listener to call back when playback ends? Needs a bit of thinking. It's still possible to keep a simple method like this for conveience and add a more flexible solution later.Limitations and out of scope
1077:
AirPort Express not supported as it requires encryption. This is fully reverse engineered and probably not that hard to implement.1078:
Apple TV running tvOS not supported.pyatv/airplay
) to handle playback and metadata.Try it out
Works more or less the same as
play_url
:Easy as that.
References
Some references I've used, but probably missed some:
https://github.com/philippe44/RAOP-Player https://github.com/owntone/owntone-server https://openairplay.github.io/airplay-spec/introduction.html https://emanuelecozzi.net/docs/airplay2 https://stackoverflow.com/questions/34584522/airplay-protocol-how-to-use-raw-pcm-instead-of-alac https://nto.github.io/AirPlay.html