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); | |
} |
Most Dante devices already have AES67 support built in. You should only use this script, if you have a Dante device that does not support AES67 (for example Dante Virtual Soundcard or Dante Via) and an AES67 device that is supposed to receive audio from Dante. What excatly are you trying to achive?
If you actually have a use case for this script, then you probably need to look at the config part at the top to get it to work, especially:
const addr = '10.10.1.100';
const danteMulticast = '239.255.220.221';
The addr
is supposed to be the address of the network interface. The second part is a bit more complex. The danteMulticast
needs to be the multicast address of the Dante multicast stream you are trying to relay. I don't (yet) have a tool to find out the address of the multicast stream, so you will have to use Wireshark and capture the network traffic for a short period of time. You then can filter the captured traffic for Dante multicast traffic with ip.dst == 239.255.0.0/16 and udp.port == 4321
. In the destination column you should only have addresses of Dante multicast traffic.
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.
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.
This works excellently with Dante VSC (by far the most reliable windows driver I've found). As mmoduu said, you'll have to change the payload type to 97 and at least for me, when I'm trying to receive an 8 ch stream with the Merging ALSA driver, I have to map it to 8 channels.
Using an AVIO as the boundary clock between PTPv1 and v2. You can probably do this yourself or there's a project on here that describes how to intercept and then 'spawn' Dante VSC's PTP process to connect to a v2 source. It's for Mac but the premise should in theory work for Windows too if you re-write the code.
I'm trying this and I get the AES67 transmitter on Dante Controller, but I don't get any Dante receiver to send the Dante audio. Can you give me any hint?