stephen / nodetunes

AirTunes v2 Server implementation
215 stars 64 forks source link

How do I delay the audio - [Audio is playing before video] #40

Open bugs181 opened 8 years ago

bugs181 commented 8 years ago

Trying to stream from a new AppleTV 4. A video delay of 1 to 3 seconds is apparent. Compared against another app (Airfoil Speakers) with no apparent delay.

Had to modify line 233 of rtspmethods.js in order to avoid a crash.

debug('uncaptured SET_PARAMETER method: %s', req.getHeader('Content-Type'));

Adding a small 2 second delay before piping to speaker can sometimes temporarily fix the issue. After a couple minutes there is however an apparent drift.

server.on('clientConnected', function(stream) {
    setTimeout(function() {
        stream.pipe(speaker);
     }, 2000)
 });
bugs181 commented 8 years ago

After a little more research, my hunch is that the issue lies within the Audio-Latency header that the server sends during the RECORD request. With Apple products, it appears that it's the server's responsibility to look at the rtptime property of the RTP-Info header and adjust playback accordingly.

It appears you've already come to the same conclusion regarding your comment:

if (!req.getHeader('RTP-Info')) {
  // it seems like iOS airplay does something else

The error I ran into earlier was actually pointing me in the right direction. There was an uncaptured setParameter method which just so happens to include the rtptime

Here's the capture of that request:

{ 'rtp-info': 'rtptime=2972745305',
  'content-length': '0',
  'content-type': 'image/none',
  cseq: '6',
  'dacp-id': '3EA1CB210409B4E8',
  'active-remote': '3043816175',
  'user-agent': 'AirPlay/268.1' }

And here is an in-depth answer of how to calculate WHEN to start playing the audio. http://stackoverflow.com/a/21585663/1251309

From my understanding, there's a small formula to calculate a timestamp from when you can start playing the audio.

bugs181 commented 8 years ago

Tried my hand at implementing the formula myself but I'm missing something.

In rtspmethods.js:

  var setParameter = function(req, res) {

    if (req.getHeader('Content-Type') == 'image/none') {

      var rtpInfo = req.getHeader('rtp-info')
      var rtptime = rtpInfo.split('rtptime=')[1]

      if (rtptime) {
        var calc = (((rtptime % 16000) / (16000 / 8000))) * 125 / 1000

        setTimeout(function() {
          rtspServer.external.emit('startStream', rtspServer.outputStream)
        }, calc * 2)
      }

    } 

And in server.js

server.on('startStream', function(stream) {
    stream.pipe(speaker)
})
bugs181 commented 8 years ago

I believe the issue comes down to one or more of the following:

  1. nodetunes not parsing the rtptime from RTP-Info header
  2. The RTP sequence number
  3. timing packets (I don't believe nodetunes handles this)
  4. sync packets (I don't believe nodetunes handles this either)
  5. Incorrect parsing of the RECORD request (it includes initial RTP sequence number and initial RTP timestamp)

I don't understand the AirPlay protocol well enough and have only been looking into the matter for less than 2 days.

More info Sync Packets:

Sync packets are sent once per second to the control port. They are used to correlate the RTP timestamps currently used in the audio stream to the NTP time used for clock synchronization.

Timing Packets: Timing packets are used to synchronize a master clock for audio. This is useful for clock recovery and precise synchronization of several devices playing the same audio stream.

HoodyUK commented 8 years ago

Similar issue from Mac 10.11 to Sonos speaker.

rightisleft commented 8 years ago

Seeing this on 10.11.5 with Node 6.7.0

tzm41 commented 7 years ago

But I am seeing delay in audio when using airsonos. Is it the same issue as this one?

anon1y4012 commented 7 years ago

@bugs181 How did you implement the change mentioned above into server.js? Do you have a copy you can share? I'm going to try and tackle the formula for the delay.