Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Relay a Dante multicast stream to AES67. This assumes the AES67 device is synced to the same PTP master, as no PTP timestamping is done (timestamp from Dante is copied to AES67 RTP packet)
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);
}
@wasge

This comment has been minimized.

Copy link

@wasge wasge commented Nov 17, 2020

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?

@philhartung

This comment has been minimized.

Copy link
Owner Author

@philhartung philhartung commented Nov 17, 2020

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.

@wasge

This comment has been minimized.

Copy link

@wasge wasge commented Nov 17, 2020

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.

@philhartung

This comment has been minimized.

Copy link
Owner Author

@philhartung philhartung commented Nov 17, 2020

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment