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);
}
@mmoduu
Copy link

mmoduu commented Oct 21, 2022

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.

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