FDH2 / UxPlay

AirPlay Unix mirroring server
GNU General Public License v3.0
1.34k stars 72 forks source link

Stream is Lagging after sleeping device for a while, then waking device #207

Closed thiccaxe closed 9 months ago

thiccaxe commented 10 months ago

ios 17 dev beta 7 - might be fixed in final release

IIRC previous behavior was that the phone would send a black screen on shutoff but now I think it stops sending video updates when you press power button. after 5-6+ seconds wake the phone and the stream is delayed and laggy.

This is not the case on airplay2, on an apple tv hd tvOS 16, where everything works as expected.

thiccaxe commented 10 months ago

In these logs I connected with screen mirroring as per normal, then periodically turned off and turned back on phone several times.

2023_09_02_turning_phone_on_and_off_screen_mirror_lag.txt

Don't know the exact cause. But it seems like the display end stream expects data but none is provided. Based on some tests with a stopwatch, the stream resumes after the data has been back-filled. Essentially, if the screen is off for 5 seconds, it will take 5 seconds for the stream to catch up again in the gstreamer window.

I will test audio and post another update...

thiccaxe commented 10 months ago

I tested audio, and similar things seem to be happening. note that this is all on 21A5319a

thiccaxe commented 10 months ago

OK, I have found a solution, I am pausing the gstreamer stream when the client sends the updated window data, then start again on the next video data packet. As well as resetting the base time.

But this does not solve all the problems, like from the VLC for ios app - it also yields a black screen. I will do more research, and submit a pull request soon

thiccaxe commented 10 months ago

This issue seems to be more complicated. It seems like airplay will (sometimes) not send updates if the content on the screen does not change. In this case, the solution doesn't work. Additionally, it fails in case that the device goes to sleep on its own (user doesn't press sleep/wake button manualy)

It seems a better solution is required. I will do more research.

fduncanh commented 10 months ago

For video, there are two gstreamer modes "sync = true" and "sync = false"

"sync = true" is the default: uxplay or "uxplay -vsync" this matches audio and video timestamps from the client. EDIT: this drops video frames which are "too late"

"sync = false" is the older method: get it back with "uxplay -vsync no" this live streams and ignores timestamps, It times audio using 44.1 kHz and video by fps. EDIT: here video frames dont get dropped if they are late.

fduncanh commented 10 months ago

Is there something I should look for in the 1.17 public beta?. I also have another ipad on 1.16

What should I do to see your issue, e.g. while a you tube movie is playing?

thiccaxe commented 10 months ago

It affects iOS 16 too:

fduncanh commented 10 months ago

OK so this is not a 1.17 issue.

Have you tried using uxplay -vsync no ?

(The vsync option wont affect the raop debug output, it only affects gstreamer what gstreamer does) . can you update your debug trace to add annotations showing what to look at? (or post excerpts in a comment)

fduncanh commented 10 months ago

For vsync mode, it would be easy to add a delay option (+ or -) to both audio and video timestamps.

fduncanh commented 10 months ago

OK I think I see it.

Yes its only there with the vsync option which I made the default as of UxPlay 1.64.

Maybe I should go back to -vsync no as the default ? (i.e. behavior prior to 1.64) Vsync drops video frames that are "too late" (time stamps would be later that "now" when rendered)

thiccaxe commented 10 months ago

Was there any reasoning behind the default video sync setting?

Anyway, I think it's better to investigate the issue, because vsync could be useful.

fduncanh commented 10 months ago

Yes, it guarantees that video and audio stay coupled. It is important for low power systems without hardware h264 decoding.

Each frame in the video stream has an NTP time stamp (seconds since jan 1, 1900 to jan1, 1970 plus time since client last boot) The NTP synchronization between client and server (abandoned in AirPlay 2) converts that to server Unix time on the client.

