const dgram = require('dgram'); | |
const client = dgram.createSocket({ type: 'udp4', reuseAddr: true }); | |
const sdp = require('./sdp'); | |
//config | |
const addr = '10.10.1.100'; | |
const danteMulticast = '239.255.220.221'; | |
const aes67Multicast = '239.69.1.122'; | |
const samplerate = 48000; | |
const channels = 2; | |
const encoding = 'L24'; | |
const name = 'Dante Multicast Relay'; | |
const sessID = Math.round(Date.now() / 1000); | |
const sessVersion = sessID; | |
const ptpMaster = '08-00-00-ff-fe-00-00-1f:0'; | |
//rtp specific vars | |
var seqNum = 0; | |
client.on('listening', function() { | |
client.addMembership(danteMulticast, addr); | |
client.setMulticastInterface(addr); | |
}); | |
client.on('message', function(buffer, remote) { | |
//read values from buffer | |
var channelCount = buffer.readUInt8(0); | |
var timestampSeconds = buffer.readUInt32BE(1); | |
//bytes 6 and 7 seem to be always 0x00, maybe reserved bytes | |
var timestampMedia = buffer.readUInt16BE(7); | |
var pcmData = buffer.slice(9); | |
//calculate media timestamp for rtp | |
var timestampRTP = ((timestampSeconds * samplerate) + timestampMedia) & 0xffffffff; | |
//create RTP header | |
var rtpHeader = Buffer.alloc(12); | |
rtpHeader.writeUInt16BE(0x8061, 0); | |
rtpHeader.writeUInt16BE(seqNum, 2); | |
rtpHeader.writeInt32BE(timestampRTP, 4); | |
rtpHeader.writeUInt32BE(0xaf12af34, 8); | |
//create and send RTP packet | |
var rtpBuffer = Buffer.concat([rtpHeader, pcmData]); | |
client.send(rtpBuffer, 5004, aes67Multicast); | |
//increase seqnum | |
seqNum = (seqNum + 1) % 65536; | |
}); | |
client.bind(4321); | |
sdp.start(addr, aes67Multicast, samplerate, channels, encoding, name, sessID, sessVersion, ptpMaster); |
var dgram = require('dgram'); | |
var socket = dgram.createSocket({ type: 'udp4', reuseAddr: true }); | |
var constructSDPMsg = function(addr, multicastAddr, samplerate, channels, encoding, name, sessID, sessVersion, ptpMaster){ | |
var sapHeader = Buffer.alloc(8); | |
var sapContentType = Buffer.from('application/sdp\0'); | |
var ip = addr.split('.'); | |
//write version/options | |
sapHeader.writeUInt8(0x20); | |
//write hash | |
sapHeader.writeUInt16LE(0xefef, 2); | |
//write ip | |
sapHeader.writeUInt8(parseInt(ip[0]), 4); | |
sapHeader.writeUInt8(parseInt(ip[1]), 5); | |
sapHeader.writeUInt8(parseInt(ip[2]), 6); | |
sapHeader.writeUInt8(parseInt(ip[3]), 7); | |
var sdpConfig = [ | |
'v=0', | |
'o=- '+sessID+' '+sessVersion+' IN IP4 '+addr, | |
's='+name, | |
'c=IN IP4 '+multicastAddr+'/32', | |
't=0 0', | |
'a=clock-domain:PTPv2 0', | |
'm=audio 5004 RTP/AVP 96', | |
'a=rtpmap:96 '+encoding+'/'+samplerate+'/'+channels, | |
'a=sync-time:0', | |
'a=framecount:48', | |
'a=ptime:1', | |
'a=mediaclk:direct=0', | |
'a=ts-refclk:ptp=IEEE1588-2008:'+ptpMaster, | |
'a=recvonly', | |
'' | |
]; | |
var sdpBody = Buffer.from(sdpConfig.join('\r\n')); | |
return Buffer.concat([sapHeader, sapContentType, sdpBody]); | |
} | |
exports.start = function(addr, multicastAddr, samplerate, channels, encoding, name, sessID, sessVersion, ptpMaster){ | |
sdpMSG = constructSDPMsg(addr, multicastAddr, samplerate, channels, encoding, name, sessID, sessVersion, ptpMaster); | |
socket.bind(9875, function(){ | |
socket.setMulticastInterface(addr); | |
socket.send(sdpMSG, 9875, '239.255.255.255', function(err){}); | |
}); | |
setInterval(function(){ | |
socket.send(sdpMSG, 9875, '239.255.255.255', function(err){}); | |
}, 30*1000); | |
} |
Good to hear you got it working! Make sure to adjust the samplerate, channels and enconding in the config section too. Also just a reminder, this script is very experimental and I wouldn't use this for production. Though in my test it did work quite well and was stable over a longer period of time. If you need low latency, you should use a more powerful system (i7-2600 test system worked well with latency <2ms and quite low latency jitter). This will work on a Raspberry Pi, but latency jitter will be higher.
Sadly Dante and Linux don't work well together. The options are pretty much AES67 (but in this case isn't supported by the device and thus wouldn't work normally) or Dante Hardware like the Dante AVIO USB (only 2x2) or the PCIe Cards (quite expensive) which are supposed to work under Linux (at least the Digigram LX-Dante).
This is amazing! It works very smooth with my i7-7567U. I have an AES67 box that acts as a PTPv2 master, Audinate AVIO dongle on the network, and this same computer runs Dante Virtual Soundcard.. I routed this computers Dante to the AVIO and then used that stream to be replicated with your script. Only issue was that the payload type was not correct on the SDP (RTP stream had payload type 97, but SDP told it was 96), but that was an easy fix of course.
I'm so sorry. My brain did absolutely ignore the "multicast" word when figuring how to use the script. Now I got it working.
I have a digital mixing console which Dante expansion card is absolutely outdated and does not support AES67 (Presonus RM16, discontinued), so I'm looking for ways to connect it to Linux computers. Every other device I own (Dante AVIO adapters and a Dante - ADAT converter) are AES67 compatible.