Skip to content

Instantly share code, notes, and snippets.

@philhartung
Last active March 27, 2025 22:47
Show Gist options
  • Save philhartung/87d336a3c432e2ce5452befcad1b945f to your computer and use it in GitHub Desktop.
Save philhartung/87d336a3c432e2ce5452befcad1b945f to your computer and use it in GitHub Desktop.
Relay Dante multicast 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);
}
@Play-AV
Copy link

Play-AV commented Apr 11, 2024

I'm just learning about Dante and AES67 so I hope you don't mind a question or two here. I got an RDL AV-XMN4 to put microphone audio onto my network, and I think I need VSC on a Windows machine to receive audio. What I want to do though is record the audio out to WAV files in 5-minute chunks over a really long period of time (I.e., hours). Linux has a utility called arecord that can do what I want if it can see an audio device to record from. Your solution here looks like it could fill in a missing piece of the puzzle, but I'm not sure I get exactly how to hook it all up. I think that this piece will appear to the Dante Controller on the Windows machine as a device on the network, to which I can send the audio. But does this provide the audio device that arecord can see? Is there perhaps an easier way to get to 5-minute WAV files sequentially over a long period of time? Thanks in advance for any advice you can give.

Most reliable way to do this if you must use Windows is to stick within the "Dante" eco system and get Audinate's VSC. Then you can handle recording your 5 minute wav dumps however you see fit. The RDL should show up as a Dante device, you don't need to involve AES67 here.
Basically, you'll use Dante Controller to wire up the 4 channels from the RDL to this virtual device on your Windows machine, which would be trivial for you to record from.

@DesertMatt
Copy link

Hello Phil,
Sorry for some noob questions - am quite new to the Dante/AES67 topic. I have Neumann MT48 Ravenna/AES67 audio interface (pre Dante version) which I'm trying to connect to VCS and some other Dante outboard gear (like Eventide H9000 with Dante card etc.).
Was already pulling my anyway scarce hair over this as the MT48 would connnect to VCS but without audio because missing AES67 protocol from VCS. Now I found this thread and your code - where do I have to paste this exactly to make my MT48 talk to the VCS (on my Windows PC and DAW) and vice versa?
Hope somebody can help me out with some baby steps guidance please
Thank you and kind regards
Matt

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