Skip to content

Instantly share code, notes, and snippets.

Last active July 10, 2023 09:06
  • Star 20 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
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');
const addr = '';
const danteMulticast = '';
const aes67Multicast = '';
const samplerate = 48000;
const channels = 2;
const encoding = 'L24';
const name = 'Dante Multicast Relay';
const sessID = Math.round( / 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.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;
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
//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 = [
'o=- '+sessID+' '+sessVersion+' IN IP4 '+addr,
'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,
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.send(sdpMSG, 9875, '', function(err){});
socket.send(sdpMSG, 9875, '', function(err){});
}, 30*1000);
Copy link

Play-AV commented Apr 24, 2023

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.

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