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

Syncs using PTP and pyaudio callback #1

Open
wants to merge 21 commits into
base: master
Choose a base branch
from

Conversation

glmnet
Copy link

@glmnet glmnet commented Jul 7, 2021

Hey there,

Though better create another thread to share my findings.

No noticeable audio skips now!!

This one I'm using the callback, this way we know more precisely when data is going to be played. It needs more love though

I applied a filter to the PTP, in my findings the PTP sync is quite ugly, i.e. it just jumps a few msec back and forth quite often which causes the stream to do skips.

The RTP buffer needs to be able to track back and forth, added a new previous() method to go back, however it might be interesting to get the frame we need directly instead of advancing / going back.

Pause, seek, and track skip is not working well

ckdo and others added 21 commits March 3, 2021 15:36
Addresses the reconnect issue and 100% CPU consumption after disconnect
-AAC
-ALAC
-OPUS
-PCM (may need tweaking)

This commit programmatically constructs the appropriate QuickTime atom
extradata chunk for ALAC to suit sample-size/rate/channels.

It also dynamically determines the correct playback clock rates, instead
of the static 44100.
-increment_index
-decrement_index
-get_fullness

Refactor find_seq from linear to binary search
Bin = O(log n) vs linear O(n) - binary iterates max several times vs
linear (up to) thousands
This commit adds a stable latency calculation which accounts for:
-default playback device latency
-pyaudio buffers

and ensures that this receiver falls into synchronization with other
playing devices. May need to pause/unpause play when a new device joins.

Admittedly, the output is not phase locked, but PTP may help to take
care of that.

We actually *want* the play-head to be offset and ahead. This way, it
synchronizes with other Airplay players (this is why Airplay clients
stream a bunch of RTP in advance). In my tests with other Sonos
endpoints - this latency calculation puts everything in sync to within
a millisecond. Idea: dynamically announce output device latency into the
plist sent to the sender.
This commit implements PTPv2 Slave in Python3.
It will listen and slave to other Masters. There is no code to run as
Master yet. Everything necessary for Airplayv2. Import this file and:

if "timingProtocol" in plist:
    if plist['timingProtocol'] == 'PTP':
        print('PTP Startup')
        mac = int((ifen[ni.AF_LINK][0]["addr"]).replace(":", ""), 16)
        self.ptp_proc, self.ptp_link = PTP.spawn(mac)

Bitshifting is an expensive operation in python. It's possible that
for every new byte shifted to the left, a new object is created to hold
the data.
int.from_bytes appears to be the most efficient binary unpacking method
(without requiring imports) - sometimes unpack is a smidge faster:

--
data = bytes.fromhex('1b02005400000608000000000000000000000000'\
'010203040506000583bf017905fe00000000000000000000000000f8f8fe4'\
'36af8010203fffe0405060001a000080010010203fffe0405060202030405060005')

--
def test3():
	correctionNanoseconds = int.from_bytes(data[8:16], byteorder='big')

print(timeit.timeit("test3()", globals=locals(), number=100000000))

>>> 26.746907015000033
--
import struct
def test2():
	correctionNanoseconds = struct.unpack(">Q", data[8:16])[0]

print(timeit.timeit("test2()", globals=locals(), number=100000000) )

>>> 26.19966978500088
--
def test():
	correctionNanoseconds = \
            data[8] <<40|data[9] <<32|data[10]<<24| \
            data[11]<<16|data[12]<< 8|data[13]

print(timeit.timeit("test()", globals=locals(), number=100000000) )

>>> 43.50181922199772
--
@systemcrash
Copy link
Owner

Like! One potential issue: does everything still work the same/as well as before, in case, e.g., someone turns off PTP in the Flags?

We're going to lint and clean up the code, (I will rebase my master also) and once those are in, you can rebase and tidy up the PR. 😄 Unless you want to rebase from the main repo?

@glmnet
Copy link
Author

glmnet commented Jul 10, 2021

Yeah. I can apply changes in a clean master. This is a mess since I formatted with black

@glmnet
Copy link
Author

glmnet commented Jul 10, 2021

Then I'm gonna PR against openairplay directly

@systemcrash systemcrash force-pushed the master branch 7 times, most recently from 5810460 to 445a3b3 Compare January 18, 2022 13:58
@systemcrash systemcrash force-pushed the master branch 8 times, most recently from 6ab566c to ca4db5c Compare January 20, 2022 20:03
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.

4 participants