Files
scrypted/plugins/sip/src/rtp-utils.ts
slyoldfox a5bae93813 Added some to parse optional video streams from the SIP call (#457)
* Added some to parse optional video streams from the SIP call

* Added some to parse optional video streams from the SIP call
2022-12-06 08:04:01 -08:00

126 lines
3.0 KiB
TypeScript

// by @dgrief from @homebridge/camera-utils
import dgram from 'dgram'
const stun = require('stun')
const stunMagicCookie = 0x2112a442 // https://tools.ietf.org/html/rfc5389#section-6
export interface RtpStreamOptions {
port: number
rtcpPort: number
}
export interface RtpOptions {
audio: RtpStreamOptions
video: RtpStreamOptions
}
export interface RtpStreamDescription extends RtpStreamOptions {
ssrc?: number
iceUFrag?: string
icePwd?: string
}
export interface RtpDescription {
address: string
audio: RtpStreamDescription
video: RtpStreamDescription
sdp: string
}
export function isRtpMessagePayloadType(payloadType: number) {
return payloadType > 90 || payloadType === 0
}
export function getPayloadType(message: Buffer) {
return message.readUInt8(1) & 0x7f
}
export function getSequenceNumber(message: Buffer) {
return message.readUInt16BE(2)
}
export function isStunMessage(message: Buffer) {
return message.length > 8 && message.readInt32BE(4) === stunMagicCookie
}
export function sendStunBindingRequest({
rtpDescription,
rtpSplitter,
rtcpSplitter,
localUfrag,
type,
}: {
rtpSplitter: dgram.Socket
rtcpSplitter: dgram.Socket
rtpDescription: RtpDescription
localUfrag?: string
type: 'audio' | 'video'
}) {
const remoteDescription = rtpDescription[type];
if( remoteDescription.port == 0 )
return;
const message = stun.createMessage(1),
{ address } = rtpDescription,
{ iceUFrag, icePwd, port, rtcpPort } = remoteDescription
if (iceUFrag && icePwd && localUfrag) {
// Full ICE supported. Send as formal stun request
message.addUsername(iceUFrag + ':' + localUfrag)
message.addMessageIntegrity(icePwd)
stun
.request(`${address}:${port}`, {
socket: rtpSplitter,
message,
})
.then(() => console.debug(`${type} stun complete`))
.catch((e: Error) => {
console.error(`${type} stun error`)
console.error(e)
})
} else {
// ICE not supported. Fire and forget the stun request for RTP and RTCP
const encodedMessage = stun.encode(message)
try {
rtpSplitter.send(encodedMessage, port, address)
} catch (e) {
console.error(e)
}
try {
rtcpSplitter.send(encodedMessage, rtcpPort, address)
} catch (e) {
console.error(e)
}
}
}
export function createStunResponder(rtpSplitter: dgram.Socket) {
return rtpSplitter.on('message', (message, info) => {
if (!isStunMessage(message)) {
return null
}
try {
const decodedMessage = stun.decode(message),
response = stun.createMessage(
stun.constants.STUN_BINDING_RESPONSE,
decodedMessage.transactionId
)
response.addXorAddress(info.address, info.port)
try {
rtpSplitter.send(stun.encode(response), info.port, info.address)
} catch (e) {
console.error(e)
}
} catch (e) {
console.debug('Failed to Decode STUN Message')
console.debug(message.toString('hex'))
console.debug(e)
}
return null
})
}