Last active
March 8, 2022 16:46
-
-
Save philhartung/d2d26e02ac2c1f870ca79fc9c06d225c to your computer and use it in GitHub Desktop.
PTPv2 client and RTP relay with timestamping for AES67. Also includes SAP/SDP.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var sdp = require('./sdp'); | |
//SDP Stuff | |
var samplerate = 48000; | |
var channels = 2; | |
var multicastAddr = '239.69.0.111'; | |
var addr = '192.168.1.1'; | |
var encoding = 'L24'; | |
var name = 'RPi 4'; | |
var sessID = Math.floor(Date.now() / 1000); | |
var sessVersion = sessID; | |
var ptpMaster; | |
var sync = false; | |
//ptp settings | |
var ptp_domain = 0; | |
var startSDP = function(){ | |
sdp.start(addr, multicastAddr, samplerate, channels, encoding, name, sessID, sessVersion, ptpMaster); | |
} | |
//RTP Server | |
var dgram = require('dgram'); | |
var server = dgram.createSocket('udp4'); | |
server.on('listening', function() { | |
server.setMulticastInterface(addr); | |
console.log('[RTP] relay server started'); | |
}); | |
server.on('message', function(message, remote) { | |
if(!sync) | |
return; | |
message.writeInt32BE(Math.ceil(rtp_timestamp() / 48)*48 + 48, 4); | |
server.send(message, 5004, multicastAddr); | |
}); | |
server.bind(5003); | |
//PTPv2 | |
var ptpClientEvent = dgram.createSocket('udp4'); | |
var ptpClientGeneral = dgram.createSocket('udp4'); | |
var ptpMulticastAddrs = ['224.0.1.129', '224.0.1.130', '224.0.1.131', '224.0.1.132']; | |
//vars | |
var t1, ts1, t2, ts2; | |
var offset = [0, 0]; | |
var sync_seq; | |
var req_seq = 0; | |
//functions | |
//creates ptp delay_req buffer | |
var ptp_delay_req = function(){ | |
var length = 52; | |
var buffer = Buffer.alloc(length); | |
buffer.writeUInt8(1, 0); | |
buffer.writeUInt8(2, 1); | |
buffer.writeUInt16BE(length, 2); | |
buffer.writeUInt16BE(++req_seq, 30); | |
return buffer; | |
} | |
//convert PTP timestamp to rtp timestamp | |
var rtp_timestamp = function(){ | |
var time = ptp_time(); | |
return ((time[0] * samplerate) + Math.round((time[1] * samplerate) / 1000000000)) & 0xffffffff; | |
} | |
//calculated ptp time | |
var ptp_time = function(){ | |
var time = process.hrtime(); | |
var timeS = time[0] - offset[0]; | |
var timeNS = time[1] - offset[1]; | |
return [timeS, timeNS]; | |
} | |
//event msg client | |
ptpClientEvent.on('listening', function() { | |
console.log('[PTP] event msg client started'); | |
ptpClientEvent.addMembership(ptpMulticastAddrs[ptp_domain], addr); | |
}); | |
ptpClientEvent.on('message', function(buffer, remote) { | |
var recv_ts = process.hrtime();//safe timestamp for ts1 | |
//read values from buffer | |
var type = buffer.readUInt8(0) & 0x0f; | |
var version = buffer.readUInt8(1); | |
var length = buffer.readUInt16BE(2); | |
var domain = buffer.readUInt8(4); | |
var flags = buffer.readUInt16BE(6); | |
var source = buffer.toString('hex', 20, 28).match(/.{1,2}/g).join('-')+':0'; | |
var sourceAlt = buffer.toString('hex', 20, 28).match(/.{1,2}/g).join(':'); | |
var sequence = buffer.readUInt16BE(30); | |
if(version != 2 || domain != ptp_domain)//check for version 2 and domain 0 | |
return; | |
if(type != 0)//only process sync messages | |
return; | |
//do we have a new ptp master? | |
if(source != ptpMaster){ | |
ptpMaster = source; | |
console.log('[PTP] New master', sourceAlt, 'on domain', ptp_domain); | |
sync = false; | |
} | |
//save sequence number | |
sync_seq = sequence; | |
//check if master is two step or not | |
if((flags & 0x0200) == 0x0200){ | |
//two step, wait for follow_up msg for accurate t1 | |
ts1 = recv_ts; | |
}else{ | |
//got accurate t1 (no follow_up msg) | |
ts1 = recv_ts; | |
//read t1 timestamp | |
var tsS = (buffer.readUInt16BE(34) << 4) + buffer.readUInt32BE(36); | |
var tsNS = buffer.readUInt32BE(40); | |
t1 = [tsS, tsNS]; | |
//send delay_req | |
ptpClientEvent.send(ptp_delay_req(), 319, ptpMulticastAddrs[ptp_domain], function(err){ | |
t2 = process.hrtime(); | |
}); | |
} | |
}); | |
ptpClientEvent.bind(319, ptpMulticastAddrs[ptp_domain]); | |
//general msg Client | |
ptpClientGeneral.on('listening', function() { | |
console.log('[PTP] general msg client started'); | |
ptpClientGeneral.addMembership(ptpMulticastAddrs[ptp_domain], addr); | |
}); | |
ptpClientGeneral.on('message', function(buffer, remote) { | |
//safe timestamp for ts2 | |
var recv_ts = process.hrtime(); | |
//read values from buffer | |
var type = buffer.readUInt8(0) & 0x0f; | |
var version = buffer.readUInt8(1); | |
var length = buffer.readUInt16BE(2); | |
var domain = buffer.readUInt8(4); | |
var flags = buffer.readUInt16BE(6); | |
var source = buffer.toString('hex', 20, 28).match(/.{1,2}/g).join('-')+':0'; | |
var sequence = buffer.readUInt16BE(30); | |
//check for version 2 and domain | |
if(version != 2 || domain != ptp_domain) | |
return; | |
if(type == 0x08 && sync_seq == sequence){ //follow up msg with current seq | |
//read t1 timestamp | |
var tsS = (buffer.readUInt16BE(34) << 4) + buffer.readUInt32BE(36); | |
var tsNS = buffer.readUInt32BE(40); | |
t1 = [tsS, tsNS]; | |
//send delay_req | |
ptpClientEvent.send(ptp_delay_req(), 319, ptpMulticastAddrs[ptp_domain], function(err){ | |
t2 = process.hrtime(); | |
}); | |
}else if(type == 0x09 && req_seq == sequence){ //delay_rsp msg | |
//read ts2 timestamp | |
var tsS = (buffer.readUInt16BE(34) << 4) + buffer.readUInt32BE(36); | |
var tsNS = buffer.readUInt32BE(40); | |
ts2 = [tsS, tsNS]; | |
//calc offset | |
offset[0] = 0.5 * (ts1[0] - t1[0] - ts2[0] + t2[0]); | |
offset[1] = 0.5 * (ts1[1] - t1[1] - ts2[1] + t2[1]); | |
//check if the clock was synced before | |
if(!sync){ | |
console.log('[PTP] Synced to Master'); | |
sync = true; | |
//start sdp | |
startSDP(); | |
} | |
} | |
}); | |
ptpClientGeneral.bind(320, ptpMulticastAddrs[ptp_domain]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
gst-launch-1.0 alsasrc device=hw:1 buffer-time=1000 !\ | |
audioconvert !\ | |
audio/x-raw, format=S24BE, channels=2, rate=48000 !\ | |
rtpL24pay min-ptime=1000000 max-ptime=1000000 !\ | |
application/x-rtp, clock-rate=48000, channels=2 !\ | |
udpsink host=192.168.1.1 port=5003 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment