watson / rtsp-stream

A transport agnostic RTSP serial multiplexer module for Node
MIT License
91 stars 28 forks source link

Added support for Interleave #2

Open surfdude75 opened 8 years ago

surfdude75 commented 8 years ago

Implementation works like

decoder.on('packet',function(packet){
  console.log('packet channel:',packet.channel);
  console.log('packet size:',packet.size);
  var bufs = [];
  packet.on('data',function(data){ bufs.push(data); });
  packet.on('end',function(){
    var payload = Buffer.concat(bufs);
    console.log('packet payload:',payload);
  });
});
surfdude75 commented 8 years ago

It seems to work. I still working on in it. I am going to try to build some tests. Aloha!

watson commented 8 years ago

@surfdude75 thanks for PR 😄 Looks good so far. I have no clue what this "Interleave" thing is - do you have a link or something to a page explaining this?

surfdude75 commented 8 years ago

hi @watson,

It is to get stream packet over the same tcp connection used by RTSP protocol You can find some info in the following link: Embedded (Interleaved) Binary Data at https://en.wikipedia.org/wiki/Real_Time_Streaming_Protocol I am using it to get camera stream data.

Thanks for sharing this project.

Aloha,

Rafael Sobral

watson commented 8 years ago

@surfdude75 ah, thanks 😃

Would you be ok with adding tests for Interleaved encoding and decoding to this PR?

surfdude75 commented 8 years ago

I am working in add the tests.

I forgot about encoding. I am going to add it too.

Aloha,

Rafael Sobral

notedit commented 7 years ago

will we merge this? i will need this too.

luislobo commented 6 years ago

@surfdude75 Did you had the chance to finish this project?

savageautomate commented 6 years ago

Ah .. I just found this PR after writing my own filtering logic to strip out the TCP interleaved RTP packets. Using the Live555 Proxy server I was gettingInvalid RTSP Request-Line errors when more than one RTSP client was connected to a common RTSP endpoint.

My changes are here: https://github.com/savageautomate/rtsp-stream/commit/7971c9942cc7f5b04f285d8125c495a0ba27e397

I was going to open a new PR for my changes but I don't want to overlap or create conflicts .. plus this PR looks to be more comprehensive.

max-mapper commented 3 years ago

I was messing with this branch to try to get a rtsp client working, but it doesn't expect to work in client mode I think, it crashed with

packet size: 2
buffer.js:615
    return _copy(this, target, targetStart, sourceStart, sourceEnd);
           ^

RangeError: Index out of range

hacky code:

var net = require('net')
var rtsp = require('./rtsp-stream')
var crypto = require('crypto')

var encoder = new rtsp.Encoder()
var decoder = new rtsp.Decoder()
let WWW_AUTH_REGEX = new RegExp('([a-z]+)=\"([^,\s]+)\"')
let uri = process.argv[2]
let seq = 0
// connect to RTSP server
var socket = net.connect({ host: 'localhost', port: 8080 }, function () {
  console.error('making request')
  // make a request to the RTSP server
  var req = encoder.request({ method: 'OPTIONS', uri: uri })
  req.setHeader('CSeq', ++seq)
  req.end()
})

let auth
let session

decoder.on('packet',function(packet){
  if (packet.size && packet.size > 0) {  
    console.error('packet channel:',packet.channel);
    console.error('packet size:',packet.size);
    var bufs = [];
    packet.on('data',function(data){ bufs.push(data); });
    packet.on('end',function(){
      var payload = Buffer.concat(bufs);
      if (payload.length) process.stdout.write(payload)
    });
  }
});

// handle response from server
decoder.on('response', async function (res) {
  console.error('--> received response from server:', res.statusCode)
  console.error('--> headers:', res.headers)

  if (!session && res.headers.session) {
    session = res.headers.session.split(';')[0]
    console.error('PLAY', session)
    var req = encoder.request({ method: 'PLAY', uri: uri })
    req.setHeader('CSeq', ++seq)
    req.setHeader('Authorization', auth)
    req.setHeader('Session', session)
    req.end()
    return
  }
  if (res.statusCode === 200 && res.headers.public) {
    var req = encoder.request({ method: 'DESCRIBE', uri: uri })
    req.setHeader('CSeq', ++seq)
    req.setHeader('Authorization', auth)
    req.setHeader('Accept', 'application/sdp')
    req.end()

    await(delay(1000))

    var req = encoder.request({ method: 'SETUP', uri: uri + '/trackID=0'})
    req.setHeader('CSeq', ++seq)
    req.setHeader('Authorization', auth)
    req.setHeader('Session', 'null')
    req.setHeader('Transport', 'DH/AVP/TCP;unicast;interleaved=0-1')
    req.end()
  }

  if (res.headers['www-authenticate']) {
    let authHeaders = {}
    let x = res.headers['www-authenticate']
    x.split(',')
    .map((x)=>x.trim())
    .map((x)=>x.split('='))
    .map((x)=>{
      x[0] = x[0].replace('Digest ', '')
      x[1] = x[1].replace(/"/ig, '')
      authHeaders[x[0]] = x[1]
    })

    auth = getDigest('OPTIONS', uri, 'admin', 'password', authHeaders.realm, authHeaders.nonce)
    var req = encoder.request({ method: 'OPTIONS', uri: uri })
    req.setHeader('CSeq', ++seq)
    req.setHeader('Authorization', auth)
    req.end()
  }
})

// connect the plumbing
encoder.pipe(socket).pipe(decoder)

function getDigest(requestName, url, username, password, realm, nonce) {
  let ha1 = getMD5Hash(`${username}:${realm}:${password}`);
  let ha2 = getMD5Hash(`${requestName}:${url}`);
  let ha3 = getMD5Hash(`${ha1}:${nonce}:${ha2}`);

  return `Digest username="${username}",realm="${realm}",nonce="${nonce}",uri="${url}",response="${ha3}"`;
}

function getMD5Hash(data) {
  return crypto.createHash('md5').update(data).digest('hex')
}

async function delay (ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve()
    }, ms)
  })
}