The audio sends "rtptime" based on the 44.1kHz audio encoding (This is iTunes mechanism). shortly after the AAC-ELD audi stream starts, the client sends signals at intervals with a client NTP time and client rtptime of the same instant. This allows audio rtptime to be kept from drifting relative to the equivalent video time.

So within a few seconds of the audiostream starting, audio and video have timestamps (in nanoseconds) based on client ntp time in seconds relative to an arbitrary epoch. This allows lipsync for ever. After a mirror connection starts, the offset betweeen client ntptime and unix time is computed, and used to adjust the timestamp offsets to match server time.

What I hadnt realized for a long time was that the way antimof implemented gstreamer support used "sync = no" which makes no use of timestamps at all. This is "uxplay -vsync no" mode, and is just live streaming: play audi when it arrives, play video when it arrives. This works if the video decoding is fast enough. If not video gets more and more delayed.

In vsync mode video frames with timestamps (indicating when they should be played) later than "now" get dropped. (audio has about a 5 second latency so is always decoded in time)

One sees the video frames getting dropped when GST_DEBUG=2 is active.

fduncanh commented 10 months ago

*Any modification can be done completely in uxplay.cpp and renderers, without touching raop code in lib

fduncanh commented 10 months ago

https://gstreamer-devel.narkive.com/v8ic4w0S/usage-of-sync-false-in-rendering

thiccaxe commented 9 months ago

I compute the offset between local and remote time once when the first (audio or video) packet of the connection arrives

In my fix I had to repeat this as well. I think we need to reset the time when raop receives a window dimensions update packet (which is why RAOP code needed to be modified) (unencrypted coded data). Let me upload my changes to git

thiccaxe commented 9 months ago

https://github.com/thiccaxe/UxPlay/commit/660a2dc378ec22bbdabfc2bd9558755d8e3b4c37

this isn't a great solution though, its more of a soggy bandaid.

fduncanh commented 9 months ago

can you identify the window dimension update packet in the debug trace?

I did some testing after issues raised in #169. I recall I found that using the ntp remote timestamps (the audio one computed from client data only) as PTS was much more accurate than using the conversion to "ntp local" using ntp, which introduces jitters.

When the client is send video at 30fps, the client timestamp increase by 0.03333xx secs per frame. The rtp time increase according to 44.1kHz, and the client peridiocally sends an rtptime+ntptime pair that allows audio rtptime to be converted to video ntptime, using an offset averaged over the previous 8 sync signals.

fduncanh commented 9 months ago

Just saw you PR. looks like a solution.

Did you look at:

https://gstreamer.freedesktop.org/documentation/application-development/advanced/clocks.html?gi-language=c

thiccaxe commented 9 months ago

Just saw you PR. looks like a solution.

it has some bugs that can pop up unfortunately (eg. when the device goes to sleep by itself). I'll took a look at the link, I think this section:

Synchronization is now a matter of making sure that a buffer with a certain running-time is played when the clock reaches the same running-time. Usually, this task is performed by sink elements. These elements also have to take into account the configured pipeline's latency and add it to the buffer running-time before synchronizing to the pipeline clock.

and The clock object needs to report an absolute-time that is monotonically increasing when the element is in the PLAYING state. It is allowed to pause the clock while the element is PAUSED. is particularily relavent

fduncanh commented 9 months ago

using GST_DEBUG=2 it seems the frames are getting dropped when the client restarts because they are "too old" I thing the gstreamer clock needs to be paused during sleep. The issue is how to detect sleep.

Maybe keep track of time since last data arrived?

thiccaxe commented 9 months ago

I think we need to some how tell gstreamer that these frames were skipped?

thiccaxe commented 9 months ago

Yeah, I think we need to pause gstreamer properly.

Maybe keep track of time since last data arrived?

seems like a good idea

fduncanh commented 9 months ago

@thiccaxe can you submit your fix as a PR or should I just copy from it?

I think its good after testing.

thiccaxe commented 9 months ago

Yeah, will submit a pr

fduncanh commented 9 months ago

PR is merged!

Thanks!