diff --git a/CHANGELOG.md b/CHANGELOG.md index 0655779..f446f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Next Release +# 3.0.7 - 13th January 2027 +Add H266 (VVC) support with output to a .266 file. +Tested with https://github.com/jimm98y/SharpRealTimeStreaming which uses the SharpRTSP Library + # v3.0.6 - 13th Oct 2025 **New features**: diff --git a/README.md b/README.md index 2e8ac45..5aa09f7 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ npm install yellowstone --save Yellowstone is a library to receive video, andio and metadata from RTSP/RTP sources including Wowza, MediaMTX and IP Cameras. The library can parse some common video and audio transport formats and delivers the (often compressed) video and audio data to the library user, or writes it to a file. The library does not include codec decoders. -Yellowstone was co-developed by Michael Bullington and Roger Hardiman. +Yellowstone is co-developed by Michael Bullington and Roger Hardiman. ## Current Features @@ -22,6 +22,7 @@ Yellowstone was co-developed by Michael Bullington and Roger Hardiman. * Pause, Play, and Teardown (Close) - H264/AVC transport parsing (and writing video to a .264 file) - H265/HEVC transport parsing (and writing video to a .265 file) +- H266/VVC transport parsing (and writing video to a .266 file) - AV1 transport parsing (and writing video to a .obu file) - AAC transport paring (and writing audio to an .aac file) - ONVIF Metadata parsing (and writing to an output file) diff --git a/dist/ONVIFClient.js.map b/dist/ONVIFClient.js.map index cdca415..7116867 100644 --- a/dist/ONVIFClient.js.map +++ b/dist/ONVIFClient.js.map @@ -1 +1 @@ -{"version":3,"file":"ONVIFClient.js","sourceRoot":"","sources":["../lib/ONVIFClient.ts"],"names":[],"mappings":";;AAAA,6CAAsC;AAEtC,qCAAqC;AACrC,MAAqB,WAAY,SAAQ,oBAAU;IACjD,YAAY,QAAgB,EAAE,QAAgB;QAC5C,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAU,EAAE,EAAS;QAClC,MAAM,GAAG,GAAG;YACV,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,SAAS,IAAI,CAAC,WAAW,EAAE,GAAG;SACtC,CAAC;QAEF,IAAI,EAAE,EAAE;YACN,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;SAC/B;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAW,EAAE,EAAS;QACtC,MAAM,GAAG,GAAQ;YACf,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,cAAc,EAAE,IAAI;YACpB,KAAK,EAAE,MAAM;SACd,CAAC;QAEF,IAAI,IAAI,EAAE;YACR,GAAG,CAAC,KAAK,GAAG,SAAS,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC;YAC3C,IAAI,EAAE,EAAE;gBACN,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;aAC/B;SACF;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AArCD,8BAqCC","sourcesContent":["import RTSPClient from \"./RTSPClient\";\n\n// RTSP client with ONVIF extensions.\nexport default class ONVIFClient extends RTSPClient {\n constructor(username: string, password: string) {\n super(username, password, { Require: \"onvif-replay\" });\n }\n\n async playFrom(from: Date, to?: Date) {\n const obj = {\n Session: this._session,\n Immediate: 'yes',\n Range: `clock=${from.toISOString()}-`\n };\n\n if (to) {\n obj.Range += to.toISOString();\n }\n\n await this.request(\"PLAY\", obj);\n return this;\n }\n\n async playReverse(from?: Date, to?: Date) {\n const obj: any = {\n Session: this._session,\n 'Rate-Control': 'no',\n Scale: '-1.0'\n };\n\n if (from) {\n obj.Range = `clock=${from.toISOString()}-`;\n if (to) {\n obj.Range += to.toISOString();\n }\n }\n\n await this.request(\"PLAY\", obj);\n return this;\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"ONVIFClient.js","sourceRoot":"","sources":["../lib/ONVIFClient.ts"],"names":[],"mappings":";;AAAA,6CAAsC;AAEtC,qCAAqC;AACrC,MAAqB,WAAY,SAAQ,oBAAU;IACjD,YAAY,QAAgB,EAAE,QAAgB;QAC5C,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAU,EAAE,EAAS;QAClC,MAAM,GAAG,GAAG;YACV,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,SAAS,EAAE,KAAK;YAChB,KAAK,EAAE,SAAS,IAAI,CAAC,WAAW,EAAE,GAAG;SACtC,CAAC;QAEF,IAAI,EAAE,EAAE;YACN,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;SAC/B;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAW,EAAE,EAAS;QACtC,MAAM,GAAG,GAAQ;YACf,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,cAAc,EAAE,IAAI;YACpB,KAAK,EAAE,MAAM;SACd,CAAC;QAEF,IAAI,IAAI,EAAE;YACR,GAAG,CAAC,KAAK,GAAG,SAAS,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC;YAC3C,IAAI,EAAE,EAAE;gBACN,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;aAC/B;SACF;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AArCD,8BAqCC","sourcesContent":["import RTSPClient from \"./RTSPClient\";\r\n\r\n// RTSP client with ONVIF extensions.\r\nexport default class ONVIFClient extends RTSPClient {\r\n constructor(username: string, password: string) {\r\n super(username, password, { Require: \"onvif-replay\" });\r\n }\r\n\r\n async playFrom(from: Date, to?: Date) {\r\n const obj = {\r\n Session: this._session,\r\n Immediate: 'yes',\r\n Range: `clock=${from.toISOString()}-`\r\n };\r\n\r\n if (to) {\r\n obj.Range += to.toISOString();\r\n }\r\n\r\n await this.request(\"PLAY\", obj);\r\n return this;\r\n }\r\n\r\n async playReverse(from?: Date, to?: Date) {\r\n const obj: any = {\r\n Session: this._session,\r\n 'Rate-Control': 'no',\r\n Scale: '-1.0'\r\n };\r\n\r\n if (from) {\r\n obj.Range = `clock=${from.toISOString()}-`;\r\n if (to) {\r\n obj.Range += to.toISOString();\r\n }\r\n }\r\n\r\n await this.request(\"PLAY\", obj);\r\n return this;\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/RTSPClient.js b/dist/RTSPClient.js index d2ae05c..dc8d1de 100644 --- a/dist/RTSPClient.js +++ b/dist/RTSPClient.js @@ -9,6 +9,7 @@ const util_1 = require("./util"); const transform = require("sdp-transform"); const RTPPacket_1 = require("./transports/RTPPacket"); const RTP_AVP = "RTP/AVP"; +const RTP_AVPF = "RTP/AVPF"; // Used by AV1. This is RTP with Feedback (via RTCP) to request Keyframes via RTCP const STATUS_OK = 200; const STATUS_UNAUTH = 401; // The WWW_AUTH is of the format @@ -131,12 +132,10 @@ class RTSPClient extends events_1.EventEmitter { this.isConnected = true; this._client = client; client.removeListener("error", errorListener); - client.on("error", postConnectErrorListener); this.on("response", responseListener); resolve(this); }); } - client.on("data", this._onData.bind(this)); client.on("error", errorListener); client.on("close", closeListener); @@ -161,7 +160,7 @@ class RTSPClient extends events_1.EventEmitter { if (!describeRes || !describeRes.mediaHeaders) { throw new Error("No media headers on DESCRIBE; RTSP server is broken (sanity check)"); } - // For now, only RTP/AVP is supported. (Some RTSPS servers use RTP/SAVP) + // For now, only RTP/AVP and RTP/AVPF are supported. (Some RTSPS servers use RTP/SAVP) const { media } = transform.parse(describeRes.mediaHeaders.join("\r\n")); // Loop over the Media Streams in the SDP looking for Video or Audio // In theory the SDP can contain multiple Video and Audio Streams. We only want one of each type @@ -197,6 +196,26 @@ class RTSPClient extends events_1.EventEmitter { codec = "H265"; } } + if (mediaSource.type === "video" && + mediaSource.protocol === RTP_AVP && + mediaSource.rtp[0].codec === "H266") { + this.emit("log", "H266 Video Stream Found in SDP", ""); + if (hasVideo == false) { + needSetup = true; + hasVideo = true; + codec = "H266"; + } + } + if (mediaSource.type === "video" && + (mediaSource.protocol === RTP_AVP || mediaSource.protocol === RTP_AVPF) && + mediaSource.rtp[0].codec === "AV1") { + this.emit("log", "AV1 Video Stream Found in SDP", ""); + if (hasVideo == false) { + needSetup = true; + hasVideo = true; + codec = "AV1"; + } + } if (mediaSource.type === "audio" && (mediaSource.direction === "recvonly" || mediaSource.direction === "sendrecv") && mediaSource.protocol === RTP_AVP && diff --git a/dist/RTSPClient.js.map b/dist/RTSPClient.js.map index 7ef0601..ea218ea 100644 --- a/dist/RTSPClient.js.map +++ b/dist/RTSPClient.js.map @@ -1 +1 @@ -{"version":3,"file":"RTSPClient.js","sourceRoot":"","sources":["../lib/RTSPClient.ts"],"names":[],"mappings":"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","sourcesContent":["import * as net from \"net\";\nimport * as dgram from \"dgram\";\nimport { parse as urlParse } from \"url\";\nimport { EventEmitter } from \"events\";\n\nimport {\n parseRTPPacket,\n parseRTCPPacket,\n getMD5Hash,\n Transport,\n parseTransport,\n generateSSRC,\n} from \"./util\";\n\nimport * as transform from \"sdp-transform\";\nimport RTPPacket from \"./transports/RTPPacket\";\nconst RTP_AVP = \"RTP/AVP\";\n\nconst STATUS_OK = 200;\nconst STATUS_UNAUTH = 401;\n\n// The WWW_AUTH is of the format\n// TOKEN key=value\n// TOKEN key1=value1,key2=value2\n// TOKEN key1=\"value1\",key2=value2\n\n// RegEx reminder ? = Zero or One item\n// * = Zero or More items\n// + = 1 or More items\n// \\s is whitespace. But we need to 'escape the slash', hence \\\\s (or put the regex in / / characters)\n// ?= is a lookahead\n\n// The RegEx has two 'Groups'\n// \n// Group 1 (finding the Key)\n// Look for one or more characters (a..z or A..Z)\n// then look for whitespace\n// then look for 'equals'\n// then look for whitespace\n// then look for an optional Quote character\n//\n// Group 2 (finding the Value) -\n// Look for EITHER 'look backwards for a Quote', some characters, 'lookahead for a Quote'\n// OR some characters until (by looking ahead) you can see that another key comes next. The lookahead is 'optinal whitespace' 'comma' 'optional whitespace' 'chars' 'optinal whitespace' 'equals'\n// OR some characters followed by 'optinal whitespace'\n\nconst WWW_AUTH = \"WWW-Authenticate\";\nconst WWW_AUTH_REGEX = new RegExp('([a-zA-Z]+)\\\\s*=\\\\s*\"?((?<=\").*?(?=\")|.*?(?=\\\\s*,?\\\\s*[a-zA-Z]+\\\\s*=)|.+[^\\\\s])', \"g\");\n\nenum ReadStates {\n SEARCHING,\n READING_RTSP_HEADER,\n READING_RTSP_PAYLOAD,\n READING_RAW_PACKET_SIZE,\n READING_RAW_PACKET,\n}\n\ntype Connection = \"udp\" | \"tcp\";\n\ntype Headers = {\n [key: string]: string | number | undefined;\n Session?: string;\n Location?: string;\n CSeq?: number;\n \"WWW-Authenticate\"?: string;\n Transport?: string;\n Unsupported?: string;\n};\n\ntype Detail = {\n codec: string;\n mediaSource: ({ // cannot work out how to pull this type in\n type: string;\n port: number;\n protocol: string;\n payloads?: string | undefined;\n } & transform.MediaDescription); // get Type from the interface\n transport: Transport['parameters']; // get Type from the interface\n isH264: boolean; // legacy API\n rtpChannel: number;\n rtcpChannel: number;\n};\n\nexport default class RTSPClient extends EventEmitter {\n username: string;\n password: string;\n headers: { [key: string]: string };\n\n isConnected = false;\n closed = false;\n\n // These are all set in #connect or #_netConnect.\n\n _url?: string;\n _client?: net.Socket;\n _cSeq = 0;\n _unsupportedExtensions?: string[];\n // Example: 'SessionId'[';timeout=seconds']\n _session?: string;\n _keepAliveID?: NodeJS.Timeout;\n _nextFreeInterleavedChannel = 0;\n _nextFreeUDPPort = 5000;\n\n readState: ReadStates = ReadStates.SEARCHING;\n\n // Used as a cache for the data stream.\n // What's in here is based on current #readState.\n messageBytes: number[] = [];\n\n // Used for parsing RTSP responses,\n\n // Content-Length header in the RTSP message.\n rtspContentLength = 0;\n rtspStatusLine = \"\";\n rtspHeaders: Headers = {};\n\n // Used for parsing RTP/RTCP responses.\n\n rtspPacketLength = 0;\n rtspPacket: Buffer = new Buffer(\"\");\n rtspPacketPointer = 0;\n\n // Used in #_emptyReceiverReport.\n clientSSRC = generateSSRC();\n\n tcpSocket: net.Socket = new net.Socket();\n setupResult: Array = [];\n constructor(\n username: string,\n password: string,\n headers?: { [key: string]: string }\n ) {\n super();\n\n this.username = username;\n this.password = password;\n this.headers = {\n ...(headers || {}),\n \"User-Agent\": \"yellowstone/3.x\",\n };\n }\n\n // This manages the lifecycle for the RTSP connection\n // over TCP.\n //\n // Sets #_client.\n //\n // Handles receiving data & closing port, called during\n // #connect.\n _netConnect(hostname: string, port: number): Promise {\n return new Promise((resolve, reject) => {\n // Set after listeners defined.\n\n const errorListener = (err: any) => {\n client.removeListener(\"error\", errorListener);\n reject(err);\n };\n\n const postConnectErrorListener = (err: any) => {\n client.removeListener(\"error\", postConnectErrorListener);\n this.emit(\"error\", err);\n reject(err);\n };\n\n const closeListener = () => {\n client.removeListener(\"close\", closeListener);\n this.emit(\"close\");\n this.close(true);\n };\n\n const responseListener = (responseName: string, headers: Headers) => {\n const name = responseName.split(\" \")[0];\n\n if (name.indexOf(\"RTSP/\") === 0) {\n return;\n }\n\n if (name === \"REDIRECT\" || name === \"ANNOUNCE\") {\n this.respond(\"200 OK\", { CSeq: headers.CSeq });\n }\n\n if (name === \"REDIRECT\" && headers.Location) {\n this.close();\n this.connect(headers.Location);\n }\n };\n\n const client = net.connect(port, hostname, () => {\n this.isConnected = true;\n this._client = client;\n\n client.removeListener(\"error\", errorListener);\n client.on(\"error\", postConnectErrorListener);\n\n this.on(\"response\", responseListener);\n resolve(this);\n });\n\n client.on(\"data\", this._onData.bind(this));\n client.on(\"error\", errorListener);\n client.on(\"close\", closeListener);\n this.tcpSocket = client;\n });\n }\n\n async connect(\n url: string,\n {\n keepAlive = true,\n connection = \"udp\",\n }: { keepAlive: boolean; connection?: Connection } = {\n keepAlive: true,\n connection: \"udp\",\n }\n ): Promise {\n const { hostname, port } = urlParse((this._url = url));\n if (!hostname) {\n throw new Error(\"URL parsing error in connect method.\");\n }\n\n const details: Detail[] = [];\n\n await this._netConnect(hostname, parseInt(port || \"554\"));\n await this.request(\"OPTIONS\");\n\n const describeRes = await this.request(\"DESCRIBE\", {\n Accept: \"application/sdp\",\n });\n if (!describeRes || !describeRes.mediaHeaders) {\n throw new Error(\n \"No media headers on DESCRIBE; RTSP server is broken (sanity check)\"\n );\n }\n\n // For now, only RTP/AVP is supported.\n const { media } = transform.parse(describeRes.mediaHeaders.join(\"\\r\\n\"));\n\n // Loop over the Media Streams in the SDP looking for Video or Audio\n // In theory the SDP can contain multiple Video and Audio Streams. We only want one of each type\n let hasVideo = false;\n let hasAudio = false;\n let hasMetaData = false;\n let hasBackchannel = false;\n\n for (let x = 0; x < media.length; x++) {\n let needSetup = false;\n let codec = \"\";\n const mediaSource = media[x];\n\n\n // RFC says \"If none of the direction attributes (\"sendonly\", \"recvonly\", \"inactive\", and \"sendrecv\") are present,\n // the \"sendrecv\" SHOULD be assumed\n if (mediaSource.direction == undefined) mediaSource.direction = \"sendrecv\"; // Wowza does not send 'direction'\n\n if (\n mediaSource.type === \"video\" &&\n mediaSource.protocol === RTP_AVP &&\n mediaSource.rtp[0].codec === \"H264\"\n ) {\n this.emit(\"log\", \"H264 Video Stream Found in SDP\", \"\");\n if (hasVideo == false) {\n needSetup = true;\n hasVideo = true;\n codec = \"H264\";\n }\n }\n\n if (\n mediaSource.type === \"video\" &&\n mediaSource.protocol === RTP_AVP &&\n mediaSource.rtp[0].codec === \"H265\"\n ) {\n this.emit(\"log\", \"H265 Video Stream Found in SDP\", \"\");\n if (hasVideo == false) {\n needSetup = true;\n hasVideo = true;\n codec = \"H265\";\n }\n }\n\n\n if (\n mediaSource.type === \"audio\" &&\n (mediaSource.direction === \"recvonly\" || mediaSource.direction === \"sendrecv\") &&\n mediaSource.protocol === RTP_AVP &&\n mediaSource.rtp[0].codec.toLowerCase() === \"mpeg4-generic\" && // (RFC examples are lower case. Axis cameras use upper case)\n mediaSource.fmtp[0].config.includes(\"AAC\")\n ) {\n this.emit(\"log\", \"AAC Audio Stream Found in SDP\", \"\");\n if (hasAudio == false) {\n needSetup = true;\n hasAudio = true;\n codec = \"AAC\";\n }\n }\n\n if (mediaSource.type === \"audio\" &&\n mediaSource.direction === \"sendonly\" &&\n mediaSource.protocol === RTP_AVP) {\n this.emit(\"log\", \"Audio backchannel Found in SDP\", \"\");\n if (hasBackchannel == false) {\n needSetup = true;\n hasBackchannel = true;\n codec = mediaSource.rtp[0].codec;\n }\n }\n\n if (\n mediaSource.type === \"application\" &&\n mediaSource.protocol === RTP_AVP &&\n mediaSource.rtp[0].codec.toLowerCase() === \"vnd.onvif.metadata\"\n ) {\n this.emit(\"log\", \"ONVIF Meta Data Found in SDP\", \"\");\n if (hasMetaData == false) {\n needSetup = true;\n hasMetaData = true;\n codec = \"vnd.onvif.metadata\";\n }\n }\n\n if (needSetup) {\n let streamurl = \"\";\n // The 'control' in the SDP can be a relative or absolute uri\n if (mediaSource.control) {\n if (mediaSource.control.toLowerCase().startsWith(\"rtsp://\")) {\n // absolute path\n streamurl = mediaSource.control;\n } else {\n // relative path\n streamurl = this._url + \"/\" + mediaSource.control;\n }\n }\n\n // Perform a SETUP on the streamurl\n // either 'udp' RTP/RTCP packets\n // or with 'tcp' RTP/TCP packets which are interleaved into the TCP based RTSP socket\n let setupRes;\n let rtpChannel;\n let rtcpChannel;\n let rtpReceiver: dgram.Socket|null = null; // UDP mode init value\n let rtcpReceiver: dgram.Socket|null = null; // UDP mode init value\n\n if (connection === \"udp\") {\n // Create a pair of UDP listeners, even numbered port for RTP\n // and odd numbered port for RTCP\n\n rtpChannel = this._nextFreeUDPPort;\n rtcpChannel = this._nextFreeUDPPort + 1;\n this._nextFreeUDPPort += 2;\n\n const rtpPort = rtpChannel;\n rtpReceiver = dgram.createSocket(\"udp4\");\n\n rtpReceiver.on(\"message\", (buf, remote) => {\n const packet = parseRTPPacket(buf);\n this.emit(\"data\", rtpPort, packet.payload, packet);\n });\n\n const rtcpPort = rtcpChannel;\n rtcpReceiver = dgram.createSocket(\"udp4\");\n\n rtcpReceiver.on(\"message\", (buf, remote) => {\n const packet = parseRTCPPacket(buf);\n this.emit(\"controlData\", rtcpPort, packet);\n\n const receiver_report = this._emptyReceiverReport();\n this._sendUDPData(remote.address, remote.port, receiver_report);\n });\n\n // Block until both UDP sockets are open.\n\n await new Promise((resolve) => {\n rtpReceiver?.bind(rtpPort, () => resolve({}));\n });\n\n await new Promise((resolve) => {\n rtcpReceiver?.bind(rtcpPort, () => resolve({}));\n });\n\n const setupHeader = {\n Transport: `RTP/AVP;unicast;client_port=${rtpPort}-${rtcpPort}`,\n };\n if (this._session)\n Object.assign(setupHeader, { Session: this._session });\n setupRes = await this.request(\"SETUP\", setupHeader, streamurl);\n } else if (connection === \"tcp\") {\n // channel 0, RTP\n // channel 1, RTCP\n\n rtpChannel = this._nextFreeInterleavedChannel;\n rtcpChannel = this._nextFreeInterleavedChannel + 1;\n this._nextFreeInterleavedChannel += 2;\n\n const setupHeader = {\n Transport: `RTP/AVP/TCP;interleaved=${rtpChannel}-${rtcpChannel}`,\n };\n if (this._session)\n Object.assign(setupHeader, { Session: this._session }); // not used on first SETUP\n setupRes = await this.request(\"SETUP\", setupHeader, streamurl);\n } else {\n throw new Error(\n `Connection parameter to RTSPClient#connect is ${connection}, not udp or tcp!`\n );\n }\n\n if (!setupRes) {\n throw new Error(\n \"No SETUP response; RTSP server is broken (sanity check)\"\n );\n }\n\n const { headers } = setupRes;\n\n if (!headers.Transport) {\n throw new Error(\n \"No Transport header on SETUP; RTSP server is broken (sanity check)\"\n );\n }\n\n const transport = parseTransport(headers.Transport);\n if (\n transport.protocol !== \"RTP/AVP/TCP\" &&\n transport.protocol !== \"RTP/AVP\"\n ) {\n throw new Error(\n \"Only RTSP servers supporting RTP/AVP(unicast) or RTP/ACP/TCP are supported at this time.\"\n );\n }\n\n // Patch from zoolyka (Zoltan Hajdu).\n // Try to open a hole in the NAT router (to allow incoming UDP packets)\n // by send a UDP packet for RTP and RTCP to the remote RTSP server.\n // Note, Roger did not have a router that needed this so the feature is untested.\n // May be better to change the RTCP message to a Receiver Report, leaving the RTP message as zero bytes\n if (connection === \"udp\" && transport && rtpReceiver && rtcpReceiver) {\n rtpReceiver.send(Buffer.from(''), Number(transport.parameters[\"server_port\"].split(\"-\")[0]), hostname);\n rtcpReceiver.send(Buffer.from(''), Number(transport.parameters[\"server_port\"].split(\"-\")[1]), hostname);\n }\n\n if (headers.Unsupported) {\n this._unsupportedExtensions = headers.Unsupported.split(\",\");\n }\n\n if (headers.Session) {\n this._session = headers.Session.split(\";\")[0];\n }\n\n const detail: Detail = {\n codec,\n mediaSource,\n transport: transport.parameters,\n isH264: codec === \"H264\",\n rtpChannel,\n rtcpChannel,\n };\n\n details.push(detail);\n } // end if (needSetup)\n } // end for loop, looping over each media stream\n\n if (keepAlive) {\n // Start a Timer to send OPTIONS every 20 seconds to keep stream alive\n // using the Session ID\n this._keepAliveID = setInterval(() => {\n this.request(\"OPTIONS\", { Session: this._session });\n // this.request(\"OPTIONS\");\n }, 20 * 1000);\n }\n\n this.setupResult = details;\n return details;\n }\n\n request(\n requestName: string,\n headersParam: Headers = {},\n url?: string\n ): Promise<{ headers: Headers; mediaHeaders?: string[] } | void> {\n if (!this._client) {\n return Promise.resolve();\n }\n\n const id = ++this._cSeq;\n // mutable via string addition\n let req = `${requestName} ${url || this._url} RTSP/1.0\\r\\nCSeq: ${id}\\r\\n`;\n\n const headers = {\n ...this.headers,\n ...headersParam,\n };\n\n // NOTE:\n // If we cache the Authenitcation Type (Direct or Basic) then we could\n // re-compute an Authorization Header here and include in the RTSP Command\n // This would make connections a faster with fewer round-trips to the RTSP Server\n\n req += Object.entries(headers)\n .map(([key, value]) => `${key}: ${value}\\r\\n`)\n .join(\"\");\n\n this.emit(\"log\", req, \"C->S\");\n // Make sure to add an empty line after the request.\n this._client.write(`${req}\\r\\n`);\n\n return new Promise((resolve, reject) => {\n const responseHandler = (\n responseName: string,\n resHeaders: Headers,\n mediaHeaders: string[]\n ) => {\n const firstAnswer: string = String(resHeaders[\"\"]) || \"\";\n if (firstAnswer.indexOf(\"401\") >= 0 && 'Authorization' in headers) {\n // If the RTSP Command we sent included an Authorization and we have 401 error, then reject()\n reject(new Error(`Bad RTSP credentials!`));\n return;\n }\n if (resHeaders.CSeq !== id) {\n return;\n }\n\n this.removeListener(\"response\", responseHandler);\n\n const statusCode = parseInt(responseName.split(\" \")[1]);\n\n if (statusCode === STATUS_OK) {\n if (mediaHeaders.length > 0) {\n resolve({\n headers: resHeaders,\n mediaHeaders,\n });\n } else {\n resolve({\n headers: resHeaders,\n });\n }\n } else {\n const authHeader = resHeaders[WWW_AUTH];\n\n // We have status code unauthenticated.\n if (statusCode === STATUS_UNAUTH && authHeader) {\n const type = authHeader.split(\" \")[0];\n\n // Get auth properties from WWW_AUTH header.\n let realm = \"\";\n let nonce = \"\";\n\n let match = WWW_AUTH_REGEX.exec(authHeader);\n while (match != null) {\n const prop = match[1];\n\n if (prop == \"realm\" && match[2]) {\n realm = match[2];\n }\n\n if (prop == \"nonce\" && match[2]) {\n nonce = match[2];\n }\n\n match = WWW_AUTH_REGEX.exec(authHeader);\n }\n\n // mutable, corresponds to Authorization header\n let authString = \"\";\n\n if (type === \"Digest\") {\n // Digest Authentication\n\n const ha1 = getMD5Hash(\n `${this.username}:${realm}:${this.password}`\n );\n const ha2 = getMD5Hash(`${requestName}:${this._url}`);\n const ha3 = getMD5Hash(`${ha1}:${nonce}:${ha2}`);\n\n authString = `Digest username=\"${this.username}\",realm=\"${realm}\",nonce=\"${nonce}\",uri=\"${this._url}\",response=\"${ha3}\"`;\n } else if (type === \"Basic\") {\n // Basic Authentication\n // https://xkcd.com/538/\n const b64 = new Buffer(\n `${this.username}:${this.password}`\n ).toString(\"base64\");\n authString = `Basic ${b64}`;\n }\n\n Object.assign(headers, {\n Authorization: authString,\n });\n\n resolve(this.request(requestName, headers, url)); // Call this.request with Authorized request\n return;\n }\n\n reject(new Error(`Bad RTSP status code ${statusCode}!`));\n return;\n }\n };\n\n this.on(\"response\", responseHandler);\n });\n }\n\n respond(status: string, headersParam: Headers = {}): void {\n if (!this._client) {\n return;\n }\n\n // mutable via string addition\n let res = `RTSP/1.0 ${status}\\r\\n`;\n\n const headers = {\n ...this.headers,\n ...headersParam,\n };\n\n res += Object.entries(headers)\n .map(([key, value]) => `${key}: ${value}\\r\\n`)\n .join(\"\");\n\n this.emit(\"log\", res, \"C->S\");\n this._client.write(`${res}\\r\\n`);\n }\n\n async play(): Promise {\n if (!this.isConnected) {\n throw new Error(\"Client is not connected.\");\n }\n\n await this.request(\"PLAY\", { Session: this._session });\n }\n\n async pause(): Promise {\n if (!this.isConnected) {\n throw new Error(\"Client is not connected.\");\n }\n\n await this.request(\"PAUSE\", { Session: this._session });\n }\n\n async sendAudioBackChannel(audioChunk: Buffer): Promise {\n let rtp, buf;\n const bufSize = 160;\n while (audioChunk.length > 0) {\n if (audioChunk.length > bufSize) {\n buf = audioChunk.slice(0, bufSize);\n audioChunk = audioChunk.slice(bufSize, audioChunk.length);\n } else {\n buf = audioChunk.slice(0, audioChunk.length);\n audioChunk = Buffer.from([]);\n }\n if (!rtp)\n rtp = new RTPPacket(buf);\n else\n rtp.payload = buf;\n // rtp.type = 8;// set động\n rtp.time += buf.length;\n rtp.seq++;\n const bufferLength = Buffer.alloc(2);\n bufferLength.writeUInt16BE(rtp.packet.length, 0);\n let channelInterleaved = this.setupResult.filter((value) => {\n return value.mediaSource.type === 'audio' && value.mediaSource.direction === 'sendonly';\n })[0].transport.interleaved;\n /* RTSP Interleaved Frame structure\n |dollar sign|channel identifier|data length|\n |1 Byte |1 Byte |2 Bytes |\n */\n channelInterleaved = channelInterleaved.split('-')[0];\n let interleavedHeader = Buffer.from([0x24]);// set '$'\n interleavedHeader = Buffer.concat([interleavedHeader, Buffer.from([channelInterleaved])]);\n interleavedHeader = Buffer.concat([interleavedHeader, bufferLength]);\n const dataToSend = Buffer.concat([interleavedHeader, rtp.packet]);\n await this._socketWrite(this.tcpSocket, dataToSend);\n }\n return;\n }\n\n async close(isImmediate = false): Promise {\n if (this.closed) return;\n this.closed = true;\n\n if (!this._client) {\n return;\n }\n\n if (!isImmediate) {\n await this.request(\"TEARDOWN\", {\n Session: this._session,\n });\n }\n\n this._client.end();\n this.removeAllListeners(\"response\");\n\n if (this._keepAliveID != undefined) {\n clearInterval(this._keepAliveID);\n this._keepAliveID = undefined;\n }\n\n this.isConnected = false;\n this._cSeq = 0;\n }\n\n _onData(data: Buffer): void {\n let index = 0;\n\n // $\n const PACKET_START = 0x24;\n // R\n const RTSP_HEADER_START = 0x52;\n // /n\n const ENDL = 10;\n\n while (index < data.length) {\n // read RTP or RTCP packet\n if (\n this.readState == ReadStates.SEARCHING &&\n data[index] == PACKET_START\n ) {\n this.messageBytes = [data[index]];\n index++;\n\n this.readState = ReadStates.READING_RAW_PACKET_SIZE;\n } else if (this.readState == ReadStates.READING_RAW_PACKET_SIZE) {\n // accumulate bytes for $, channel and length\n this.messageBytes.push(data[index]);\n index++;\n\n if (this.messageBytes.length == 4) {\n this.rtspPacketLength =\n (this.messageBytes[2] << 8) + this.messageBytes[3];\n\n if (this.rtspPacketLength > 0) {\n this.rtspPacket = new Buffer(this.rtspPacketLength);\n this.rtspPacketPointer = 0;\n this.readState = ReadStates.READING_RAW_PACKET;\n } else {\n this.readState = ReadStates.SEARCHING;\n }\n }\n } else if (this.readState == ReadStates.READING_RAW_PACKET) {\n this.rtspPacket[this.rtspPacketPointer++] = data[index];\n index++;\n\n if (this.rtspPacketPointer == this.rtspPacketLength) {\n const packetChannel = this.messageBytes[1];\n if ((packetChannel & 0x01) === 0) {\n // even number\n const packet = parseRTPPacket(this.rtspPacket);\n this.emit(\"data\", packetChannel, packet.payload, packet);\n }\n if ((packetChannel & 0x01) === 1) {\n // odd number\n const packet = parseRTCPPacket(this.rtspPacket);\n this.emit(\"controlData\", packetChannel, packet);\n const receiver_report = this._emptyReceiverReport();\n this._sendInterleavedData(packetChannel, receiver_report);\n }\n this.readState = ReadStates.SEARCHING;\n }\n // read response data\n } else if (\n this.readState == ReadStates.SEARCHING &&\n data[index] == RTSP_HEADER_START\n ) {\n // found the start of a RTSP rtsp_message\n this.messageBytes = [data[index]];\n index++;\n\n this.readState = ReadStates.READING_RTSP_HEADER;\n } else if (this.readState == ReadStates.READING_RTSP_HEADER) {\n // Reading a RTSP message.\n\n // Add character to the messageBytes\n // Ignore /r (13) but keep /n (10)\n if (data[index] != 13) {\n this.messageBytes.push(data[index]);\n }\n index++;\n\n // if we have two new lines back to back then we have a complete RTSP command,\n // note we may still need to read the Content Payload (the body) e.g. the SDP\n if (\n this.messageBytes.length >= 2 &&\n this.messageBytes[this.messageBytes.length - 2] == ENDL &&\n this.messageBytes[this.messageBytes.length - 1] == ENDL\n ) {\n // Parse the Header\n\n const text = String.fromCharCode.apply(null, this.messageBytes);\n const lines = text.split(\"\\n\");\n\n this.rtspContentLength = 0;\n this.rtspStatusLine = lines[0];\n this.rtspHeaders = {};\n\n lines.forEach((line) => {\n const indexOf = line.indexOf(\":\");\n\n if (indexOf !== line.length - 1) {\n const key = line.substring(0, indexOf).trim();\n const data = line.substring(indexOf + 1).trim();\n\n this.rtspHeaders[key] =\n key != \"Session\" && data.match(/^[0-9]+$/)\n ? parseInt(data, 10)\n : data;\n\n // workaround for buggy Hipcam RealServer/V1.0 camera which returns Content-length and not Content-Length\n if (key.toLowerCase() == \"content-length\") {\n this.rtspContentLength = parseInt(data, 10);\n }\n }\n });\n\n // if no content length, there there's no media headers\n // emit the message\n if (!this.rtspContentLength) {\n this.emit(\"log\", text, \"S->C\");\n\n this.emit(\"response\", this.rtspStatusLine, this.rtspHeaders, []);\n this.readState = ReadStates.SEARCHING;\n } else {\n this.messageBytes = [];\n this.readState = ReadStates.READING_RTSP_PAYLOAD;\n }\n }\n } else if (\n this.readState == ReadStates.READING_RTSP_PAYLOAD &&\n this.messageBytes.length < this.rtspContentLength\n ) {\n // Copy data into the RTSP payload\n this.messageBytes.push(data[index]);\n index++;\n\n if (this.messageBytes.length == this.rtspContentLength) {\n const text = String.fromCharCode.apply(null, this.messageBytes);\n const mediaHeaders = text.split(\"\\n\");\n\n // Emit the RTSP message\n this.emit(\n \"log\",\n String.fromCharCode.apply(null, this.messageBytes) + text,\n \"S->C\"\n );\n\n this.emit(\n \"response\",\n this.rtspStatusLine,\n this.rtspHeaders,\n mediaHeaders\n );\n this.readState = ReadStates.SEARCHING;\n }\n } else {\n // unexpected data\n throw new Error(\n \"Bug in RTSP data framing, please file an issue with the author with stacktrace.\"\n );\n }\n } // end while\n }\n\n _sendInterleavedData(channel: number, buffer: Buffer): void {\n if (!this._client) {\n return;\n }\n\n const req = `${buffer.length} bytes of interleaved data on channel ${channel}`;\n this.emit(\"log\", req, \"C->S\");\n\n const header = new Buffer(4);\n header[0] = 0x24; // ascii $\n header[1] = channel;\n header[2] = (buffer.length >> 8) & 0xff;\n header[3] = (buffer.length >> 0) & 0xff;\n\n const data = Buffer.concat([header, buffer]);\n this._client.write(data);\n }\n\n _sendUDPData(host: string, port: number, buffer: Buffer): void {\n const udp = dgram.createSocket(\"udp4\");\n udp.send(buffer, 0, buffer.length, port, host, (err, bytes) => {\n // TODO: Don't ignore errors.\n udp.close();\n });\n }\n\n _emptyReceiverReport(): Buffer {\n const report = new Buffer(8);\n const version = 2;\n const paddingBit = 0;\n const reportCount = 0; // an empty report\n const packetType = 201; // Receiver Report\n const length = report.length / 4 - 1; // num 32 bit words minus 1\n report[0] = (version << 6) + (paddingBit << 5) + reportCount;\n report[1] = packetType;\n report[2] = (length >> 8) & 0xff;\n report[3] = (length >> 0) & 0xff;\n report[4] = (this.clientSSRC >> 24) & 0xff;\n report[5] = (this.clientSSRC >> 16) & 0xff;\n report[6] = (this.clientSSRC >> 8) & 0xff;\n report[7] = (this.clientSSRC >> 0) & 0xff;\n\n return report;\n }\n\n async _socketWrite(socket: net.Socket, data: Buffer): Promise {\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n socket.write(data, (error: any) => {\n if (error) {\n reject(error);\n } else {\n resolve(undefined);\n }\n })\n }, 20);\n })\n }\n}\n\nexport { RTPPacket, RTCPPacket } from \"./util\";\n"]} +{"version":3,"file":"RTSPClient.js","sourceRoot":"","sources":["../lib/RTSPClient.ts"],"names":[],"mappings":"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","sourcesContent":["import * as net from \"net\";\r\nimport * as tls from \"tls\";\r\n\r\n// Union of net.Socket and tls.TLSSocket\r\ntype SocketUnion = net.Socket | tls.TLSSocket;\r\n\r\nimport * as dgram from \"dgram\";\r\nimport { parse as urlParse } from \"url\";\r\nimport { EventEmitter } from \"events\";\r\n\r\nimport * as util from \"./util\";\r\n\r\nimport {\r\n parseRTPPacket,\r\n parseRTCPPacket,\r\n getMD5Hash,\r\n getSHA256Hash,\r\n Transport,\r\n parseTransport,\r\n generateSSRC,\r\n} from \"./util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport RTPPacket from \"./transports/RTPPacket\";\r\nconst RTP_AVP = \"RTP/AVP\";\r\nconst RTP_AVPF = \"RTP/AVPF\"; // Used by AV1. This is RTP with Feedback (via RTCP) to request Keyframes via RTCP\r\n\r\nconst STATUS_OK = 200;\r\nconst STATUS_UNAUTH = 401;\r\n\r\n// The WWW_AUTH is of the format\r\n// TOKEN key=value\r\n// TOKEN key1=value1,key2=value2\r\n// TOKEN key1=\"value1\",key2=value2\r\n\r\n// RegEx reminder ? = Zero or One item\r\n// * = Zero or More items\r\n// + = 1 or More items\r\n// \\s is whitespace. But we need to 'escape the slash', hence \\\\s (or put the regex in / / characters)\r\n// ?= is a lookahead\r\n\r\n// The RegEx has two 'Groups'\r\n// \r\n// Group 1 (finding the Key)\r\n// Look for one or more characters (a..z or A..Z)\r\n// then look for whitespace\r\n// then look for 'equals'\r\n// then look for whitespace\r\n// then look for an optional Quote character\r\n//\r\n// Group 2 (finding the Value) -\r\n// Look for EITHER 'look backwards for a Quote', some characters, 'lookahead for a Quote'\r\n// OR some characters until (by looking ahead) you can see that another key comes next. The lookahead is 'optinal whitespace' 'comma' 'optional whitespace' 'chars' 'optinal whitespace' 'equals'\r\n// OR some characters followed by 'optinal whitespace'\r\n\r\nconst WWW_AUTH = \"WWW-Authenticate\";\r\nconst WWW_AUTH_REGEX = new RegExp('([a-zA-Z]+)\\\\s*=\\\\s*\"?((?<=\").*?(?=\")|.*?(?=\\\\s*,?\\\\s*[a-zA-Z]+\\\\s*=)|.+[^\\\\s])', \"g\");\r\n\r\nenum ReadStates {\r\n SEARCHING,\r\n READING_RTSP_HEADER,\r\n READING_RTSP_PAYLOAD,\r\n READING_RAW_PACKET_SIZE,\r\n READING_RAW_PACKET,\r\n}\r\n\r\ntype Connection = \"udp\" | \"tcp\";\r\n\r\ntype Headers = {\r\n [key: string]: string | number | undefined;\r\n Session?: string;\r\n Location?: string;\r\n CSeq?: number;\r\n \"WWW-Authenticate\"?: string;\r\n Transport?: string;\r\n Unsupported?: string;\r\n};\r\n\r\n// Details for each Session within the RTSP Stream (eg video session, audio session, metadata session)\r\ntype Detail = {\r\n codec: string;\r\n mediaSource: ({ // cannot work out how to pull this type in\r\n type: string;\r\n port: number;\r\n protocol: string;\r\n payloads?: string | undefined;\r\n } & transform.MediaDescription); // get Type from the interface\r\n transport: Transport['parameters']; // get Type from the interface\r\n isH264: boolean; // legacy API\r\n rtpChannel: number;\r\n rtcpChannel: number;\r\n\r\n // Cache any optional RTCP Sender Report values (used to calculate Wall Clock Time)\r\n sr_ntpMSW?: number;\r\n sr_ntpLSW?: number;\r\n sr_rtptimestamp?: number;\r\n};\r\n\r\nexport default class RTSPClient extends EventEmitter {\r\n username: string;\r\n password: string;\r\n headers: { [key: string]: string };\r\n\r\n isConnected = false;\r\n closed = false;\r\n\r\n // These are all set in #connect or #_netConnect.\r\n\r\n _url?: string;\r\n _client?: SocketUnion;\r\n _cSeq = 0;\r\n _unsupportedExtensions?: string[];\r\n // Example: 'SessionId'[';timeout=seconds']\r\n _session?: string;\r\n _keepAliveID?: NodeJS.Timeout;\r\n _nextFreeInterleavedChannel = 0;\r\n _nextFreeUDPPort = 5000;\r\n\r\n readState: ReadStates = ReadStates.SEARCHING;\r\n\r\n // Used as a cache for the data stream.\r\n // What's in here is based on current #readState.\r\n messageBytes: number[] = [];\r\n\r\n // Used for parsing RTSP responses,\r\n\r\n // Content-Length header in the RTSP message.\r\n rtspContentLength = 0;\r\n rtspStatusLine = \"\";\r\n rtspHeaders: Headers = {};\r\n\r\n // Used for parsing RTP/RTCP responses.\r\n\r\n rtspPacketLength = 0;\r\n rtspPacket = Buffer.from(\"\");\r\n rtspPacketPointer = 0;\r\n\r\n // Used in #_emptyReceiverReport.\r\n clientSSRC = generateSSRC();\r\n\r\n tcpSocket: SocketUnion = new net.Socket();\r\n setupResult: Array = [];\r\n constructor(\r\n username: string,\r\n password: string,\r\n headers?: { [key: string]: string }\r\n ) {\r\n super();\r\n\r\n this.username = username;\r\n this.password = password;\r\n this.headers = {\r\n ...(headers || {}),\r\n \"User-Agent\": \"yellowstone/3.x\",\r\n };\r\n }\r\n\r\n // This manages the lifecycle for the RTSP connection\r\n // over TCP.\r\n //\r\n // Sets #_client.\r\n //\r\n // Handles receiving data & closing port, called during\r\n // #connect.\r\n _netConnect(hostname: string, port: number, secure: boolean = false): Promise {\r\n return new Promise((resolve, reject) => {\r\n // Set after listeners defined.\r\n\r\n const errorListener = (err: any) => {\r\n client.removeListener(\"error\", errorListener);\r\n reject(err);\r\n };\r\n\r\n const postConnectErrorListener = (err: any) => {\r\n client.removeListener(\"error\", postConnectErrorListener);\r\n this.emit(\"error\", err);\r\n reject(err);\r\n };\r\n\r\n const closeListener = () => {\r\n client.removeListener(\"close\", closeListener);\r\n this.emit(\"close\");\r\n this.close(true);\r\n };\r\n\r\n const responseListener = (responseName: string, headers: Headers) => {\r\n const name = responseName.split(\" \")[0];\r\n\r\n if (name.indexOf(\"RTSP/\") === 0) {\r\n return;\r\n }\r\n\r\n if (name === \"REDIRECT\" || name === \"ANNOUNCE\") {\r\n this.respond(\"200 OK\", { CSeq: headers.CSeq });\r\n }\r\n\r\n if (name === \"REDIRECT\" && headers.Location) {\r\n this.close();\r\n this.connect(headers.Location);\r\n }\r\n };\r\n\r\n // rtsp or rtsps(with tls)\r\n let client: SocketUnion;\r\n if (secure == false) {\r\n client = net.connect(port, hostname, () => {\r\n this.isConnected = true;\r\n this._client = client;\r\n\r\n client.removeListener(\"error\", errorListener);\r\n client.on(\"error\", postConnectErrorListener);\r\n\r\n this.on(\"response\", responseListener);\r\n resolve(this);\r\n });\r\n }\r\n else {\r\n const options: tls.ConnectionOptions = {\r\n rejectUnauthorized: false\r\n };\r\n client = tls.connect(port, hostname, options, () => {\r\n console.log(\"TLS Connection\");\r\n this.isConnected = true;\r\n this._client = client;\r\n\r\n client.removeListener(\"error\", errorListener);\r\n\r\n this.on(\"response\", responseListener);\r\n resolve(this);\r\n });\r\n }\r\n\r\n client.on(\"data\", this._onData.bind(this));\r\n client.on(\"error\", errorListener);\r\n client.on(\"close\", closeListener);\r\n this.tcpSocket = client;\r\n });\r\n }\r\n\r\n async connect(\r\n url: string,\r\n {\r\n keepAlive = true,\r\n connection = \"udp\",\r\n secure = false,\r\n }: { keepAlive: boolean; connection?: Connection, secure: boolean } = {\r\n keepAlive: true,\r\n connection: \"udp\",\r\n secure: false\r\n }\r\n ): Promise {\r\n const { hostname, port } = urlParse((this._url = url));\r\n if (!hostname) {\r\n throw new Error(\"URL parsing error in connect method.\");\r\n }\r\n\r\n const details: Detail[] = [];\r\n\r\n await this._netConnect(hostname, parseInt(port || \"554\"), secure);\r\n await this.request(\"OPTIONS\");\r\n\r\n const describeRes = await this.request(\"DESCRIBE\", {\r\n Accept: \"application/sdp\",\r\n });\r\n if (!describeRes || !describeRes.mediaHeaders) {\r\n throw new Error(\r\n \"No media headers on DESCRIBE; RTSP server is broken (sanity check)\"\r\n );\r\n }\r\n\r\n // For now, only RTP/AVP and RTP/AVPF are supported. (Some RTSPS servers use RTP/SAVP)\r\n const { media } = transform.parse(describeRes.mediaHeaders.join(\"\\r\\n\"));\r\n\r\n // Loop over the Media Streams in the SDP looking for Video or Audio\r\n // In theory the SDP can contain multiple Video and Audio Streams. We only want one of each type\r\n let hasVideo = false;\r\n let hasAudio = false;\r\n let hasMetaData = false;\r\n let hasBackchannel = false;\r\n\r\n for (let x = 0; x < media.length; x++) {\r\n let needSetup = false;\r\n let codec = \"\";\r\n const mediaSource = media[x];\r\n\r\n\r\n // RFC says \"If none of the direction attributes (\"sendonly\", \"recvonly\", \"inactive\", and \"sendrecv\") are present,\r\n // the \"sendrecv\" SHOULD be assumed\r\n if (mediaSource.direction == undefined) mediaSource.direction = \"sendrecv\"; // Wowza does not send 'direction'\r\n\r\n if (\r\n mediaSource.type === \"video\" &&\r\n mediaSource.protocol === RTP_AVP &&\r\n mediaSource.rtp[0].codec === \"H264\"\r\n ) {\r\n this.emit(\"log\", \"H264 Video Stream Found in SDP\", \"\");\r\n if (hasVideo == false) {\r\n needSetup = true;\r\n hasVideo = true;\r\n codec = \"H264\";\r\n }\r\n }\r\n\r\n if (\r\n mediaSource.type === \"video\" &&\r\n mediaSource.protocol === RTP_AVP &&\r\n mediaSource.rtp[0].codec === \"H265\"\r\n ) {\r\n this.emit(\"log\", \"H265 Video Stream Found in SDP\", \"\");\r\n if (hasVideo == false) {\r\n needSetup = true;\r\n hasVideo = true;\r\n codec = \"H265\";\r\n }\r\n }\r\n\r\n if (\r\n mediaSource.type === \"video\" &&\r\n mediaSource.protocol === RTP_AVP &&\r\n mediaSource.rtp[0].codec === \"H266\"\r\n ) {\r\n this.emit(\"log\", \"H266 Video Stream Found in SDP\", \"\");\r\n if (hasVideo == false) {\r\n needSetup = true;\r\n hasVideo = true;\r\n codec = \"H266\";\r\n }\r\n }\r\n\r\n if (\r\n mediaSource.type === \"video\" &&\r\n (mediaSource.protocol === RTP_AVP || mediaSource.protocol === RTP_AVPF) &&\r\n mediaSource.rtp[0].codec === \"AV1\"\r\n ) {\r\n this.emit(\"log\", \"AV1 Video Stream Found in SDP\", \"\");\r\n if (hasVideo == false) {\r\n needSetup = true;\r\n hasVideo = true;\r\n codec = \"AV1\";\r\n }\r\n }\r\n\r\n if (\r\n mediaSource.type === \"audio\" &&\r\n (mediaSource.direction === \"recvonly\" || mediaSource.direction === \"sendrecv\") &&\r\n mediaSource.protocol === RTP_AVP &&\r\n mediaSource.rtp[0].codec.toLowerCase() === \"mpeg4-generic\" && // (RFC examples are lower case. Axis cameras use upper case)\r\n mediaSource.fmtp[0].config.includes(\"AAC\")\r\n ) {\r\n this.emit(\"log\", \"AAC Audio Stream Found in SDP\", \"\");\r\n if (hasAudio == false) {\r\n needSetup = true;\r\n hasAudio = true;\r\n codec = \"AAC\";\r\n }\r\n }\r\n\r\n if (mediaSource.type === \"audio\" &&\r\n mediaSource.direction === \"sendonly\" &&\r\n mediaSource.protocol === RTP_AVP) {\r\n this.emit(\"log\", \"Audio backchannel Found in SDP\", \"\");\r\n if (hasBackchannel == false) {\r\n needSetup = true;\r\n hasBackchannel = true;\r\n codec = mediaSource.rtp[0].codec;\r\n }\r\n }\r\n\r\n if (\r\n mediaSource.type === \"application\" &&\r\n mediaSource.protocol === RTP_AVP &&\r\n mediaSource.rtp[0].codec.toLowerCase() === \"vnd.onvif.metadata\"\r\n ) {\r\n this.emit(\"log\", \"ONVIF Meta Data Found in SDP\", \"\");\r\n if (hasMetaData == false) {\r\n needSetup = true;\r\n hasMetaData = true;\r\n codec = \"vnd.onvif.metadata\";\r\n }\r\n }\r\n\r\n if (needSetup) {\r\n let streamurl = \"\";\r\n // The 'control' in the SDP can be a relative or absolute uri\r\n if (mediaSource.control) {\r\n if (mediaSource.control.toLowerCase().startsWith(\"rtsp://\")) {\r\n // absolute path\r\n streamurl = mediaSource.control;\r\n } else {\r\n // relative path\r\n streamurl = this._url + \"/\" + mediaSource.control;\r\n }\r\n }\r\n\r\n // Perform a SETUP on the streamurl\r\n // either 'udp' RTP/RTCP packets\r\n // or with 'tcp' RTP/TCP packets which are interleaved into the TCP based RTSP socket\r\n let setupRes;\r\n let rtpChannel: number;\r\n let rtcpChannel: number;\r\n let rtpReceiver: dgram.Socket|null = null; // UDP mode init value\r\n let rtcpReceiver: dgram.Socket|null = null; // UDP mode init value\r\n\r\n if (connection === \"udp\") {\r\n // Create a pair of UDP listeners, even numbered port for RTP\r\n // and odd numbered port for RTCP\r\n\r\n rtpChannel = this._nextFreeUDPPort;\r\n rtcpChannel = this._nextFreeUDPPort + 1;\r\n this._nextFreeUDPPort += 2;\r\n\r\n const rtpPort = rtpChannel;\r\n rtpReceiver = dgram.createSocket(\"udp4\");\r\n\r\n rtpReceiver.on(\"message\", (buf, remote) => {\r\n let packet = parseRTPPacket(buf);\r\n\r\n // Add wall clock time\r\n const detail = this.setupResult.find(item => item.rtpChannel == rtpChannel);\r\n if (detail != undefined) packet.wallclockTime = this.GetWallClockTime(packet, detail);\r\n\r\n this.emit(\"data\", rtpPort, packet.payload, packet);\r\n });\r\n\r\n const rtcpPort = rtcpChannel;\r\n rtcpReceiver = dgram.createSocket(\"udp4\");\r\n\r\n rtcpReceiver.on(\"message\", (buf, remote) => {\r\n const packet = parseRTCPPacket(buf);\r\n\r\n // If this is a Sender Report, cache the NTP Wall Clock data\r\n if (packet.packetType == 200 && packet.senderReport != undefined) {\r\n let detail = this.setupResult.find(item => item.rtcpChannel == rtcpChannel);\r\n if (detail != undefined) {\r\n detail.sr_ntpMSW = packet.senderReport.ntpTimestampMSW;\r\n detail.sr_ntpLSW = packet.senderReport.ntpTimestampLSW;\r\n detail.sr_rtptimestamp = packet.senderReport.rtpTimestamp;\r\n }\r\n }\r\n\r\n this.emit(\"controlData\", rtcpPort, packet);\r\n\r\n const receiver_report = this._emptyReceiverReport();\r\n this._sendUDPData(remote.address, remote.port, receiver_report);\r\n });\r\n\r\n // Block until both UDP sockets are open.\r\n\r\n await new Promise((resolve) => {\r\n rtpReceiver?.bind(rtpPort, () => resolve({}));\r\n });\r\n\r\n await new Promise((resolve) => {\r\n rtcpReceiver?.bind(rtcpPort, () => resolve({}));\r\n });\r\n\r\n const setupHeader = {\r\n Transport: `RTP/AVP;unicast;client_port=${rtpPort}-${rtcpPort}`,\r\n };\r\n if (this._session)\r\n Object.assign(setupHeader, { Session: this._session });\r\n setupRes = await this.request(\"SETUP\", setupHeader, streamurl);\r\n } else if (connection === \"tcp\") {\r\n // channel 0, RTP\r\n // channel 1, RTCP\r\n\r\n rtpChannel = this._nextFreeInterleavedChannel;\r\n rtcpChannel = this._nextFreeInterleavedChannel + 1;\r\n this._nextFreeInterleavedChannel += 2;\r\n\r\n const setupHeader = {\r\n Transport: `RTP/AVP/TCP;interleaved=${rtpChannel}-${rtcpChannel}`,\r\n };\r\n if (this._session)\r\n Object.assign(setupHeader, { Session: this._session }); // not used on first SETUP\r\n setupRes = await this.request(\"SETUP\", setupHeader, streamurl);\r\n } else {\r\n throw new Error(\r\n `Connection parameter to RTSPClient#connect is ${connection}, not udp or tcp!`\r\n );\r\n }\r\n\r\n if (!setupRes) {\r\n throw new Error(\r\n \"No SETUP response; RTSP server is broken (sanity check)\"\r\n );\r\n }\r\n\r\n const { headers } = setupRes;\r\n\r\n if (!headers.Transport) {\r\n throw new Error(\r\n \"No Transport header on SETUP; RTSP server is broken (sanity check)\"\r\n );\r\n }\r\n\r\n const transport = parseTransport(headers.Transport);\r\n if (\r\n transport.protocol !== \"RTP/AVP/TCP\" &&\r\n transport.protocol !== \"RTP/AVP\"\r\n ) {\r\n throw new Error(\r\n \"Only RTSP servers supporting RTP/AVP(unicast) or RTP/AVP/TCP are supported at this time.\"\r\n );\r\n }\r\n\r\n // Patch from zoolyka (Zoltan Hajdu).\r\n // Try to open a hole in the NAT router (to allow incoming UDP packets)\r\n // by send a UDP packet for RTP and RTCP to the remote RTSP server.\r\n // Note, Roger did not have a router that needed this so the feature is untested.\r\n // May be better to change the RTCP message to a Receiver Report, leaving the RTP message as zero bytes\r\n if (connection === \"udp\" && transport && rtpReceiver && rtcpReceiver) {\r\n rtpReceiver.send(Buffer.from(''), Number(transport.parameters[\"server_port\"].split(\"-\")[0]), hostname);\r\n rtcpReceiver.send(Buffer.from(''), Number(transport.parameters[\"server_port\"].split(\"-\")[1]), hostname);\r\n }\r\n\r\n if (headers.Unsupported) {\r\n this._unsupportedExtensions = headers.Unsupported.split(\",\");\r\n }\r\n\r\n if (headers.Session) {\r\n this._session = headers.Session.split(\";\")[0];\r\n }\r\n\r\n const detail: Detail = {\r\n codec,\r\n mediaSource,\r\n transport: transport.parameters,\r\n isH264: codec === \"H264\", // legacy API\r\n rtpChannel,\r\n rtcpChannel,\r\n };\r\n\r\n details.push(detail);\r\n } // end if (needSetup)\r\n } // end for loop, looping over each media stream\r\n\r\n if (keepAlive) {\r\n // Start a Timer to send OPTIONS every 20 seconds to keep stream alive\r\n // using the Session ID\r\n this._keepAliveID = setInterval(() => {\r\n this.request(\"OPTIONS\", { Session: this._session });\r\n // this.request(\"OPTIONS\");\r\n }, 20 * 1000);\r\n }\r\n\r\n this.setupResult = details;\r\n return details;\r\n }\r\n\r\n request(\r\n requestName: string,\r\n headersParam: Headers = {},\r\n url?: string\r\n ): Promise<{ headers: Headers; mediaHeaders?: string[] } | void> {\r\n if (!this._client) {\r\n return Promise.resolve();\r\n }\r\n\r\n const id = ++this._cSeq;\r\n // mutable via string addition\r\n let req = `${requestName} ${url || this._url} RTSP/1.0\\r\\nCSeq: ${id}\\r\\n`;\r\n\r\n const headers = {\r\n ...this.headers,\r\n ...headersParam,\r\n };\r\n\r\n // NOTE:\r\n // If we cache the Authenitcation Type (Direct or Basic) then we could\r\n // re-compute an Authorization Header here and include in the RTSP Command\r\n // This would make connections a faster with fewer round-trips to the RTSP Server\r\n\r\n req += Object.entries(headers)\r\n .map(([key, value]) => `${key}: ${value}\\r\\n`)\r\n .join(\"\");\r\n\r\n this.emit(\"log\", req, \"C->S\");\r\n // Make sure to add an empty line after the request.\r\n this._client.write(`${req}\\r\\n`);\r\n\r\n return new Promise((resolve, reject) => {\r\n const responseHandler = (\r\n responseName: string,\r\n resHeaders: Headers,\r\n mediaHeaders: string[]\r\n ) => {\r\n const firstAnswer: string = String(resHeaders[\"\"]) || \"\";\r\n if (firstAnswer.indexOf(\"401\") >= 0 && 'Authorization' in headers) {\r\n // If the RTSP Command we sent included an Authorization and we have 401 error, then reject()\r\n reject(new Error(`Bad RTSP credentials!`));\r\n return;\r\n }\r\n if (resHeaders.CSeq !== id) {\r\n return;\r\n }\r\n\r\n this.removeListener(\"response\", responseHandler);\r\n\r\n const statusCode = parseInt(responseName.split(\" \")[1]);\r\n\r\n if (statusCode === STATUS_OK) {\r\n if (mediaHeaders.length > 0) {\r\n resolve({\r\n headers: resHeaders,\r\n mediaHeaders,\r\n });\r\n } else {\r\n resolve({\r\n headers: resHeaders,\r\n });\r\n }\r\n } else {\r\n const authHeader = resHeaders[WWW_AUTH];\r\n\r\n // We have status code unauthenticated.\r\n if (statusCode === STATUS_UNAUTH && authHeader) {\r\n const type = authHeader.split(\" \")[0];\r\n\r\n // Get auth properties from WWW_AUTH header.\r\n let realm = \"\";\r\n let nonce = \"\";\r\n let algorithm = \"MD5\"; // Default to MD5 if no algorthm is given. Milestone's RTSP server also supports SHA-256 for FIPS\r\n\r\n let match = WWW_AUTH_REGEX.exec(authHeader);\r\n while (match != null) {\r\n const prop = match[1];\r\n\r\n if (prop == \"realm\" && match[2]) {\r\n realm = match[2];\r\n }\r\n\r\n if (prop == \"nonce\" && match[2]) {\r\n nonce = match[2];\r\n }\r\n\r\n if (prop == \"algorithm\" && match[2]) {\r\n algorithm = match[2];\r\n }\r\n\r\n match = WWW_AUTH_REGEX.exec(authHeader);\r\n }\r\n\r\n // mutable, corresponds to Authorization header\r\n let authString = \"\";\r\n\r\n if (type === \"Digest\") {\r\n // Digest Authentication\r\n\r\n // Select Hash Function, default to MD5\r\n const HashFunction = (algorithm == \"SHA-256\" ? getSHA256Hash : getMD5Hash);\r\n\r\n const ha1 = HashFunction(\r\n `${this.username}:${realm}:${this.password}`\r\n );\r\n const ha2 = HashFunction(`${requestName}:${this._url}`);\r\n const ha3 = HashFunction(`${ha1}:${nonce}:${ha2}`);\r\n\r\n // Some RTSP servers to not accept \"algorithm=NNN\" in the authString and reject the authentication. So only add algorithm=ZZZZ when not using MD5\r\n if (algorithm == \"MD5\")\r\n authString = `Digest username=\"${this.username}\",realm=\"${realm}\",nonce=\"${nonce}\",uri=\"${this._url}\",response=\"${ha3}\"`;\r\n else\r\n authString = `Digest username=\"${this.username}\",realm=\"${realm}\",nonce=\"${nonce}\",algorithm=${algorithm},uri=\"${this._url}\",response=\"${ha3}\"`;\r\n } else if (type === \"Basic\") {\r\n // Basic Authentication\r\n // https://xkcd.com/538/\r\n const b64 = Buffer.from(\r\n `${this.username}:${this.password}`\r\n ).toString(\"base64\");\r\n authString = `Basic ${b64}`;\r\n }\r\n\r\n Object.assign(headers, {\r\n Authorization: authString,\r\n });\r\n\r\n resolve(this.request(requestName, headers, url)); // Call this.request with Authorized request\r\n return;\r\n }\r\n\r\n reject(new Error(`Bad RTSP status code ${statusCode}!`));\r\n return;\r\n }\r\n };\r\n\r\n this.on(\"response\", responseHandler);\r\n });\r\n }\r\n\r\n respond(status: string, headersParam: Headers = {}): void {\r\n if (!this._client) {\r\n return;\r\n }\r\n\r\n // mutable via string addition\r\n let res = `RTSP/1.0 ${status}\\r\\n`;\r\n\r\n const headers = {\r\n ...this.headers,\r\n ...headersParam,\r\n };\r\n\r\n res += Object.entries(headers)\r\n .map(([key, value]) => `${key}: ${value}\\r\\n`)\r\n .join(\"\");\r\n\r\n this.emit(\"log\", res, \"C->S\");\r\n this._client.write(`${res}\\r\\n`);\r\n }\r\n\r\n async play(): Promise {\r\n if (!this.isConnected) {\r\n throw new Error(\"Client is not connected.\");\r\n }\r\n\r\n await this.request(\"PLAY\", { Session: this._session });\r\n }\r\n\r\n async pause(): Promise {\r\n if (!this.isConnected) {\r\n throw new Error(\"Client is not connected.\");\r\n }\r\n\r\n await this.request(\"PAUSE\", { Session: this._session });\r\n }\r\n\r\n async sendAudioBackChannel(audioChunk: Buffer): Promise {\r\n let rtp, buf;\r\n const bufSize = 160;\r\n while (audioChunk.length > 0) {\r\n if (audioChunk.length > bufSize) {\r\n buf = audioChunk.slice(0, bufSize);\r\n audioChunk = audioChunk.slice(bufSize, audioChunk.length);\r\n } else {\r\n buf = audioChunk.slice(0, audioChunk.length);\r\n audioChunk = Buffer.from([]);\r\n }\r\n if (!rtp)\r\n rtp = new RTPPacket(buf);\r\n else\r\n rtp.payload = buf;\r\n // rtp.type = 8;// set động\r\n rtp.time += buf.length;\r\n rtp.seq++;\r\n const bufferLength = Buffer.alloc(2);\r\n bufferLength.writeUInt16BE(rtp.packet.length, 0);\r\n let channelInterleaved = this.setupResult.filter((value) => {\r\n return value.mediaSource.type === 'audio' && value.mediaSource.direction === 'sendonly';\r\n })[0].transport.interleaved;\r\n /* RTSP Interleaved Frame structure\r\n |dollar sign|channel identifier|data length|\r\n |1 Byte |1 Byte |2 Bytes |\r\n */\r\n channelInterleaved = channelInterleaved.split('-')[0];\r\n let interleavedHeader = Buffer.from([0x24]);// set '$'\r\n interleavedHeader = Buffer.concat([interleavedHeader, Buffer.from([channelInterleaved])]);\r\n interleavedHeader = Buffer.concat([interleavedHeader, bufferLength]);\r\n const dataToSend = Buffer.concat([interleavedHeader, rtp.packet]);\r\n await this._socketWrite(this.tcpSocket, dataToSend);\r\n }\r\n return;\r\n }\r\n\r\n async close(isImmediate = false): Promise {\r\n if (this.closed) return;\r\n this.closed = true;\r\n\r\n if (!this._client) {\r\n return;\r\n }\r\n\r\n if (!isImmediate) {\r\n await this.request(\"TEARDOWN\", {\r\n Session: this._session,\r\n });\r\n }\r\n\r\n this._client.end();\r\n this.removeAllListeners(\"response\");\r\n\r\n if (this._keepAliveID != undefined) {\r\n clearInterval(this._keepAliveID);\r\n this._keepAliveID = undefined;\r\n }\r\n\r\n this.isConnected = false;\r\n this._cSeq = 0;\r\n }\r\n\r\n _onData(data: Buffer): void {\r\n let index = 0;\r\n\r\n // $\r\n const PACKET_START = 0x24;\r\n // R\r\n const RTSP_HEADER_START = 0x52;\r\n // /n\r\n const ENDL = 10;\r\n\r\n while (index < data.length) {\r\n // read RTP or RTCP packet\r\n if (\r\n this.readState == ReadStates.SEARCHING &&\r\n data[index] == PACKET_START\r\n ) {\r\n this.messageBytes = [data[index]];\r\n index++;\r\n\r\n this.readState = ReadStates.READING_RAW_PACKET_SIZE;\r\n } else if (this.readState == ReadStates.READING_RAW_PACKET_SIZE) {\r\n // accumulate bytes for $, channel and length\r\n this.messageBytes.push(data[index]);\r\n index++;\r\n\r\n if (this.messageBytes.length == 4) {\r\n this.rtspPacketLength =\r\n (this.messageBytes[2] << 8) + this.messageBytes[3];\r\n\r\n if (this.rtspPacketLength > 0) {\r\n this.rtspPacket = Buffer.alloc(this.rtspPacketLength);\r\n this.rtspPacketPointer = 0;\r\n this.readState = ReadStates.READING_RAW_PACKET;\r\n } else {\r\n this.readState = ReadStates.SEARCHING;\r\n }\r\n }\r\n } else if (this.readState == ReadStates.READING_RAW_PACKET) {\r\n this.rtspPacket[this.rtspPacketPointer++] = data[index];\r\n index++;\r\n\r\n if (this.rtspPacketPointer == this.rtspPacketLength) {\r\n const packetChannel = this.messageBytes[1];\r\n if ((packetChannel & 0x01) === 0) {\r\n // even number\r\n let packet = parseRTPPacket(this.rtspPacket);\r\n\r\n // Get the Session Detail\r\n const detail = this.setupResult.find(item => item.rtpChannel == packetChannel);\r\n if (detail != undefined) packet.wallclockTime = this.GetWallClockTime(packet, detail);\r\n\r\n this.emit(\"data\", packetChannel, packet.payload, packet);\r\n }\r\n if ((packetChannel & 0x01) === 1) {\r\n // odd number\r\n const packet = parseRTCPPacket(this.rtspPacket);\r\n\r\n // If this is a Sender Report, cache the NTP Wall Clock data\r\n if (packet.packetType == 200 && packet.senderReport != undefined) {\r\n let detail = this.setupResult.find(item => item.rtcpChannel == packetChannel);\r\n if (detail != undefined) {\r\n detail.sr_ntpMSW = packet.senderReport.ntpTimestampMSW;\r\n detail.sr_ntpLSW = packet.senderReport.ntpTimestampLSW;\r\n detail.sr_rtptimestamp = packet.senderReport.rtpTimestamp;\r\n }\r\n }\r\n \r\n this.emit(\"controlData\", packetChannel, packet);\r\n\r\n const receiver_report = this._emptyReceiverReport();\r\n this._sendInterleavedData(packetChannel, receiver_report);\r\n }\r\n this.readState = ReadStates.SEARCHING;\r\n }\r\n // read response data\r\n } else if (\r\n this.readState == ReadStates.SEARCHING &&\r\n data[index] == RTSP_HEADER_START\r\n ) {\r\n // found the start of a RTSP rtsp_message\r\n this.messageBytes = [data[index]];\r\n index++;\r\n\r\n this.readState = ReadStates.READING_RTSP_HEADER;\r\n } else if (this.readState == ReadStates.READING_RTSP_HEADER) {\r\n // Reading a RTSP message.\r\n\r\n // Add character to the messageBytes\r\n // Ignore /r (13) but keep /n (10)\r\n if (data[index] != 13) {\r\n this.messageBytes.push(data[index]);\r\n }\r\n index++;\r\n\r\n // if we have two new lines back to back then we have a complete RTSP command,\r\n // note we may still need to read the Content Payload (the body) e.g. the SDP\r\n if (\r\n this.messageBytes.length >= 2 &&\r\n this.messageBytes[this.messageBytes.length - 2] == ENDL &&\r\n this.messageBytes[this.messageBytes.length - 1] == ENDL\r\n ) {\r\n // Parse the Header\r\n\r\n const text = String.fromCharCode.apply(null, this.messageBytes);\r\n const lines = text.split(\"\\n\");\r\n\r\n this.rtspContentLength = 0;\r\n this.rtspStatusLine = lines[0];\r\n this.rtspHeaders = {};\r\n\r\n lines.forEach((line) => {\r\n const indexOf = line.indexOf(\":\");\r\n\r\n if (indexOf !== line.length - 1) {\r\n const key = line.substring(0, indexOf).trim();\r\n const data = line.substring(indexOf + 1).trim();\r\n\r\n this.rtspHeaders[key] =\r\n key != \"Session\" && data.match(/^[0-9]+$/)\r\n ? parseInt(data, 10)\r\n : data;\r\n\r\n // workaround for buggy Hipcam RealServer/V1.0 camera which returns Content-length and not Content-Length\r\n if (key.toLowerCase() == \"content-length\") {\r\n this.rtspContentLength = parseInt(data, 10);\r\n }\r\n }\r\n });\r\n\r\n // if no content length, there there's no media headers\r\n // emit the message\r\n if (!this.rtspContentLength) {\r\n this.emit(\"log\", text, \"S->C\");\r\n\r\n this.emit(\"response\", this.rtspStatusLine, this.rtspHeaders, []);\r\n this.readState = ReadStates.SEARCHING;\r\n } else {\r\n this.messageBytes = [];\r\n this.readState = ReadStates.READING_RTSP_PAYLOAD;\r\n }\r\n }\r\n } else if (\r\n this.readState == ReadStates.READING_RTSP_PAYLOAD &&\r\n this.messageBytes.length < this.rtspContentLength\r\n ) {\r\n // Copy data into the RTSP payload\r\n this.messageBytes.push(data[index]);\r\n index++;\r\n\r\n if (this.messageBytes.length == this.rtspContentLength) {\r\n const text = String.fromCharCode.apply(null, this.messageBytes);\r\n const mediaHeaders = text.split(\"\\n\");\r\n\r\n // Emit the RTSP message\r\n this.emit(\r\n \"log\",\r\n String.fromCharCode.apply(null, this.messageBytes) + text,\r\n \"S->C\"\r\n );\r\n\r\n this.emit(\r\n \"response\",\r\n this.rtspStatusLine,\r\n this.rtspHeaders,\r\n mediaHeaders\r\n );\r\n this.readState = ReadStates.SEARCHING;\r\n }\r\n } else {\r\n // unexpected data\r\n throw new Error(\r\n \"Bug in RTSP data framing, please file an issue with the author with stacktrace.\"\r\n );\r\n }\r\n } // end while\r\n }\r\n\r\n _sendInterleavedData(channel: number, buffer: Buffer): void {\r\n if (!this._client) {\r\n return;\r\n }\r\n\r\n const req = `${buffer.length} bytes of interleaved data on channel ${channel}`;\r\n this.emit(\"log\", req, \"C->S\");\r\n\r\n const header = Buffer.alloc(4);\r\n header[0] = 0x24; // ascii $\r\n header[1] = channel;\r\n header[2] = (buffer.length >> 8) & 0xff;\r\n header[3] = (buffer.length >> 0) & 0xff;\r\n\r\n const data = Buffer.concat([header, buffer]);\r\n this._client.write(data);\r\n }\r\n\r\n _sendUDPData(host: string, port: number, buffer: Buffer): void {\r\n const udp = dgram.createSocket(\"udp4\");\r\n udp.send(buffer, 0, buffer.length, port, host, (err, bytes) => {\r\n // TODO: Don't ignore errors.\r\n udp.close();\r\n });\r\n }\r\n\r\n _emptyReceiverReport(): Buffer {\r\n const report = Buffer.alloc(8);\r\n const version = 2;\r\n const paddingBit = 0;\r\n const reportCount = 0; // an empty report\r\n const packetType = 201; // Receiver Report\r\n const length = report.length / 4 - 1; // num 32 bit words minus 1\r\n report[0] = (version << 6) + (paddingBit << 5) + reportCount;\r\n report[1] = packetType;\r\n report[2] = (length >> 8) & 0xff;\r\n report[3] = (length >> 0) & 0xff;\r\n report[4] = (this.clientSSRC >> 24) & 0xff;\r\n report[5] = (this.clientSSRC >> 16) & 0xff;\r\n report[6] = (this.clientSSRC >> 8) & 0xff;\r\n report[7] = (this.clientSSRC >> 0) & 0xff;\r\n\r\n return report;\r\n }\r\n\r\n async _socketWrite(socket: SocketUnion, data: Buffer): Promise {\r\n return new Promise((resolve, reject) => {\r\n setTimeout(() => {\r\n socket.write(data, (error: any) => {\r\n if (error) {\r\n reject(error);\r\n } else {\r\n resolve(undefined);\r\n }\r\n })\r\n }, 20);\r\n })\r\n }\r\n\r\n ntpBaseDate_ms = new Date(\"1900/1/1\").getTime();\r\n\r\n // Note we have had a RTP Packet in Yellowstone for many years, but the Audio Backchennal code added another object also called RTPPacket\r\n GetWallClockTime(packet: util.RTPPacket, detail: Detail): Date | undefined {\r\n\r\n // Add Wall Clock Time\r\n if (detail.sr_ntpMSW != undefined && detail.sr_ntpLSW != undefined && detail.sr_rtptimestamp != undefined && detail.mediaSource.rtp[0].rate != undefined) {\r\n let refTimestampSecs = detail.sr_rtptimestamp / detail.mediaSource.rtp[0].rate; // H264 is 90 kHz clock rate\r\n let packetTimestampSecs = packet.timestamp / detail.mediaSource.rtp[0].rate; // eg 90kHz\r\n let packetTimestampDeltaSecs = packetTimestampSecs - refTimestampSecs;\r\n let refTimestamp = new Date(this.ntpBaseDate_ms + (detail.sr_ntpMSW * 1000) + ((detail.sr_ntpLSW/Math.pow(2,32))*1000));\r\n let wallclockTime = new Date(refTimestamp.getTime() + (packetTimestampDeltaSecs*1000));\r\n return wallclockTime;\r\n }\r\n\r\n // Could not generate a Wall Clock Time\r\n return undefined;\r\n }\r\n\r\n}\r\n\r\nexport { RTPPacket, RTCPPacket } from \"./util\";\r\n"]} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts index 5fc566a..9a18fb1 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,8 +1,10 @@ import H264Transport from "./transports/H264Transport"; import H265Transport from "./transports/H265Transport"; +import H266Transport from "./transports/H266Transport"; +import AV1Transport from "./transports/AV1Transport"; import AACTransport from "./transports/AACTransport"; import ONVIFMetadataTransport from "./transports/ONVIFMetadataTransport"; import ONVIFClient from "./ONVIFClient"; import RTSPClient from "./RTSPClient"; import { RTPPacket, RTCPPacket } from "./util"; -export { H264Transport, H265Transport, AACTransport, ONVIFMetadataTransport, ONVIFClient, RTSPClient, RTPPacket, RTCPPacket }; +export { H264Transport, H265Transport, H266Transport, AV1Transport, AACTransport, ONVIFMetadataTransport, ONVIFClient, RTSPClient, RTPPacket, RTCPPacket }; diff --git a/dist/index.js b/dist/index.js index 5249422..d365638 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,10 +1,14 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.RTSPClient = exports.ONVIFClient = exports.ONVIFMetadataTransport = exports.AACTransport = exports.H265Transport = exports.H264Transport = void 0; +exports.RTSPClient = exports.ONVIFClient = exports.ONVIFMetadataTransport = exports.AACTransport = exports.AV1Transport = exports.H266Transport = exports.H265Transport = exports.H264Transport = void 0; const H264Transport_1 = require("./transports/H264Transport"); exports.H264Transport = H264Transport_1.default; const H265Transport_1 = require("./transports/H265Transport"); exports.H265Transport = H265Transport_1.default; +const H266Transport_1 = require("./transports/H266Transport"); +exports.H266Transport = H266Transport_1.default; +const AV1Transport_1 = require("./transports/AV1Transport"); +exports.AV1Transport = AV1Transport_1.default; const AACTransport_1 = require("./transports/AACTransport"); exports.AACTransport = AACTransport_1.default; const ONVIFMetadataTransport_1 = require("./transports/ONVIFMetadataTransport"); diff --git a/dist/index.js.map b/dist/index.js.map index 75b6c7b..0d3df35 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;AAAA,8DAAuD;AASrD,wBATK,uBAAa,CASL;AARf,8DAAuD;AASrD,wBATK,uBAAa,CASL;AARf,4DAAqD;AASnD,uBATK,sBAAY,CASL;AARd,gFAAyE;AASvE,iCATK,gCAAsB,CASL;AARxB,+CAAwC;AAStC,sBATK,qBAAW,CASL;AARb,6CAAsC;AASpC,qBATK,oBAAU,CASL","sourcesContent":["import H264Transport from \"./transports/H264Transport\";\nimport H265Transport from \"./transports/H265Transport\";\nimport AACTransport from \"./transports/AACTransport\";\nimport ONVIFMetadataTransport from \"./transports/ONVIFMetadataTransport\";\nimport ONVIFClient from \"./ONVIFClient\";\nimport RTSPClient from \"./RTSPClient\";\nimport {RTPPacket, RTCPPacket} from \"./util\";\n\nexport {\n H264Transport,\n H265Transport,\n AACTransport,\n ONVIFMetadataTransport,\n ONVIFClient,\n RTSPClient,\n RTPPacket,\n RTCPPacket\n}\n"]} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;AAAA,8DAAuD;AAWrD,wBAXK,uBAAa,CAWL;AAVf,8DAAuD;AAWrD,wBAXK,uBAAa,CAWL;AAVf,8DAAuD;AAWrD,wBAXK,uBAAa,CAWL;AAVf,4DAAqD;AAWnD,uBAXK,sBAAY,CAWL;AAVd,4DAAqD;AAWnD,uBAXK,sBAAY,CAWL;AAVd,gFAAyE;AAWvE,iCAXK,gCAAsB,CAWL;AAVxB,+CAAwC;AAWtC,sBAXK,qBAAW,CAWL;AAVb,6CAAsC;AAWpC,qBAXK,oBAAU,CAWL","sourcesContent":["import H264Transport from \"./transports/H264Transport\";\r\nimport H265Transport from \"./transports/H265Transport\";\r\nimport H266Transport from \"./transports/H266Transport\";\r\nimport AV1Transport from \"./transports/AV1Transport\";\r\nimport AACTransport from \"./transports/AACTransport\";\r\nimport ONVIFMetadataTransport from \"./transports/ONVIFMetadataTransport\";\r\nimport ONVIFClient from \"./ONVIFClient\";\r\nimport RTSPClient from \"./RTSPClient\";\r\nimport {RTPPacket, RTCPPacket} from \"./util\";\r\n\r\nexport {\r\n H264Transport,\r\n H265Transport,\r\n H266Transport,\r\n AV1Transport,\r\n AACTransport,\r\n ONVIFMetadataTransport,\r\n ONVIFClient,\r\n RTSPClient,\r\n RTPPacket,\r\n RTCPPacket\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/AACTransport.js.map b/dist/transports/AACTransport.js.map index a18fe05..4f1653d 100644 --- a/dist/transports/AACTransport.js.map +++ b/dist/transports/AACTransport.js.map @@ -1 +1 @@ -{"version":3,"file":"AACTransport.js","sourceRoot":"","sources":["../../lib/transports/AACTransport.ts"],"names":[],"mappings":"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","sourcesContent":["// De-packetize RTP packets to re-create AAC High Bit Rate (hbr) Audio\n// Write Audio to a .aac file\n// By Roger Hardiman, October 2019\n\nimport RTSPClient from \"../RTSPClient\";\nimport { RTPPacket, BitStream } from \"../util\";\n\nimport * as transform from \"sdp-transform\";\nimport { Writable } from \"stream\";\n\ninterface Details {\n codec: string;\n mediaSource: transform.MediaDescription;\n rtpChannel: number;\n rtcpChannel: number;\n}\n\nexport default class AACTransport {\n client: RTSPClient;\n stream: Writable;\n\n ObjectType = 0;\n FrequencyIndex = 0;\n ChannelConfiguration = 0;\n\n constructor(client: RTSPClient, stream: Writable, details: Details) {\n this.client = client;\n this.stream = stream;\n\n client.on(\"data\", (channel, data, packet) => {\n if (channel == details.rtpChannel) {\n this.processRTPPacket(packet);\n }\n });\n\n // Process the SDP to get the parameters for the AAC audio\n // \"profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1490\"\n const fmtp = details.mediaSource.fmtp[0];\n const fmtpConfig = transform.parseParams(fmtp.config);\n\n const bs = new BitStream();\n bs.AddHexString(fmtpConfig[\"config\"].toString());\n\n /***\n 5 bits: object type\n if (object type == 31)\n 6 bits + 32: object type\n 4 bits: frequency index\n if (frequency index == 15)\n 24 bits: frequency\n 4 bits: channel configuration\n var bits: AOT Specific Config\n ***/\n\n // Read 5 bits\n this.ObjectType = bs.Read(5);\n\n // Read 4 bits\n this.FrequencyIndex = bs.Read(4);\n\n // Read 4 bits\n this.ChannelConfiguration = bs.Read(4);\n }\n\n processRTPPacket(packet: RTPPacket): void {\n // RTP Payload for MPEG4-GENERIC consis of multiple blocks of data\n\n // Each block has 3 parts\n // Part 1 - Acesss Unit Header Length + Header\n // Part 2 - Access Unit Auxiliary Data Length + Data (not used in AAC High Bitrate)\n // Part 3 - Access Unit Audio Data\n\n const rtp_payload = packet.payload;\n let ptr = 0;\n const audio_data = [];\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (ptr + 4 > rtp_payload.length) break; // 2 bytes for AU Header Length, 2 bytes of AU Header payload\n\n // Get Size of the AU Header\n const au_headers_length_bits =\n (rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0); // 16 bits\n const au_headers_length = Math.ceil(au_headers_length_bits / 8.0);\n ptr += 2;\n\n // Examine the AU Header. Get the size of the AAC data\n const aac_frame_size =\n ((rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0)) >> 3; // 13 bits\n const aac_index_delta = rtp_payload[ptr + 1] & 0x03; // 3 bits\n ptr += au_headers_length;\n\n // extract the AAC block\n if (ptr + aac_frame_size > rtp_payload.length) break; // not enough data to copy\n audio_data.push(rtp_payload.slice(ptr, ptr + aac_frame_size));\n ptr += aac_frame_size;\n }\n\n // Write Audio Data Transport Stream (adts) header\n // followed by the AAC data\n for (let x = 0; x < audio_data.length; x++) {\n const data = audio_data[x];\n\n const bs = new BitStream(); //TODO - we could cache the header bitstream\n\n bs.AddValue(0xfff, 12); // (a) Start of data\n bs.AddValue(0, 1); // (b) Version ID, 0 = MPEG4\n bs.AddValue(0, 2); // (c) Layer always 2 bits set to 0\n const protection_absent = 1;\n bs.AddValue(protection_absent, 1); // (d) 1 = No CRC\n bs.AddValue(this.ObjectType - 1, 2); // (e) MPEG Object Type / Profile, minus 1\n bs.AddValue(this.FrequencyIndex, 4); // (f)\n bs.AddValue(0, 1); // (g) private bit. Always zero\n bs.AddValue(this.ChannelConfiguration, 3); // (h)\n bs.AddValue(0, 1); // (i) originality\n bs.AddValue(0, 1); // (j) home\n bs.AddValue(0, 1); // (k) copyrighted id\n bs.AddValue(0, 1); // (l) copyright id start\n bs.AddValue(data.length + 7, 13); // (m) AAC data + size of the ASDT header\n bs.AddValue(2047, 11); // (n) buffer fullness ???\n const num_acc_frames = 1;\n bs.AddValue(num_acc_frames - 1, 1); // (o) num of AAC Frames, minus 1\n\n // TODO If Protection was On [value=0], there would be a 16 bit CRC here\n // if (protection_absent == 0) bs.AddValue(/*Calc CRC()*/, 16); // (p)\n\n const header = bs.ToArray();\n\n // write to the aac file\n this.stream.write(header);\n this.stream.write(data);\n }\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"AACTransport.js","sourceRoot":"","sources":["../../lib/transports/AACTransport.ts"],"names":[],"mappings":"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","sourcesContent":["// De-packetize RTP packets to re-create AAC High Bit Rate (hbr) Audio\r\n// Write Audio to a .aac file\r\n// By Roger Hardiman, October 2019\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport { RTPPacket, BitStream } from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\n\r\ninterface Details {\r\n codec: string;\r\n mediaSource: transform.MediaDescription;\r\n rtpChannel: number;\r\n rtcpChannel: number;\r\n}\r\n\r\nexport default class AACTransport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n\r\n ObjectType = 0;\r\n FrequencyIndex = 0;\r\n ChannelConfiguration = 0;\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n this.processRTPPacket(packet);\r\n }\r\n });\r\n\r\n // Process the SDP to get the parameters for the AAC audio\r\n // \"profile-level-id=1;mode=AAC-hbr;sizelength=13;indexlength=3;indexdeltalength=3;config=1490\"\r\n const fmtp = details.mediaSource.fmtp[0];\r\n const fmtpConfig = transform.parseParams(fmtp.config);\r\n\r\n const bs = new BitStream();\r\n bs.AddHexString(fmtpConfig[\"config\"].toString());\r\n\r\n /***\r\n 5 bits: object type\r\n if (object type == 31)\r\n 6 bits + 32: object type\r\n 4 bits: frequency index\r\n if (frequency index == 15)\r\n 24 bits: frequency\r\n 4 bits: channel configuration\r\n var bits: AOT Specific Config\r\n ***/\r\n\r\n // Read 5 bits\r\n this.ObjectType = bs.Read(5);\r\n\r\n // Read 4 bits\r\n this.FrequencyIndex = bs.Read(4);\r\n\r\n // Read 4 bits\r\n this.ChannelConfiguration = bs.Read(4);\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // RTP Payload for MPEG4-GENERIC consis of multiple blocks of data\r\n\r\n // Each block has 3 parts\r\n // Part 1 - Acesss Unit Header Length + Header\r\n // Part 2 - Access Unit Auxiliary Data Length + Data (not used in AAC High Bitrate)\r\n // Part 3 - Access Unit Audio Data\r\n\r\n const rtp_payload = packet.payload;\r\n let ptr = 0;\r\n const audio_data = [];\r\n\r\n // eslint-disable-next-line no-constant-condition\r\n while (true) {\r\n if (ptr + 4 > rtp_payload.length) break; // 2 bytes for AU Header Length, 2 bytes of AU Header payload\r\n\r\n // Get Size of the AU Header\r\n const au_headers_length_bits =\r\n (rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0); // 16 bits\r\n const au_headers_length = Math.ceil(au_headers_length_bits / 8.0);\r\n ptr += 2;\r\n\r\n // Examine the AU Header. Get the size of the AAC data\r\n const aac_frame_size =\r\n ((rtp_payload[ptr] << 8) + (rtp_payload[ptr + 1] << 0)) >> 3; // 13 bits\r\n const aac_index_delta = rtp_payload[ptr + 1] & 0x03; // 3 bits\r\n ptr += au_headers_length;\r\n\r\n // extract the AAC block\r\n if (ptr + aac_frame_size > rtp_payload.length) break; // not enough data to copy\r\n audio_data.push(rtp_payload.slice(ptr, ptr + aac_frame_size));\r\n ptr += aac_frame_size;\r\n }\r\n\r\n // Write Audio Data Transport Stream (adts) header\r\n // followed by the AAC data\r\n for (let x = 0; x < audio_data.length; x++) {\r\n const data = audio_data[x];\r\n\r\n const bs = new BitStream(); //TODO - we could cache the header bitstream\r\n\r\n bs.AddValue(0xfff, 12); // (a) Start of data\r\n bs.AddValue(0, 1); // (b) Version ID, 0 = MPEG4\r\n bs.AddValue(0, 2); // (c) Layer always 2 bits set to 0\r\n const protection_absent = 1;\r\n bs.AddValue(protection_absent, 1); // (d) 1 = No CRC\r\n bs.AddValue(this.ObjectType - 1, 2); // (e) MPEG Object Type / Profile, minus 1\r\n bs.AddValue(this.FrequencyIndex, 4); // (f)\r\n bs.AddValue(0, 1); // (g) private bit. Always zero\r\n bs.AddValue(this.ChannelConfiguration, 3); // (h)\r\n bs.AddValue(0, 1); // (i) originality\r\n bs.AddValue(0, 1); // (j) home\r\n bs.AddValue(0, 1); // (k) copyrighted id\r\n bs.AddValue(0, 1); // (l) copyright id start\r\n bs.AddValue(data.length + 7, 13); // (m) AAC data + size of the ASDT header\r\n bs.AddValue(2047, 11); // (n) buffer fullness ???\r\n const num_acc_frames = 1;\r\n bs.AddValue(num_acc_frames - 1, 1); // (o) num of AAC Frames, minus 1\r\n\r\n // TODO If Protection was On [value=0], there would be a 16 bit CRC here\r\n // if (protection_absent == 0) bs.AddValue(/*Calc CRC()*/, 16); // (p)\r\n\r\n const header = bs.ToArray();\r\n\r\n // write to the aac file\r\n this.stream.write(header);\r\n this.stream.write(data);\r\n }\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/AV1Transport.d.ts b/dist/transports/AV1Transport.d.ts new file mode 100644 index 0000000..b4fa7a4 --- /dev/null +++ b/dist/transports/AV1Transport.d.ts @@ -0,0 +1,24 @@ +/// +/// +import RTSPClient from "../RTSPClient"; +import { RTPPacket } from "../util"; +import * as transform from "sdp-transform"; +import { Writable } from "stream"; +interface Details { + codec: string; + mediaSource: transform.MediaDescription; + rtpChannel: number; + rtcpChannel: number; +} +export default class AV1Transport { + client: RTSPClient; + stream: Writable; + rtpPackets: Buffer[]; + waitingForSequenceHeader: boolean; + constructor(client: RTSPClient, stream: Writable, details: Details); + processConnectionDetails(details: Details): void; + processRTPPacket(packet: RTPPacket): void; + processRTPFrame(rtpPackets: Buffer[]): void; + GetOBUName(obu_type: number): string; +} +export {}; diff --git a/dist/transports/AV1Transport.js b/dist/transports/AV1Transport.js new file mode 100644 index 0000000..4d1696f --- /dev/null +++ b/dist/transports/AV1Transport.js @@ -0,0 +1,264 @@ +"use strict"; +// Handle AV1 Video +// Process SDP and RTP packets +// De-packetize RTP packets to re-create AV1 OBUs +// Write AV1 OBUs to a .obu file which can be played with "ffplay" +// +// By Roger Hardiman, May 2025 +Object.defineProperty(exports, "__esModule", { value: true }); +const transform = require("sdp-transform"); +class AV1Transport { + constructor(client, stream, details) { + this.rtpPackets = []; + this.waitingForSequenceHeader = true; // used when writing .obu file as 'ffplay' does not like it if the first OBUs are not TD then SH + this.client = client; + this.stream = stream; + // process 'fmtp' + this.processConnectionDetails(details); + client.on("data", (channel, data, packet) => { + if (channel == details.rtpChannel) { + this.processRTPPacket(packet); + } + }); + } + processConnectionDetails(details) { + // There is no Sequence Header (the extra_data / parameter set) in the SDP of AV1 + // and currently we have no use for profile, level-idx or tier + const fmtp = (details.mediaSource.fmtp)[0]; + if (!fmtp) { + return; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const fmtpConfig = transform.parseParams(fmtp.config); + /* + const _profile = fmtpConfig['profile'].toString(); + const _level_idx = fmtpConfig['level-idx'].toString(); + const _tier = fmtpConfig['tier'].toString(); + */ + } + processRTPPacket(packet) { + // Accumatate RTP packets + this.rtpPackets.push(packet.payload); + // When Marker is set to 1 pass the group of packets to processRTPFrame() + if (packet.marker == 1) { + this.processRTPFrame(this.rtpPackets); + this.rtpPackets = []; + } + } + processRTPFrame(rtpPackets) { + const obus = []; // the OBUs from the RTSP server, which normally come without their length bytes (leb128) + for (let i = 0; i < rtpPackets.length; i++) { + // The RTP packet can contain more than one OBU element + // Examine the first byte of the RTP data, the Aggregation Header. + // Z = 1 Indicates that the first OBU element in this RTP packet is a contination of the last OBU element from the last packet (ie fragmentation) + // Y = 1 Indicates that the last OBU element in this RTP packet will be fragmented and will continue in the next RTP packet (so next RTP packet will have Z=1) + // W = Number of OBU elements in this RTP Packet, or 0 if the number of OBUs is not given + // If W = 0, all OBU elements are prefixed with a LEB128 length. + // If W > 0, the OBU elements _except the last one_ have a LEB128 length prefix. Last OBU has no LEB128 length prefix. It can be computed from the RTP payload size + // N = 1 Indicates first packet of a Coded Video Sequence + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // |Z|Y| W |N|-|-|-| + // +-+-+-+-+-+-+-+-+ + const packet = rtpPackets[i]; + let ptr = 0; + const aggregation_header = packet[ptr]; + ptr++; + const aggregation_header_z_bit = (aggregation_header >> 7) & 0x01; + //const aggregation_header_y_bit = (aggregation_header >> 6) & 0x01; + const aggregation_header_w = (aggregation_header >> 4) & 0x03; + //const aggregation_header_n_bit = (aggregation_header >> 3) & 0x01; + /* + if (aggregation_header_z_bit == 1) { + console.log("AV1 Z Fragmentation"); + } + if (aggregation_header_y_bit == 1) { + console.log("AV1 Y Fragmentation"); + } + if (aggregation_header_n_bit == 1) { + console.log("AV1 N Bit is set"); + } + */ + let obuCount = 0; + // Loop over each OBU + while (ptr < packet.length) { + obuCount++; + // Check if the OBU element will be prefixed with a LEB128 length + let hasLeb128Prefix = false; + if (aggregation_header_w == 0) + hasLeb128Prefix = true; + if (aggregation_header_w != 0 && obuCount != aggregation_header_w) + hasLeb128Prefix = true; + let obu_element_size = 0; + if (hasLeb128Prefix) { + for (let i = 0; i < 8; i++) { // max 8 bytes + const lebByte = packet[ptr]; + ptr++; + obu_element_size = obu_element_size + ((lebByte & 0x7F) << (i * 7)); + if ((lebByte & 0x80) == 0) { + // finished + break; + } + } + } + else { + // no LEB128. Size is the remaining bytes + obu_element_size = packet.length - ptr; + } + // Extract the OBU + let obu = packet.slice(ptr, ptr + obu_element_size); + ptr = ptr + obu_element_size; + // Check Z bit. If Z = 1 we need to append the new OBU data to the Partial OBU data (fragmented data) from the last RTP packet + if (aggregation_header_z_bit == 1 && obuCount == 1) { + // Pop off the last 'partial' OBU + const lastPartialObu = obus.pop(); + if (lastPartialObu == undefined) { + // error. We do not have any partial data to append this new OBU data to so drop the new OBU. + } + else { + const combinedObuData = Buffer.concat([ + lastPartialObu, + obu + ]); + obu = combinedObuData; + } + } + // We have an OBU so store it + // Note if 'Y' is set, and we are processing the last OBU in the RTP packet, the data will be only part of the fragmented data + // but we don't check the Y bit. We rely on the Z bit being set to 1 in the next RTP packet + if (obu.length > 0) { + obus.push(obu); + } + } // Ptr now parsed all OBU elements in this RTP packet + } // end for-each RTP packet in the Frame + // Write out all the OBUs + // When we write to a File, we need to add the Temporal Delimiter (TD) + // and then the SEQUENCE_HEADER (SH) + // and then the other OBUs. + // The OBUs are modified to include a LEB128 size as required in the AV1 File Format Spec Section 5 file format + // The modification is needed as the AV1 RTSP Spec strips out the OBU lengths and replaces them with OBU Prefix lengths + // which come before the OBU instead of inside the OBU. + // There is an Annex B format that keeps the length bytes as prefixes on the OBU (instead of inside them) but I've not implemented that + // Check if this Frame includes a Sequence Header + if (this.waitingForSequenceHeader) { + for (const obu of obus) { + const obuHeader = obu[0]; + const obu_type = (obuHeader >> 3) & 0x0F; + if (obu_type == 1) { // Sequence Header + this.waitingForSequenceHeader = false; + break; + } + } + } + if (this.waitingForSequenceHeader) { + // drop this RTP frame + console.log("AV1: Waiting for Sequence Header"); + } + else { + // Write the OBUs + const temporalDelimiter = Buffer.from([0x12, 0x00]); + this.stream.write(temporalDelimiter); + for (const obu of obus) { + // Take a look at the OBU and see what it contains to verify it looks correct + // OBU Header Byte + // -------------------------------------------------------------------------------- + // | 7 | 6,5,4,3 | 2 | 1 | 0 | + // |1 bit forbidden|4 bit OBU Type|1 bit hasExtension|1 bit hasSize|1 bit reserved| + // -------------------------------------------------------------------------------- + if (obu.length > 0) { + const obuHeader = obu[0]; + //const forbidden_bit = (obuHeader >> 7) & 0x01; + const obu_type = (obuHeader >> 3) & 0x0F; + const extension_bit = (obuHeader >> 2) & 0x01; + const size_bit = (obuHeader >> 1) & 0x01; + /* + const obu_name = this.GetOBUName(obu_type); + console.log("Found AV1 OBU:" + obu_name); + + if (forbidden_bit == 1) { + console.log("OBU Forbidden Bit Error"); + } + + if (obu_name == "Reserved") { + console.log("OBU Type Error"); + } + */ + if (this.waitingForSequenceHeader) { + if (obu_type == 1) { + this.waitingForSequenceHeader = false; + // Write the First TD + } + else { + // we are still waiting so drop this OBU + console.log("AV1 file writing: Dropping OBU while waiting for Sequence Header"); + continue; + } + } + // In order to write to a .obu file, we have to ensure there is a LEB128 Size after the OBU Header Byte (and Optional Extension Byte) + // The LEB128 length gets stripped out in RTP packets + if (size_bit == 0) { + let size = 0; + if (extension_bit == 0) + size = obu.length - 1; // -1 for the OBU header + if (extension_bit == 1) + size = obu.length - 2; // -2 for the OBU header and the Header Extension Byte + // Convert the Size into a LEB128 byte sequence + const leb128_bytes = []; + while (size > 0) { + const lower_7_bits = (size & 0x7F); + if (size <= 127) { + leb128_bytes.push(lower_7_bits); // leave msbit as 0 + } + else { + leb128_bytes.push(0x80 + lower_7_bits); // set msbit to 1 + } + size = (size >> 7); + } + const leb128Buffer = Buffer.from(leb128_bytes); + // Insert the leb128 size into the OBU + const header_and_extention_len = (extension_bit == 0 ? 1 : 2); // length of header PLUS extension + // Insert the LEB128 length + const newObu = Buffer.concat([ + obu.slice(0, 0 + header_and_extention_len), + leb128Buffer, + obu.slice(header_and_extention_len, obu.length) + ]); + // Set the hasSize flag to '1' in the OBU Header + newObu[0] = newObu[0] | 0x02; + // WRITE DATA + this.stream.write(newObu); + } + else { + // This OBU came with a LEB128 length. The AV1 RTSP Spec says the RTSP server should strip them out, but this + // handles the case where a RTSP Server leaves them in + // WRITE DATA + this.stream.write(obu); + } + } + } // for-each OBU in OBUs Arrau + } + } + GetOBUName(obu_type) { + switch (obu_type) { + case 0: return "Reserved"; + case 1: return "SEQUENCE_HEADER"; + case 2: return "TEMPORAL_DELIMITER"; + case 3: return "FRAME_HEADER"; + case 4: return "TILE_GROUP"; + case 5: return "METADATA"; + case 6: return "FRAME"; + case 7: return "REDUNDANT_FRAME_HEADER"; + case 8: return "TILE_LIST"; + case 9: return "Reserved"; + case 10: return "Reserved"; + case 11: return "Reserved"; + case 12: return "Reserved"; + case 13: return "Reserved"; + case 14: return "Reserved"; + case 15: return "PADDING"; + } + return "Error getting OBU Type"; + } +} +exports.default = AV1Transport; +//# sourceMappingURL=AV1Transport.js.map \ No newline at end of file diff --git a/dist/transports/AV1Transport.js.map b/dist/transports/AV1Transport.js.map new file mode 100644 index 0000000..ea69659 --- /dev/null +++ b/dist/transports/AV1Transport.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AV1Transport.js","sourceRoot":"","sources":["../../lib/transports/AV1Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Handle AV1 Video\r\n// Process SDP and RTP packets\r\n// De-packetize RTP packets to re-create AV1 OBUs\r\n// Write AV1 OBUs to a .obu file which can be played with \"ffplay\"\r\n//\r\n// By Roger Hardiman, May 2025\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport { RTPPacket } from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\n\r\ninterface Details {\r\n codec: string\r\n mediaSource: transform.MediaDescription\r\n rtpChannel: number,\r\n rtcpChannel: number\r\n}\r\n\r\nexport default class AV1Transport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n\r\n rtpPackets: Buffer[] = [];\r\n waitingForSequenceHeader = true; // used when writing .obu file as 'ffplay' does not like it if the first OBUs are not TD then SH\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n\r\n // process 'fmtp'\r\n this.processConnectionDetails(details);\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n this.processRTPPacket(packet);\r\n }\r\n });\r\n\r\n }\r\n\r\n processConnectionDetails(details: Details): void {\r\n // There is no Sequence Header (the extra_data / parameter set) in the SDP of AV1\r\n // and currently we have no use for profile, level-idx or tier\r\n const fmtp = (details.mediaSource.fmtp)[0];\r\n\r\n if (!fmtp) {\r\n return;\r\n }\r\n\r\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\r\n const fmtpConfig = transform.parseParams(fmtp.config);\r\n\r\n /*\r\n const _profile = fmtpConfig['profile'].toString();\r\n const _level_idx = fmtpConfig['level-idx'].toString();\r\n const _tier = fmtpConfig['tier'].toString();\r\n */\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // Accumatate RTP packets\r\n this.rtpPackets.push(packet.payload);\r\n\r\n // When Marker is set to 1 pass the group of packets to processRTPFrame()\r\n if (packet.marker == 1) {\r\n this.processRTPFrame(this.rtpPackets);\r\n this.rtpPackets = [];\r\n }\r\n }\r\n\r\n processRTPFrame(rtpPackets: Buffer[]): void {\r\n const obus: Buffer[] = []; // the OBUs from the RTSP server, which normally come without their length bytes (leb128) \r\n \r\n for (let i = 0; i < rtpPackets.length; i++) {\r\n\r\n // The RTP packet can contain more than one OBU element\r\n\r\n // Examine the first byte of the RTP data, the Aggregation Header.\r\n // Z = 1 Indicates that the first OBU element in this RTP packet is a contination of the last OBU element from the last packet (ie fragmentation)\r\n // Y = 1 Indicates that the last OBU element in this RTP packet will be fragmented and will continue in the next RTP packet (so next RTP packet will have Z=1)\r\n \r\n // W = Number of OBU elements in this RTP Packet, or 0 if the number of OBUs is not given\r\n // If W = 0, all OBU elements are prefixed with a LEB128 length.\r\n // If W > 0, the OBU elements _except the last one_ have a LEB128 length prefix. Last OBU has no LEB128 length prefix. It can be computed from the RTP payload size\r\n\r\n // N = 1 Indicates first packet of a Coded Video Sequence\r\n\r\n // 0 1 2 3 4 5 6 7\r\n // +-+-+-+-+-+-+-+-+\r\n // |Z|Y| W |N|-|-|-|\r\n // +-+-+-+-+-+-+-+-+\r\n const packet = rtpPackets[i];\r\n\r\n let ptr = 0;\r\n const aggregation_header = packet[ptr];\r\n ptr++\r\n const aggregation_header_z_bit = (aggregation_header >> 7) & 0x01;\r\n //const aggregation_header_y_bit = (aggregation_header >> 6) & 0x01;\r\n const aggregation_header_w = (aggregation_header >> 4) & 0x03;\r\n //const aggregation_header_n_bit = (aggregation_header >> 3) & 0x01;\r\n\r\n /*\r\n if (aggregation_header_z_bit == 1) {\r\n console.log(\"AV1 Z Fragmentation\");\r\n }\r\n if (aggregation_header_y_bit == 1) {\r\n console.log(\"AV1 Y Fragmentation\");\r\n }\r\n if (aggregation_header_n_bit == 1) {\r\n console.log(\"AV1 N Bit is set\");\r\n }\r\n */\r\n\r\n let obuCount = 0;\r\n\r\n // Loop over each OBU\r\n while (ptr < packet.length) {\r\n\r\n obuCount++;\r\n\r\n // Check if the OBU element will be prefixed with a LEB128 length\r\n let hasLeb128Prefix = false;\r\n if (aggregation_header_w == 0) hasLeb128Prefix = true;\r\n if (aggregation_header_w != 0 && obuCount != aggregation_header_w) hasLeb128Prefix = true;\r\n\r\n let obu_element_size = 0;\r\n if (hasLeb128Prefix) {\r\n for (let i = 0; i < 8; i++) { // max 8 bytes\r\n const lebByte = packet[ptr];\r\n ptr++;\r\n\r\n obu_element_size = obu_element_size + ((lebByte & 0x7F) << (i * 7));\r\n\r\n if ((lebByte & 0x80) == 0) {\r\n // finished\r\n break;\r\n }\r\n }\r\n } else {\r\n // no LEB128. Size is the remaining bytes\r\n obu_element_size = packet.length - ptr;\r\n }\r\n\r\n // Extract the OBU\r\n let obu = packet.slice(ptr, ptr + obu_element_size);\r\n ptr = ptr + obu_element_size;\r\n\r\n // Check Z bit. If Z = 1 we need to append the new OBU data to the Partial OBU data (fragmented data) from the last RTP packet\r\n if (aggregation_header_z_bit == 1 && obuCount == 1) {\r\n // Pop off the last 'partial' OBU\r\n const lastPartialObu = obus.pop();\r\n if (lastPartialObu == undefined) {\r\n // error. We do not have any partial data to append this new OBU data to so drop the new OBU.\r\n } else {\r\n const combinedObuData = Buffer.concat([\r\n lastPartialObu,\r\n obu\r\n ]);\r\n obu = combinedObuData;\r\n }\r\n }\r\n\r\n // We have an OBU so store it\r\n // Note if 'Y' is set, and we are processing the last OBU in the RTP packet, the data will be only part of the fragmented data\r\n // but we don't check the Y bit. We rely on the Z bit being set to 1 in the next RTP packet\r\n if (obu.length > 0) {\r\n obus.push(obu);\r\n }\r\n } // Ptr now parsed all OBU elements in this RTP packet\r\n\r\n\r\n } // end for-each RTP packet in the Frame\r\n\r\n\r\n // Write out all the OBUs\r\n // When we write to a File, we need to add the Temporal Delimiter (TD)\r\n // and then the SEQUENCE_HEADER (SH)\r\n // and then the other OBUs.\r\n // The OBUs are modified to include a LEB128 size as required in the AV1 File Format Spec Section 5 file format\r\n // The modification is needed as the AV1 RTSP Spec strips out the OBU lengths and replaces them with OBU Prefix lengths\r\n // which come before the OBU instead of inside the OBU.\r\n // There is an Annex B format that keeps the length bytes as prefixes on the OBU (instead of inside them) but I've not implemented that\r\n\r\n // Check if this Frame includes a Sequence Header\r\n if (this.waitingForSequenceHeader) {\r\n for (const obu of obus) {\r\n const obuHeader = obu[0];\r\n const obu_type = (obuHeader >> 3) & 0x0F;\r\n if (obu_type == 1) { // Sequence Header\r\n this.waitingForSequenceHeader = false;\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (this.waitingForSequenceHeader) {\r\n // drop this RTP frame\r\n console.log(\"AV1: Waiting for Sequence Header\")\r\n }\r\n else\r\n {\r\n // Write the OBUs\r\n const temporalDelimiter = Buffer.from([0x12, 0x00]);\r\n this.stream.write(temporalDelimiter);\r\n \r\n for (const obu of obus) {\r\n\r\n // Take a look at the OBU and see what it contains to verify it looks correct\r\n // OBU Header Byte\r\n // --------------------------------------------------------------------------------\r\n // | 7 | 6,5,4,3 | 2 | 1 | 0 |\r\n // |1 bit forbidden|4 bit OBU Type|1 bit hasExtension|1 bit hasSize|1 bit reserved|\r\n // --------------------------------------------------------------------------------\r\n if (obu.length > 0) {\r\n const obuHeader = obu[0];\r\n //const forbidden_bit = (obuHeader >> 7) & 0x01;\r\n const obu_type = (obuHeader >> 3) & 0x0F;\r\n const extension_bit = (obuHeader >> 2) & 0x01;\r\n const size_bit = (obuHeader >> 1) & 0x01;\r\n\r\n /*\r\n const obu_name = this.GetOBUName(obu_type);\r\n console.log(\"Found AV1 OBU:\" + obu_name);\r\n\r\n if (forbidden_bit == 1) {\r\n console.log(\"OBU Forbidden Bit Error\");\r\n }\r\n \r\n if (obu_name == \"Reserved\") {\r\n console.log(\"OBU Type Error\");\r\n }\r\n */\r\n\r\n if (this.waitingForSequenceHeader) {\r\n if (obu_type == 1) {\r\n this.waitingForSequenceHeader = false;\r\n\r\n // Write the First TD\r\n\r\n\r\n } else {\r\n // we are still waiting so drop this OBU\r\n console.log(\"AV1 file writing: Dropping OBU while waiting for Sequence Header\")\r\n continue;\r\n }\r\n }\r\n \r\n\r\n // In order to write to a .obu file, we have to ensure there is a LEB128 Size after the OBU Header Byte (and Optional Extension Byte)\r\n // The LEB128 length gets stripped out in RTP packets\r\n if (size_bit == 0) {\r\n let size = 0;\r\n if (extension_bit == 0) size = obu.length - 1; // -1 for the OBU header\r\n if (extension_bit == 1) size = obu.length - 2; // -2 for the OBU header and the Header Extension Byte\r\n \r\n // Convert the Size into a LEB128 byte sequence\r\n const leb128_bytes = [];\r\n while (size > 0) {\r\n const lower_7_bits = (size & 0x7F);\r\n if (size <= 127) {\r\n leb128_bytes.push(lower_7_bits); // leave msbit as 0\r\n } else {\r\n leb128_bytes.push(0x80 + lower_7_bits); // set msbit to 1\r\n }\r\n size = (size >> 7);\r\n }\r\n const leb128Buffer = Buffer.from(leb128_bytes);\r\n\r\n // Insert the leb128 size into the OBU\r\n const header_and_extention_len = (extension_bit == 0 ? 1 : 2); // length of header PLUS extension\r\n // Insert the LEB128 length\r\n const newObu = Buffer.concat([\r\n obu.slice(0, 0 + header_and_extention_len),\r\n leb128Buffer,\r\n obu.slice(header_and_extention_len,obu.length)\r\n ]);\r\n // Set the hasSize flag to '1' in the OBU Header\r\n newObu[0] = newObu[0] | 0x02;\r\n\r\n // WRITE DATA\r\n this.stream.write(newObu);\r\n }\r\n else\r\n {\r\n // This OBU came with a LEB128 length. The AV1 RTSP Spec says the RTSP server should strip them out, but this\r\n // handles the case where a RTSP Server leaves them in\r\n // WRITE DATA\r\n this.stream.write(obu);\r\n }\r\n }\r\n } // for-each OBU in OBUs Arrau\r\n }\r\n }\r\n\r\n\r\n GetOBUName(obu_type: number): string {\r\n switch (obu_type)\r\n {\r\n case 0: return \"Reserved\";\r\n case 1: return \"SEQUENCE_HEADER\";\r\n case 2: return \"TEMPORAL_DELIMITER\";\r\n case 3: return \"FRAME_HEADER\";\r\n case 4: return \"TILE_GROUP\";\r\n case 5: return \"METADATA\";\r\n case 6: return \"FRAME\";\r\n case 7: return \"REDUNDANT_FRAME_HEADER\";\r\n case 8: return \"TILE_LIST\";\r\n case 9: return \"Reserved\";\r\n case 10: return \"Reserved\";\r\n case 11: return \"Reserved\";\r\n case 12: return \"Reserved\";\r\n case 13: return \"Reserved\";\r\n case 14: return \"Reserved\";\r\n case 15: return \"PADDING\";\r\n }\r\n\r\n return \"Error getting OBU Type\";\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/H264Transport.js.map b/dist/transports/H264Transport.js.map index ea7619f..066d89c 100644 --- a/dist/transports/H264Transport.js.map +++ b/dist/transports/H264Transport.js.map @@ -1 +1 @@ -{"version":3,"file":"H264Transport.js","sourceRoot":"","sources":["../../lib/transports/H264Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Process SDP and RTP packets\n// De-packetize RTP packets to re-create H264 NAL Units\n// Write H264 NAL units to a .264 file\n\nimport RTSPClient from \"../RTSPClient\";\nimport {RTPPacket} from \"../util\";\n\nimport * as transform from \"sdp-transform\";\nimport { Writable } from \"stream\";\n\n// .h264 file header\nconst H264_HEADER = Buffer.from([0x00,0x00,0x00,0x01]);\n\ninterface Details {\n codec: string\n mediaSource: transform.MediaDescription\n rtpChannel: number,\n rtcpChannel: number\n}\n\nexport default class H264Transport {\n client: RTSPClient;\n stream: Writable;\n\n rtpPackets: Buffer[] = [];\n\n _headerWritten = false;\n\n constructor(client: RTSPClient, stream: Writable, details: Details) {\n this.client = client;\n this.stream = stream;\n\n client.on(\"data\", (channel, data, packet) => {\n if (channel == details.rtpChannel) {\n if (this._headerWritten) {\n this.processRTPPacket(packet);\n }\n }\n });\n\n this.processConnectionDetails(details);\n }\n\n processConnectionDetails(details: Details): void {\n // Extract SPS and PPS from the MediaSource part of the SDP\n const fmtp = (details.mediaSource.fmtp)[0];\n \n if (!fmtp) {\n return;\n }\n \n const fmtpConfig = transform.parseParams(fmtp.config);\n const splitSpropParameterSets = fmtpConfig['sprop-parameter-sets'].toString().split(',');\n const sps_base64 = splitSpropParameterSets[0];\n const pps_base64 = splitSpropParameterSets[1];\n const sps = Buffer.from(sps_base64, \"base64\");\n const pps = Buffer.from(pps_base64, \"base64\");\n\n this.stream.write(H264_HEADER);\n this.stream.write(sps);\n this.stream.write(H264_HEADER);\n this.stream.write(pps);\n\n this._headerWritten = true;\n }\n\n processRTPPacket(packet: RTPPacket): void {\n // Accumatate RTP packets\n this.rtpPackets.push(packet.payload);\n \n // When Marker is set to 1 pass the group of packets to processRTPFrame()\n if (packet.marker == 1) {\n this.processRTPFrame(this.rtpPackets);\n this.rtpPackets = [];\n }\n }\n\n processRTPFrame(rtpPackets: Buffer[]): void {\n const nals = [];\n let partialNal = [];\n\n for (let i = 0; i < rtpPackets.length; i++) {\n const packet = rtpPackets[i];\n const nal_header_f_bit = (packet[0] >> 7) & 0x01;\n const nal_header_nri = (packet[0] >> 5) & 0x03;\n const nal_header_type = (packet[0] >> 0) & 0x1F;\n\n if (nal_header_type >= 1 && nal_header_type <= 23) { // Normal NAL. Not fragmented\n nals.push(packet);\n } else if (nal_header_type == 24) { // Aggregation type STAP-A. Multiple NAls in one RTP Packet\n let ptr = 1; // start after the nal_header_type which was '24'\n // if we have at least 2 more bytes (the 16 bit size) then consume more data\n while (ptr + 2 < (packet.length - 1)) {\n const size = (packet[ptr] << 8) + (packet[ptr + 1] << 0);\n ptr = ptr + 2;\n nals.push(packet.slice(ptr,ptr+size));\n ptr = ptr + size;\n }\n } else if (nal_header_type == 25) { // STAP-B\n // Not supported\n } else if (nal_header_type == 26) { // MTAP-16\n // Not supported\n } else if (nal_header_type == 27) { // MTAP-24\n // Not supported\n } else if (nal_header_type == 28) { // Frag FU-A\n // NAL is split over several RTP packets\n // Accumulate them in a tempoary buffer\n // Parse Fragmentation Unit Header\n const fu_header_s = (packet[1] >> 7) & 0x01; // start marker\n const fu_header_e = (packet[1] >> 6) & 0x01; // end marker\n const fu_header_r = (packet[1] >> 5) & 0x01; // reserved. should be 0\n const fu_header_type = (packet[1] >> 0) & 0x1F; // Original NAL unit header\n\n // Check Start and End flags\n if (fu_header_s == 1 && fu_header_e == 0) { // Start of Fragment}\n const reconstructed_nal_type = (nal_header_f_bit << 7)\n + (nal_header_nri << 5)\n + fu_header_type;\n partialNal = [];\n partialNal.push(reconstructed_nal_type);\n\n // copy the rest of the RTP payload to the temp buffer\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\n }\n\n if (fu_header_s == 0 && fu_header_e == 0) { // Middle part of fragment}\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\n }\n\n if (fu_header_s == 0 && fu_header_e == 1) { // End of fragment}\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\n nals.push(Buffer.from(partialNal));\n }\n } else if (nal_header_type == 29) { // Frag FU-B\n // Not supported\n }\n }\n\n // Write out all the NALs\n for (let x = 0; x < nals.length; x++) {\n this.stream.write(H264_HEADER);\n this.stream.write(nals[x]);\n }\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"H264Transport.js","sourceRoot":"","sources":["../../lib/transports/H264Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Process SDP and RTP packets\r\n// De-packetize RTP packets to re-create H264 NAL Units\r\n// Write H264 NAL units to a .264 file\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport {RTPPacket} from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\n\r\n// .h264 file header\r\nconst H264_HEADER = Buffer.from([0x00,0x00,0x00,0x01]);\r\n\r\ninterface Details {\r\n codec: string\r\n mediaSource: transform.MediaDescription\r\n rtpChannel: number,\r\n rtcpChannel: number\r\n}\r\n\r\nexport default class H264Transport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n\r\n rtpPackets: Buffer[] = [];\r\n\r\n _headerWritten = false;\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n if (this._headerWritten) {\r\n this.processRTPPacket(packet);\r\n }\r\n }\r\n });\r\n\r\n this.processConnectionDetails(details);\r\n }\r\n\r\n processConnectionDetails(details: Details): void {\r\n // Extract SPS and PPS from the MediaSource part of the SDP\r\n const fmtp = (details.mediaSource.fmtp)[0];\r\n \r\n if (!fmtp) {\r\n return;\r\n }\r\n \r\n const fmtpConfig = transform.parseParams(fmtp.config);\r\n const splitSpropParameterSets = fmtpConfig['sprop-parameter-sets'].toString().split(',');\r\n const sps_base64 = splitSpropParameterSets[0];\r\n const pps_base64 = splitSpropParameterSets[1];\r\n const sps = Buffer.from(sps_base64, \"base64\");\r\n const pps = Buffer.from(pps_base64, \"base64\");\r\n\r\n this.stream.write(H264_HEADER);\r\n this.stream.write(sps);\r\n this.stream.write(H264_HEADER);\r\n this.stream.write(pps);\r\n\r\n this._headerWritten = true;\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // Accumatate RTP packets\r\n this.rtpPackets.push(packet.payload);\r\n \r\n // When Marker is set to 1 pass the group of packets to processRTPFrame()\r\n if (packet.marker == 1) {\r\n this.processRTPFrame(this.rtpPackets);\r\n this.rtpPackets = [];\r\n }\r\n }\r\n\r\n processRTPFrame(rtpPackets: Buffer[]): void {\r\n const nals = [];\r\n let partialNal = [];\r\n\r\n for (let i = 0; i < rtpPackets.length; i++) {\r\n const packet = rtpPackets[i];\r\n const nal_header_f_bit = (packet[0] >> 7) & 0x01;\r\n const nal_header_nri = (packet[0] >> 5) & 0x03;\r\n const nal_header_type = (packet[0] >> 0) & 0x1F;\r\n\r\n if (nal_header_type >= 1 && nal_header_type <= 23) { // Normal NAL. Not fragmented\r\n nals.push(packet);\r\n } else if (nal_header_type == 24) { // Aggregation type STAP-A. Multiple NAls in one RTP Packet\r\n let ptr = 1; // start after the nal_header_type which was '24'\r\n // if we have at least 2 more bytes (the 16 bit size) then consume more data\r\n while (ptr + 2 < (packet.length - 1)) {\r\n const size = (packet[ptr] << 8) + (packet[ptr + 1] << 0);\r\n ptr = ptr + 2;\r\n nals.push(packet.slice(ptr,ptr+size));\r\n ptr = ptr + size;\r\n }\r\n } else if (nal_header_type == 25) { // STAP-B\r\n // Not supported\r\n } else if (nal_header_type == 26) { // MTAP-16\r\n // Not supported\r\n } else if (nal_header_type == 27) { // MTAP-24\r\n // Not supported\r\n } else if (nal_header_type == 28) { // Frag FU-A\r\n // NAL is split over several RTP packets\r\n // Accumulate them in a tempoary buffer\r\n // Parse Fragmentation Unit Header\r\n const fu_header_s = (packet[1] >> 7) & 0x01; // start marker\r\n const fu_header_e = (packet[1] >> 6) & 0x01; // end marker\r\n const fu_header_r = (packet[1] >> 5) & 0x01; // reserved. should be 0\r\n const fu_header_type = (packet[1] >> 0) & 0x1F; // Original NAL unit header\r\n\r\n // Check Start and End flags\r\n if (fu_header_s == 1 && fu_header_e == 0) { // Start of Fragment}\r\n const reconstructed_nal_type = (nal_header_f_bit << 7)\r\n + (nal_header_nri << 5)\r\n + fu_header_type;\r\n partialNal = [];\r\n partialNal.push(reconstructed_nal_type);\r\n\r\n // copy the rest of the RTP payload to the temp buffer\r\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\r\n }\r\n\r\n if (fu_header_s == 0 && fu_header_e == 0) { // Middle part of fragment}\r\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\r\n }\r\n\r\n if (fu_header_s == 0 && fu_header_e == 1) { // End of fragment}\r\n for (let x=2; x< packet.length;x++) partialNal.push(packet[x]);\r\n nals.push(Buffer.from(partialNal));\r\n }\r\n } else if (nal_header_type == 29) { // Frag FU-B\r\n // Not supported\r\n }\r\n }\r\n\r\n // Write out all the NALs\r\n for (let x = 0; x < nals.length; x++) {\r\n this.stream.write(H264_HEADER);\r\n this.stream.write(nals[x]);\r\n }\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/H265Transport.js.map b/dist/transports/H265Transport.js.map index 3620cc1..e7a6bad 100644 --- a/dist/transports/H265Transport.js.map +++ b/dist/transports/H265Transport.js.map @@ -1 +1 @@ -{"version":3,"file":"H265Transport.js","sourceRoot":"","sources":["../../lib/transports/H265Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Process SDP and RTP packets\n// De-packetize RTP packets to re-create H265 NAL Units\n// Write H265 NAL units to a .265 file\n\nimport RTSPClient from \"../RTSPClient\";\nimport { RTPPacket } from \"../util\";\n\nimport * as transform from \"sdp-transform\";\nimport { Writable } from \"stream\";\n\n// .h265 file header\nconst H265_HEADER = Buffer.from([0x00, 0x00, 0x00, 0x01]);\n\ninterface Details {\n codec: string\n mediaSource: transform.MediaDescription\n rtpChannel: number,\n rtcpChannel: number\n}\n\nexport default class H265Transport {\n client: RTSPClient;\n stream: Writable;\n has_donl = false;\n\n rtpPackets: Buffer[] = [];\n\n constructor(client: RTSPClient, stream: Writable, details: Details) {\n this.client = client;\n this.stream = stream;\n\n // process 'fmtp' (which is optional in the SDP)\n this.processConnectionDetails(details);\n\n client.on(\"data\", (channel, data, packet) => {\n if (channel == details.rtpChannel) {\n this.processRTPPacket(packet);\n }\n });\n\n }\n\n processConnectionDetails(details: Details): void {\n // Extract the VPS, SPS and PPS from the MediaSource part of the SDP.\n // NOTE the H265 RTP standard makes this optional and we may need to extract this from the RTP payload\n // as inband VPS/SPS/PPS instead\n const fmtp = (details.mediaSource.fmtp)[0];\n\n if (!fmtp) {\n return;\n }\n\n const fmtpConfig = transform.parseParams(fmtp.config);\n const vps = Buffer.from(fmtpConfig['sprop-vps'].toString(), \"base64\");\n const sps = Buffer.from(fmtpConfig['sprop-sps'].toString(), \"base64\");\n const pps = Buffer.from(fmtpConfig['sprop-pps'].toString(), \"base64\");\n\n this.stream.write(H265_HEADER);\n this.stream.write(vps);\n this.stream.write(H265_HEADER);\n this.stream.write(sps);\n this.stream.write(H265_HEADER);\n this.stream.write(pps);\n }\n\n processRTPPacket(packet: RTPPacket): void {\n // Accumatate RTP packets\n this.rtpPackets.push(packet.payload);\n\n // When Marker is set to 1 pass the group of packets to processRTPFrame()\n if (packet.marker == 1) {\n this.processRTPFrame(this.rtpPackets);\n this.rtpPackets = [];\n }\n }\n\n processRTPFrame(rtpPackets: Buffer[]): void {\n const nals = [];\n let partialNal = [];\n\n for (let i = 0; i < rtpPackets.length; i++) {\n\n // Examine the first two bytes of the RTP data, the Payload Header\n // F (Forbidden Bit),\n // Type of NAL Unit (or VCL NAL Unit if Type is < 32),\n // LayerId\n // TID (TemporalID = TID - 1)\n /*+---------------+---------------+\n *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|\n *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n *|F| Type | LayerId | TID |\n *+-------------+-----------------+\n */\n const packet = rtpPackets[i];\n\n const payload_header = (packet[0] << 8) | (packet[1]);\n const payload_header_f_bit = (payload_header >> 15) & 0x01;\n const payload_header_type = (payload_header >> 9) & 0x3F;\n const payload_header_layer_id = (payload_header >> 3) & 0x3F;\n const payload_header_tid = payload_header & 0x7;\n\n // There are three ways to Packetize NAL units into RTP Packets\n // Single NAL Unit Packet\n // Aggregation Packet (payload_header_type = 48)\n // Fragmentation Unit (payload_header_type = 49)\n\n\n // Single NAL Unit Packet\n // 32=VPS\n // 33=SPS\n // 34=PPS\n if (payload_header_type != 48 && payload_header_type != 49) {\n //TODO - Handle DONL\n nals.push(packet);\n }\n\n // Aggregation Packet\n else if (payload_header_type == 48) {\n // TODO\n console.log(\"eek\");\n }\n\n\n // Fragmentation Unit\n else if (payload_header_type == 49) {\n //Console.WriteLine(\"Fragmentation Unit\");\n\n // 0 1 2 3 4 5 6 7\n // +-+-+-+-+-+-+-+-+\n // |S|E| FuType |\n // +---------------+\n //\n\n // Parse Fragmentation Unit Header\n const fu_header_s = (packet[2] >> 7) & 0x01; // start marker\n const fu_header_e = (packet[2] >> 6) & 0x01; // end marker\n const fu_header_type = (packet[2] >> 0) & 0x3F; // fu type\n\n // Console.WriteLine(\"Frag FU-A s=\" + fu_header_s + \"e=\" + fu_header_e);\n\n // Check Start and End flags\n if (fu_header_s == 1 && fu_header_e == 0) {\n // Start Fragment.\n // Initiise the partialNal byte array\n\n // Empty the partial NAL array\n partialNal = [];\n\n // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type\n let nal_header = (payload_header & 0x81FF); // strip out existing 'type'\n nal_header = nal_header | (fu_header_type << 9);\n\n partialNal.push((nal_header >> 8) & 0xFF);\n partialNal.push((nal_header >> 0) & 0xFF);\n }\n\n\n // Copy the video data\n if (this.has_donl) {\n // start copying after the DONL data\n for (let x = 5; x < packet.length; x++) {\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\n }\n }\n else {\n // there is no DONL data\n for (let x = 3; x < packet.length; x++) {\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\n }\n }\n\n if (fu_header_s == 0 && fu_header_e == 1) {\n // End Fragment\n // Append this payload to the fragmented_nal\n\n // Add the NAL to the array of NAL units\n nals.push(Buffer.from(partialNal));\n }\n }\n else {\n //Console.WriteLine(\"Unknown Payload Header Type = \" + payload_header_type);\n }\n }\n\n // Write out all the NALs\n for (let x = 0; x < nals.length; x++) {\n this.stream.write(H265_HEADER);\n this.stream.write(nals[x]);\n }\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"H265Transport.js","sourceRoot":"","sources":["../../lib/transports/H265Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Process SDP and RTP packets\r\n// De-packetize RTP packets to re-create H265 NAL Units\r\n// Write H265 NAL units to a .265 file\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport { RTPPacket } from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\n\r\n// .h265 file header\r\nconst H265_HEADER = Buffer.from([0x00, 0x00, 0x00, 0x01]);\r\n\r\ninterface Details {\r\n codec: string\r\n mediaSource: transform.MediaDescription\r\n rtpChannel: number,\r\n rtcpChannel: number\r\n}\r\n\r\nexport default class H265Transport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n has_donl = false;\r\n\r\n rtpPackets: Buffer[] = [];\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n\r\n // process 'fmtp' (which is optional in the SDP)\r\n this.processConnectionDetails(details);\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n this.processRTPPacket(packet);\r\n }\r\n });\r\n\r\n }\r\n\r\n processConnectionDetails(details: Details): void {\r\n // Extract the VPS, SPS and PPS from the MediaSource part of the SDP.\r\n // NOTE the H265 RTP standard makes this optional and we may need to extract this from the RTP payload\r\n // as inband VPS/SPS/PPS instead\r\n const fmtp = (details.mediaSource.fmtp)[0];\r\n\r\n if (!fmtp) {\r\n return;\r\n }\r\n\r\n const fmtpConfig = transform.parseParams(fmtp.config);\r\n const vps = Buffer.from(fmtpConfig['sprop-vps'].toString(), \"base64\");\r\n const sps = Buffer.from(fmtpConfig['sprop-sps'].toString(), \"base64\");\r\n const pps = Buffer.from(fmtpConfig['sprop-pps'].toString(), \"base64\");\r\n\r\n this.stream.write(H265_HEADER);\r\n this.stream.write(vps);\r\n this.stream.write(H265_HEADER);\r\n this.stream.write(sps);\r\n this.stream.write(H265_HEADER);\r\n this.stream.write(pps);\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // Accumatate RTP packets\r\n this.rtpPackets.push(packet.payload);\r\n\r\n // When Marker is set to 1 pass the group of packets to processRTPFrame()\r\n if (packet.marker == 1) {\r\n this.processRTPFrame(this.rtpPackets);\r\n this.rtpPackets = [];\r\n }\r\n }\r\n\r\n processRTPFrame(rtpPackets: Buffer[]): void {\r\n const nals = [];\r\n let partialNal = [];\r\n\r\n for (let i = 0; i < rtpPackets.length; i++) {\r\n\r\n // Examine the first two bytes of the RTP data, the Payload Header\r\n // F (Forbidden Bit),\r\n // Type of NAL Unit (or VCL NAL Unit if Type is < 32),\r\n // LayerId\r\n // TID (TemporalID = TID - 1)\r\n /*+---------------+---------------+\r\n *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|\r\n *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\r\n *|F| Type | LayerId | TID |\r\n *+-------------+-----------------+\r\n */\r\n const packet = rtpPackets[i];\r\n\r\n const payload_header = (packet[0] << 8) | (packet[1]);\r\n const payload_header_f_bit = (payload_header >> 15) & 0x01;\r\n const payload_header_type = (payload_header >> 9) & 0x3F;\r\n const payload_header_layer_id = (payload_header >> 3) & 0x3F;\r\n const payload_header_tid = payload_header & 0x7;\r\n\r\n // There are three ways to Packetize NAL units into RTP Packets\r\n // Single NAL Unit Packet\r\n // Aggregation Packet (payload_header_type = 48)\r\n // Fragmentation Unit (payload_header_type = 49)\r\n\r\n\r\n // Single NAL Unit Packet\r\n // 32=VPS\r\n // 33=SPS\r\n // 34=PPS\r\n if (payload_header_type != 48 && payload_header_type != 49) {\r\n //TODO - Handle DONL\r\n nals.push(packet);\r\n }\r\n\r\n // Aggregation Packet\r\n else if (payload_header_type == 48) {\r\n // TODO\r\n console.log(\"eek\");\r\n }\r\n\r\n\r\n // Fragmentation Unit\r\n else if (payload_header_type == 49) {\r\n //Console.WriteLine(\"Fragmentation Unit\");\r\n\r\n // 0 1 2 3 4 5 6 7\r\n // +-+-+-+-+-+-+-+-+\r\n // |S|E| FuType |\r\n // +---------------+\r\n //\r\n\r\n // Parse Fragmentation Unit Header\r\n const fu_header_s = (packet[2] >> 7) & 0x01; // start marker\r\n const fu_header_e = (packet[2] >> 6) & 0x01; // end marker\r\n const fu_header_type = (packet[2] >> 0) & 0x3F; // fu type\r\n\r\n // Console.WriteLine(\"Frag FU-A s=\" + fu_header_s + \"e=\" + fu_header_e);\r\n\r\n // Check Start and End flags\r\n if (fu_header_s == 1 && fu_header_e == 0) {\r\n // Start Fragment.\r\n // Initiise the partialNal byte array\r\n\r\n // Empty the partial NAL array\r\n partialNal = [];\r\n\r\n // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type\r\n let nal_header = (payload_header & 0x81FF); // strip out existing 'type'\r\n nal_header = nal_header | (fu_header_type << 9);\r\n\r\n partialNal.push((nal_header >> 8) & 0xFF);\r\n partialNal.push((nal_header >> 0) & 0xFF);\r\n }\r\n\r\n\r\n // Copy the video data\r\n if (this.has_donl) {\r\n // start copying after the DONL data\r\n for (let x = 5; x < packet.length; x++) {\r\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\r\n }\r\n }\r\n else {\r\n // there is no DONL data\r\n for (let x = 3; x < packet.length; x++) {\r\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\r\n }\r\n }\r\n\r\n if (fu_header_s == 0 && fu_header_e == 1) {\r\n // End Fragment\r\n // Append this payload to the fragmented_nal\r\n\r\n // Add the NAL to the array of NAL units\r\n nals.push(Buffer.from(partialNal));\r\n }\r\n }\r\n else {\r\n //Console.WriteLine(\"Unknown Payload Header Type = \" + payload_header_type);\r\n }\r\n }\r\n\r\n // Write out all the NALs\r\n for (let x = 0; x < nals.length; x++) {\r\n this.stream.write(H265_HEADER);\r\n this.stream.write(nals[x]);\r\n }\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/H266Transport.d.ts b/dist/transports/H266Transport.d.ts new file mode 100644 index 0000000..470bcd2 --- /dev/null +++ b/dist/transports/H266Transport.d.ts @@ -0,0 +1,23 @@ +/// +/// +import RTSPClient from "../RTSPClient"; +import { RTPPacket } from "../util"; +import * as transform from "sdp-transform"; +import { Writable } from "stream"; +interface Details { + codec: string; + mediaSource: transform.MediaDescription; + rtpChannel: number; + rtcpChannel: number; +} +export default class H266Transport { + client: RTSPClient; + stream: Writable; + has_donl: boolean; + rtpPackets: Buffer[]; + constructor(client: RTSPClient, stream: Writable, details: Details); + processConnectionDetails(details: Details): void; + processRTPPacket(packet: RTPPacket): void; + processRTPFrame(rtpPackets: Buffer[]): void; +} +export {}; diff --git a/dist/transports/H266Transport.js b/dist/transports/H266Transport.js new file mode 100644 index 0000000..db67835 --- /dev/null +++ b/dist/transports/H266Transport.js @@ -0,0 +1,164 @@ +"use strict"; +// Process SDP and RTP packets +// De-packetize RTP packets to re-create H266 NAL Units +// Write H266 NAL units to a .266 file +Object.defineProperty(exports, "__esModule", { value: true }); +const transform = require("sdp-transform"); +// .h266 file header (Annex B) +const H266_HEADER = Buffer.from([0x00, 0x00, 0x00, 0x01]); +class H266Transport { + constructor(client, stream, details) { + this.has_donl = false; // We have to check the SDP to see if DONL bytes are being set in the RTP packet + this.rtpPackets = []; + this.client = client; + this.stream = stream; + // process 'fmtp' (which is optional in the SDP) + this.processConnectionDetails(details); + client.on("data", (channel, data, packet) => { + if (channel == details.rtpChannel) { + this.processRTPPacket(packet); + } + }); + } + processConnectionDetails(details) { + var _a, _b, _c, _d; + // Extract the DCI, VPS, SPS and PPS from the MediaSource part of the SDP. + // NOTE the H266 RTP standard makes this optional and we may need to extract this from the RTP payload + // as inband DCI/VPS/SPS/PPS instead + const fmtp = (details.mediaSource.fmtp)[0]; + if (!fmtp) { + return; + } + const fmtpConfig = transform.parseParams(fmtp.config); + const dci = ('sprop-dci' in fmtpConfig) ? Buffer.from((_a = fmtpConfig['sprop-dci']) === null || _a === void 0 ? void 0 : _a.toString(), "base64") : null; + const vps = ('sprop-vps' in fmtpConfig) ? Buffer.from((_b = fmtpConfig['sprop-vps']) === null || _b === void 0 ? void 0 : _b.toString(), "base64") : null; + const sps = ('sprop-sps' in fmtpConfig) ? Buffer.from((_c = fmtpConfig['sprop-sps']) === null || _c === void 0 ? void 0 : _c.toString(), "base64") : null; + const pps = ('sprop-pps' in fmtpConfig) ? Buffer.from((_d = fmtpConfig['sprop-pps']) === null || _d === void 0 ? void 0 : _d.toString(), "base64") : null; + if (dci != null) { + this.stream.write(H266_HEADER); + this.stream.write(dci); + } + if (vps != null) { + this.stream.write(H266_HEADER); + this.stream.write(vps); + } + if (sps != null) { + this.stream.write(H266_HEADER); + this.stream.write(sps); + } + if (pps != null) { + this.stream.write(H266_HEADER); + this.stream.write(pps); + } + } + processRTPPacket(packet) { + // Accumatate RTP packets + this.rtpPackets.push(packet.payload); + // When Marker is set to 1 pass the group of packets to processRTPFrame() + if (packet.marker == 1) { + this.processRTPFrame(this.rtpPackets); + this.rtpPackets = []; + // Write out the AUD + this.stream.write(H266_HEADER); + this.stream.write(Buffer.from([0x00, 0xA1, 0x88])); + } + } + processRTPFrame(rtpPackets) { + const nals = []; + let partialNal = []; + for (let i = 0; i < rtpPackets.length; i++) { + // Examine the first two bytes of the RTP data, the Payload Header + // F (Forbidden Bit), must be 0 + // Z (Zero Bit), must be 0 + // LayerId (6 bits) + // Type of NAL Unit (5 bits) + // TID (TemporalID = TID - 1) + /*+---------------+---------------+ + *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + *|F|Z| LayerID | Type | TID | + *+-------------+-----------------+ + */ + const packet = rtpPackets[i]; + const payload_header = (packet[0] << 8) | (packet[1]); + const payload_header_f_bit = (payload_header >> 15) & 0x01; + const payload_header_z_bit = (payload_header >> 14) & 0x01; + const payload_header_layer_id = (payload_header >> 8) & 0x3F; + const payload_header_type = (payload_header >> 3) & 0x1F; + const payload_header_tid = payload_header & 0x7; + // There are three ways to Packetize NAL units into RTP Packets + // Single NAL Unit Packet + // Aggregation Packet + // Fragmentation Unit + // Note H266/VVC has a concept of a GDR - Gradual Decoder Refresh + // and of the IDR - Instantaneous Decoding Refresh + // Single NAL Unit Packet + if (payload_header_type != 28 && payload_header_type != 29) { + //TODO - Handle DONL + nals.push(packet); + } + // Aggregation Packet + else if (payload_header_type == 28) { + // TODO. This is an Agregation Packet so we need to extract + // the 2 or more NALs from the packet. + console.log("eek - we have not implemented Agregation Packets yet"); + } + // Fragmentation Unit + else if (payload_header_type == 29) { + //Console.WriteLine("Fragmentation Unit"); + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // |S|E|P| FuType | + // +---------------+ + // + // Parse Fragmentation Unit Header + const fu_header_s = (packet[2] >> 7) & 0x01; // start marker + const fu_header_e = (packet[2] >> 6) & 0x01; // end marker + const fu_header_p = (packet[2] >> 5) & 0x01; // P = last FU of a Coded Picture + const fu_header_type = (packet[2] >> 0) & 0x1F; // fu type (5 bits) + // Console.WriteLine("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e); + // Check Start and End flags + if (fu_header_s == 1 && fu_header_e == 0) { + // Start Fragment. + // Initiise the partialNal byte array + // Empty the partial NAL array + partialNal = []; + // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type + let nal_header = (payload_header & 0xFF07); // strip out existing 'type' which is the "FU Type" + nal_header = nal_header | (fu_header_type << 3); // and replace it with the fu_header_type + partialNal.push((nal_header >> 8) & 0xFF); + partialNal.push((nal_header >> 0) & 0xFF); + } + // Copy the video data + if (this.has_donl) { + // start copying after the DONL data + for (let x = 5; x < packet.length; x++) { + partialNal.push(packet[x]); // not very efficient, copying one byte at a time + } + } + else { + // there is no DONL data + for (let x = 3; x < packet.length; x++) { + partialNal.push(packet[x]); // not very efficient, copying one byte at a time + } + } + if (fu_header_s == 0 && fu_header_e == 1) { + // End Fragment + // Append this payload to the fragmented_nal + // Add the NAL to the array of NAL units + nals.push(Buffer.from(partialNal)); + } + } + else { + //Console.WriteLine("Unknown Payload Header Type = " + payload_header_type); + } + } + // Write out all the NALs + for (let x = 0; x < nals.length; x++) { + this.stream.write(H266_HEADER); + this.stream.write(nals[x]); + } + } +} +exports.default = H266Transport; +//# sourceMappingURL=H266Transport.js.map \ No newline at end of file diff --git a/dist/transports/H266Transport.js.map b/dist/transports/H266Transport.js.map new file mode 100644 index 0000000..de00bf6 --- /dev/null +++ b/dist/transports/H266Transport.js.map @@ -0,0 +1 @@ +{"version":3,"file":"H266Transport.js","sourceRoot":"","sources":["../../lib/transports/H266Transport.ts"],"names":[],"mappings":"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","sourcesContent":["// Process SDP and RTP packets\r\n// De-packetize RTP packets to re-create H266 NAL Units\r\n// Write H266 NAL units to a .266 file\r\n\r\n// (c) 2026 Roger Hardiman\r\n// Based on my Yellowstone H265 Parser which in turn is based on my SharpRTSP H265 RTP Parser I wrote in September 2018\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport { RTPPacket } from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\n\r\n// .h266 file header (Annex B)\r\nconst H266_HEADER = Buffer.from([0x00, 0x00, 0x00, 0x01]);\r\n\r\ninterface Details {\r\n codec: string\r\n mediaSource: transform.MediaDescription\r\n rtpChannel: number,\r\n rtcpChannel: number\r\n}\r\n\r\nexport default class H266Transport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n has_donl = false; // We have to check the SDP to see if DONL bytes are being set in the RTP packet\r\n\r\n rtpPackets: Buffer[] = [];\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n\r\n // process 'fmtp' (which is optional in the SDP)\r\n this.processConnectionDetails(details);\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n this.processRTPPacket(packet);\r\n }\r\n });\r\n\r\n }\r\n\r\n processConnectionDetails(details: Details): void {\r\n // Extract the DCI, VPS, SPS and PPS from the MediaSource part of the SDP.\r\n // NOTE the H266 RTP standard makes this optional and we may need to extract this from the RTP payload\r\n // as inband DCI/VPS/SPS/PPS instead\r\n const fmtp = (details.mediaSource.fmtp)[0];\r\n\r\n if (!fmtp) {\r\n return;\r\n }\r\n\r\n const fmtpConfig = transform.parseParams(fmtp.config);\r\n const dci = ('sprop-dci' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-dci']?.toString(), \"base64\") : null;\r\n const vps = ('sprop-vps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-vps']?.toString(), \"base64\") : null; \r\n const sps = ('sprop-sps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-sps']?.toString(), \"base64\") : null;\r\n const pps = ('sprop-pps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-pps']?.toString(), \"base64\") : null\r\n\r\n if (dci != null) {\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(dci);\r\n }\r\n if (vps != null) {\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(vps);\r\n }\r\n if (sps != null) {\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(sps);\r\n }\r\n if (pps != null) {\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(pps);\r\n }\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // Accumatate RTP packets\r\n this.rtpPackets.push(packet.payload);\r\n\r\n // When Marker is set to 1 pass the group of packets to processRTPFrame()\r\n if (packet.marker == 1) {\r\n this.processRTPFrame(this.rtpPackets);\r\n this.rtpPackets = [];\r\n\r\n // Write out the AUD\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(Buffer.from([0x00, 0xA1, 0x88]));\r\n\r\n }\r\n }\r\n\r\n processRTPFrame(rtpPackets: Buffer[]): void {\r\n const nals = [];\r\n let partialNal = [];\r\n\r\n for (let i = 0; i < rtpPackets.length; i++) {\r\n\r\n // Examine the first two bytes of the RTP data, the Payload Header\r\n // F (Forbidden Bit), must be 0\r\n // Z (Zero Bit), must be 0\r\n // LayerId (6 bits)\r\n // Type of NAL Unit (5 bits)\r\n // TID (TemporalID = TID - 1)\r\n /*+---------------+---------------+\r\n *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|\r\n *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\r\n *|F|Z| LayerID | Type | TID |\r\n *+-------------+-----------------+\r\n */\r\n const packet = rtpPackets[i];\r\n\r\n const payload_header = (packet[0] << 8) | (packet[1]);\r\n const payload_header_f_bit = (payload_header >> 15) & 0x01;\r\n const payload_header_z_bit = (payload_header >> 14) & 0x01;\r\n const payload_header_layer_id = (payload_header >> 8) & 0x3F;\r\n const payload_header_type = (payload_header >> 3) & 0x1F;\r\n const payload_header_tid = payload_header & 0x7;\r\n\r\n // There are three ways to Packetize NAL units into RTP Packets\r\n // Single NAL Unit Packet\r\n // Aggregation Packet\r\n // Fragmentation Unit\r\n\r\n\r\n // Note H266/VVC has a concept of a GDR - Gradual Decoder Refresh\r\n // and of the IDR - Instantaneous Decoding Refresh\r\n\r\n\r\n // Single NAL Unit Packet\r\n if (payload_header_type != 28 && payload_header_type != 29) {\r\n //TODO - Handle DONL\r\n nals.push(packet);\r\n }\r\n\r\n // Aggregation Packet\r\n else if (payload_header_type == 28) {\r\n // TODO. This is an Agregation Packet so we need to extract\r\n // the 2 or more NALs from the packet.\r\n console.log(\"eek - we have not implemented Agregation Packets yet\");\r\n }\r\n\r\n\r\n // Fragmentation Unit\r\n else if (payload_header_type == 29) {\r\n //Console.WriteLine(\"Fragmentation Unit\");\r\n\r\n // 0 1 2 3 4 5 6 7\r\n // +-+-+-+-+-+-+-+-+\r\n // |S|E|P| FuType |\r\n // +---------------+\r\n //\r\n\r\n // Parse Fragmentation Unit Header\r\n const fu_header_s = (packet[2] >> 7) & 0x01; // start marker\r\n const fu_header_e = (packet[2] >> 6) & 0x01; // end marker\r\n const fu_header_p = (packet[2] >> 5) & 0x01; // P = last FU of a Coded Picture\r\n const fu_header_type = (packet[2] >> 0) & 0x1F; // fu type (5 bits)\r\n\r\n // Console.WriteLine(\"Frag FU-A s=\" + fu_header_s + \"e=\" + fu_header_e);\r\n\r\n // Check Start and End flags\r\n if (fu_header_s == 1 && fu_header_e == 0) {\r\n // Start Fragment.\r\n // Initiise the partialNal byte array\r\n\r\n // Empty the partial NAL array\r\n partialNal = [];\r\n\r\n // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type\r\n let nal_header = (payload_header & 0xFF07); // strip out existing 'type' which is the \"FU Type\"\r\n nal_header = nal_header | (fu_header_type << 3); // and replace it with the fu_header_type\r\n\r\n partialNal.push((nal_header >> 8) & 0xFF);\r\n partialNal.push((nal_header >> 0) & 0xFF);\r\n }\r\n\r\n\r\n // Copy the video data\r\n if (this.has_donl) {\r\n // start copying after the DONL data\r\n for (let x = 5; x < packet.length; x++) {\r\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\r\n }\r\n }\r\n else {\r\n // there is no DONL data\r\n for (let x = 3; x < packet.length; x++) {\r\n partialNal.push(packet[x]); // not very efficient, copying one byte at a time\r\n }\r\n }\r\n\r\n if (fu_header_s == 0 && fu_header_e == 1) {\r\n // End Fragment\r\n // Append this payload to the fragmented_nal\r\n\r\n // Add the NAL to the array of NAL units\r\n nals.push(Buffer.from(partialNal));\r\n }\r\n }\r\n else {\r\n //Console.WriteLine(\"Unknown Payload Header Type = \" + payload_header_type);\r\n }\r\n }\r\n\r\n // Write out all the NALs\r\n for (let x = 0; x < nals.length; x++) {\r\n this.stream.write(H266_HEADER);\r\n this.stream.write(nals[x]);\r\n }\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/ONVIFMetadataTransport.js.map b/dist/transports/ONVIFMetadataTransport.js.map index 0aae8ed..8bcdb67 100644 --- a/dist/transports/ONVIFMetadataTransport.js.map +++ b/dist/transports/ONVIFMetadataTransport.js.map @@ -1 +1 @@ -{"version":3,"file":"ONVIFMetadataTransport.js","sourceRoot":"","sources":["../../lib/transports/ONVIFMetadataTransport.ts"],"names":[],"mappings":";AAAA,wDAAwD;AACxD,uBAAuB;AACvB,mCAAmC;;AAOnC,2BAAuB;AASvB,MAAqB,sBAAsB;IAKzC,YAAY,MAAkB,EAAE,MAAgB,EAAE,OAAgB;QAChE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QAEd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YAC1C,IAAI,OAAO,IAAI,OAAO,CAAC,UAAU,EAAE;gBACjC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;aAC/B;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,MAAiB;QAChC,iCAAiC;QAEjC,qBAAqB;QACrB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEtD,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE;YACtB,4BAA4B;YAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAG,GAAC,QAAG,CAAC,CAAC;YAC3B,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;SACf;IACH,CAAC;CACF;AA9BD,yCA8BC","sourcesContent":["// De-packetize RTP packets to re-create ONVIF Mfetadata\n// Write data to a file\n// By Roger Hardiman, December 2021\n\nimport RTSPClient from \"../RTSPClient\";\nimport { RTPPacket } from \"../util\";\n\nimport * as transform from \"sdp-transform\";\nimport { Writable } from \"stream\";\nimport {EOL} from \"os\";\n\ninterface Details {\n codec: string;\n mediaSource: transform.MediaDescription;\n rtpChannel: number;\n rtcpChannel: number;\n}\n\nexport default class ONVIFMetadataTransport {\n client: RTSPClient;\n stream: Writable;\n xml: string;\n\n constructor(client: RTSPClient, stream: Writable, details: Details) {\n this.client = client;\n this.stream = stream;\n this.xml = \"\";\n\n client.on(\"data\", (channel, data, packet) => {\n if (channel == details.rtpChannel) {\n this.processRTPPacket(packet);\n }\n });\n }\n\n processRTPPacket(packet: RTPPacket): void {\n // RTP Payload for ONVIF Metadata\n\n // Accumulate payload\n this.xml = this.xml.concat(packet.payload.toString());\n\n if (packet.marker == 1) {\n // end of xml, write to file\n this.stream.write(this.xml);\n this.stream.write(EOL+EOL);\n this.xml = \"\";\n }\n }\n}\n"]} \ No newline at end of file +{"version":3,"file":"ONVIFMetadataTransport.js","sourceRoot":"","sources":["../../lib/transports/ONVIFMetadataTransport.ts"],"names":[],"mappings":";AAAA,wDAAwD;AACxD,uBAAuB;AACvB,mCAAmC;;AAOnC,2BAAuB;AASvB,MAAqB,sBAAsB;IAKzC,YAAY,MAAkB,EAAE,MAAgB,EAAE,OAAgB;QAChE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;QAEd,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;YAC1C,IAAI,OAAO,IAAI,OAAO,CAAC,UAAU,EAAE;gBACjC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;aAC/B;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,MAAiB;QAChC,iCAAiC;QAEjC,qBAAqB;QACrB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAEtD,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE;YACtB,4BAA4B;YAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAG,GAAC,QAAG,CAAC,CAAC;YAC3B,IAAI,CAAC,GAAG,GAAG,EAAE,CAAC;SACf;IACH,CAAC;CACF;AA9BD,yCA8BC","sourcesContent":["// De-packetize RTP packets to re-create ONVIF Mfetadata\r\n// Write data to a file\r\n// By Roger Hardiman, December 2021\r\n\r\nimport RTSPClient from \"../RTSPClient\";\r\nimport { RTPPacket } from \"../util\";\r\n\r\nimport * as transform from \"sdp-transform\";\r\nimport { Writable } from \"stream\";\r\nimport {EOL} from \"os\";\r\n\r\ninterface Details {\r\n codec: string;\r\n mediaSource: transform.MediaDescription;\r\n rtpChannel: number;\r\n rtcpChannel: number;\r\n}\r\n\r\nexport default class ONVIFMetadataTransport {\r\n client: RTSPClient;\r\n stream: Writable;\r\n xml: string;\r\n\r\n constructor(client: RTSPClient, stream: Writable, details: Details) {\r\n this.client = client;\r\n this.stream = stream;\r\n this.xml = \"\";\r\n\r\n client.on(\"data\", (channel, data, packet) => {\r\n if (channel == details.rtpChannel) {\r\n this.processRTPPacket(packet);\r\n }\r\n });\r\n }\r\n\r\n processRTPPacket(packet: RTPPacket): void {\r\n // RTP Payload for ONVIF Metadata\r\n\r\n // Accumulate payload\r\n this.xml = this.xml.concat(packet.payload.toString());\r\n\r\n if (packet.marker == 1) {\r\n // end of xml, write to file\r\n this.stream.write(this.xml);\r\n this.stream.write(EOL+EOL);\r\n this.xml = \"\";\r\n }\r\n }\r\n}\r\n"]} \ No newline at end of file diff --git a/dist/transports/RTPPacket.js.map b/dist/transports/RTPPacket.js.map index 3709fe5..58d0bd9 100644 --- a/dist/transports/RTPPacket.js.map +++ b/dist/transports/RTPPacket.js.map @@ -1 +1 @@ -{"version":3,"file":"RTPPacket.js","sourceRoot":"","sources":["../../lib/transports/RTPPacket.ts"],"names":[],"mappings":"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","sourcesContent":["// Packetize audio chunk to RTP packets \n// By Thangcq98, August 2022\n\nimport { Buffer } from \"buffer\";\n\nfunction toUnsigned(val: number): number {\n return ((val >>> 1) * 2 + (val & 1));\n}\n\nclass RTPPacket {\n private _bufpkt: Buffer; // holds the RTP header (12 bytes) AND the RTP packet payload\n\n constructor(bufpayload: Buffer, hasHeader = false) {\n /* See RFC3550 for more details: http://www.ietf.org/rfc/rfc3550.txt\n V = 2, // version. always 2 for this RFC (2 bits)\n P = 0, // padding. not supported yet, so always 0 (1 bit)\n X = 0, // header extension (1 bit)\n CC = 0, // CSRC count (4 bits)\n M = 0, // marker (1 bit)\n PT = 0, // payload type. see section 6 in RFC3551 for valid types: http://www.ietf.org/rfc/rfc3551.txt (7 bits)\n SN = Math.floor(1000 * Math.random()), // sequence number. SHOULD be random (16 bits)\n TS = 1, // timestamp in the format of NTP (# sec. since 0h UTC 1 January 1900)? (32 bits)\n SSRC = 1; // synchronization source (32 bits)\n //CSRC = 0, // contributing sources. not supported yet (32 bits)\n //DP = 0, // header extension, 'Defined By Profile'. not supported yet (16 bits)\n //EL = 0; // header extension length. not supported yet (16 bits)\n */\n if (hasHeader && bufpayload.length >= 12) {\n // full packet (generally an incoming packet straight from the socket)\n this._bufpkt = bufpayload;\n /*V = (bufpkt[0] >>> 6 & 0x03);\n P = (bufpkt[0] >>> 5 & 0x01);\n X = (bufpkt[0] >>> 4 & 0x01);\n CC = (bufpkt[0] & 0x0F);\n M = (bufpkt[1] >>> 7 & 0x01);\n PT = (bufpkt[1] & 0x7F);\n SN = (bufpkt[2] << 8 | bufpkt[3]);\n TS = (bufpkt[4] << 24 | bufpkt[5] << 16 | bufpkt[6] << 8 | bufpkt[7]);\n SSRC = (bufpkt[8] << 24 | bufpkt[9] << 16 | bufpkt[10] << 8 | bufpkt[11]);*/\n // bufpkt[12..bufpkg.length-1] == payload data\n } else {\n // just payload data (for outgoing/sending)\n this._bufpkt = new Buffer(12 + bufpayload.length); // V..SSRC + payload\n /*bufpkt[0] = (V << 6 | P << 5 | X << 4 | CC);\n bufpkt[1] = (M << 7 | PT);\n bufpkt[2] = (SN >>> 8)\n bufpkt[3] = (SN & 0xFF);\n bufpkt[4] = (TS >>> 24);\n bufpkt[5] = (TS >>> 16 & 0xFF);\n bufpkt[6] = (TS >>> 8 & 0xFF);\n bufpkt[7] = (TS & 0xFF);\n bufpkt[8] = (SSRC >>> 24);\n bufpkt[9] = (SSRC >>> 16 & 0xFF);\n bufpkt[10] = (SSRC >>> 8 & 0xFF);\n bufpkt[11] = (SSRC & 0xFF);*/\n this._bufpkt[0] = 0x80;\n this._bufpkt[1] = 0;\n const SN = Math.floor(1000 * Math.random());\n this._bufpkt[2] = (SN >>> 8)\n this._bufpkt[3] = (SN & 0xFF);\n this._bufpkt[4] = 0;\n this._bufpkt[5] = 0;\n this._bufpkt[6] = 0;\n this._bufpkt[7] = 1;\n this._bufpkt[8] = 0;\n this._bufpkt[9] = 0;\n this._bufpkt[10] = 0;\n this._bufpkt[11] = 1;\n bufpayload.copy(this._bufpkt, 12, 0); // append payload data\n }\n }\n\n public get type() { return (this._bufpkt[1] & 0x7F); }\n public set type(val) {\n val = toUnsigned(val);\n if (val <= 127) {\n this._bufpkt[1] -= (this._bufpkt[1] & 0x7F);\n this._bufpkt[1] |= val;\n }\n }\n\n public get seq() { return (this._bufpkt[2] << 8 | this._bufpkt[3]); }\n public set seq(val) {\n val = toUnsigned(val);\n if (val <= 65535) {\n this._bufpkt[2] = (val >>> 8);\n this._bufpkt[3] = (val & 0xFF);\n }\n }\n\n public get time() { return (this._bufpkt[4] << 24 | this._bufpkt[5] << 16 | this._bufpkt[6] << 8 | this._bufpkt[7]); }\n public set time(val) {\n val = toUnsigned(val);\n if (val <= 4294967295) {\n this._bufpkt[4] = (val >>> 24);\n this._bufpkt[5] = (val >>> 16 & 0xFF);\n this._bufpkt[6] = (val >>> 8 & 0xFF);\n this._bufpkt[7] = (val & 0xFF);\n }\n }\n\n public get source() { return (this._bufpkt[8] << 24 | this._bufpkt[9] << 16 | this._bufpkt[10] << 8 | this._bufpkt[11]); }\n public set source(val) {\n val = toUnsigned(val);\n if (val <= 4294967295) {\n this._bufpkt[8] = (val >>> 24);\n this._bufpkt[9] = (val >>> 16 & 0xFF);\n this._bufpkt[10] = (val >>> 8 & 0xFF);\n this._bufpkt[11] = (val & 0xFF);\n }\n }\n\n // Gets/Sets the payload of an existing RTP packet (without any RTP Headers)\n public get payload() { return (this._bufpkt.slice(12, this._bufpkt.length)); }\n public set payload(val) {\n if (Buffer.isBuffer(val)) {\n const newsize = 12 + val.length;\n if (this._bufpkt.length == newsize)\n val.copy(this._bufpkt, 12, 0);\n else {\n const newbuf = new Buffer(newsize);\n this._bufpkt.copy(newbuf, 0, 0, 12); // copy the RTP header\n val.copy(newbuf, 12, 0);\n this._bufpkt = newbuf;\n }\n }\n }\n\n // gets/sets the RTP Header and RTP Payload\n public get packet() { return this._bufpkt; }\n public set packet(val) {\n this._bufpkt = val;\n }\n}\n\nexport default RTPPacket;"]} +{"version":3,"file":"RTPPacket.js","sourceRoot":"","sources":["../../lib/transports/RTPPacket.ts"],"names":[],"mappings":"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","sourcesContent":["// Packetize audio chunk to RTP packets \r\n// By Thangcq98, August 2022\r\n\r\nimport { Buffer } from \"buffer\";\r\n\r\nfunction toUnsigned(val: number): number {\r\n return ((val >>> 1) * 2 + (val & 1));\r\n}\r\n\r\nclass RTPPacket {\r\n private _bufpkt: Buffer; // holds the RTP header (12 bytes) AND the RTP packet payload\r\n\r\n constructor(bufpayload: Buffer, hasHeader = false) {\r\n /* See RFC3550 for more details: http://www.ietf.org/rfc/rfc3550.txt\r\n V = 2, // version. always 2 for this RFC (2 bits)\r\n P = 0, // padding. not supported yet, so always 0 (1 bit)\r\n X = 0, // header extension (1 bit)\r\n CC = 0, // CSRC count (4 bits)\r\n M = 0, // marker (1 bit)\r\n PT = 0, // payload type. see section 6 in RFC3551 for valid types: http://www.ietf.org/rfc/rfc3551.txt (7 bits)\r\n SN = Math.floor(1000 * Math.random()), // sequence number. SHOULD be random (16 bits)\r\n TS = 1, // timestamp in the format of NTP (# sec. since 0h UTC 1 January 1900)? (32 bits)\r\n SSRC = 1; // synchronization source (32 bits)\r\n //CSRC = 0, // contributing sources. not supported yet (32 bits)\r\n //DP = 0, // header extension, 'Defined By Profile'. not supported yet (16 bits)\r\n //EL = 0; // header extension length. not supported yet (16 bits)\r\n */\r\n if (hasHeader && bufpayload.length >= 12) {\r\n // full packet (generally an incoming packet straight from the socket)\r\n this._bufpkt = bufpayload;\r\n /*V = (bufpkt[0] >>> 6 & 0x03);\r\n P = (bufpkt[0] >>> 5 & 0x01);\r\n X = (bufpkt[0] >>> 4 & 0x01);\r\n CC = (bufpkt[0] & 0x0F);\r\n M = (bufpkt[1] >>> 7 & 0x01);\r\n PT = (bufpkt[1] & 0x7F);\r\n SN = (bufpkt[2] << 8 | bufpkt[3]);\r\n TS = (bufpkt[4] << 24 | bufpkt[5] << 16 | bufpkt[6] << 8 | bufpkt[7]);\r\n SSRC = (bufpkt[8] << 24 | bufpkt[9] << 16 | bufpkt[10] << 8 | bufpkt[11]);*/\r\n // bufpkt[12..bufpkg.length-1] == payload data\r\n } else {\r\n // just payload data (for outgoing/sending)\r\n this._bufpkt = Buffer.alloc(12 + bufpayload.length); // V..SSRC + payload\r\n /*bufpkt[0] = (V << 6 | P << 5 | X << 4 | CC);\r\n bufpkt[1] = (M << 7 | PT);\r\n bufpkt[2] = (SN >>> 8)\r\n bufpkt[3] = (SN & 0xFF);\r\n bufpkt[4] = (TS >>> 24);\r\n bufpkt[5] = (TS >>> 16 & 0xFF);\r\n bufpkt[6] = (TS >>> 8 & 0xFF);\r\n bufpkt[7] = (TS & 0xFF);\r\n bufpkt[8] = (SSRC >>> 24);\r\n bufpkt[9] = (SSRC >>> 16 & 0xFF);\r\n bufpkt[10] = (SSRC >>> 8 & 0xFF);\r\n bufpkt[11] = (SSRC & 0xFF);*/\r\n this._bufpkt[0] = 0x80;\r\n this._bufpkt[1] = 0;\r\n const SN = Math.floor(1000 * Math.random());\r\n this._bufpkt[2] = (SN >>> 8)\r\n this._bufpkt[3] = (SN & 0xFF);\r\n this._bufpkt[4] = 0;\r\n this._bufpkt[5] = 0;\r\n this._bufpkt[6] = 0;\r\n this._bufpkt[7] = 1;\r\n this._bufpkt[8] = 0;\r\n this._bufpkt[9] = 0;\r\n this._bufpkt[10] = 0;\r\n this._bufpkt[11] = 1;\r\n bufpayload.copy(this._bufpkt, 12, 0); // append payload data\r\n }\r\n }\r\n\r\n public get type() { return (this._bufpkt[1] & 0x7F); }\r\n public set type(val) {\r\n val = toUnsigned(val);\r\n if (val <= 127) {\r\n this._bufpkt[1] -= (this._bufpkt[1] & 0x7F);\r\n this._bufpkt[1] |= val;\r\n }\r\n }\r\n\r\n public get seq() { return (this._bufpkt[2] << 8 | this._bufpkt[3]); }\r\n public set seq(val) {\r\n val = toUnsigned(val);\r\n if (val <= 65535) {\r\n this._bufpkt[2] = (val >>> 8);\r\n this._bufpkt[3] = (val & 0xFF);\r\n }\r\n }\r\n\r\n public get time() { return (this._bufpkt[4] << 24 | this._bufpkt[5] << 16 | this._bufpkt[6] << 8 | this._bufpkt[7]); }\r\n public set time(val) {\r\n val = toUnsigned(val);\r\n if (val <= 4294967295) {\r\n this._bufpkt[4] = (val >>> 24);\r\n this._bufpkt[5] = (val >>> 16 & 0xFF);\r\n this._bufpkt[6] = (val >>> 8 & 0xFF);\r\n this._bufpkt[7] = (val & 0xFF);\r\n }\r\n }\r\n\r\n public get source() { return (this._bufpkt[8] << 24 | this._bufpkt[9] << 16 | this._bufpkt[10] << 8 | this._bufpkt[11]); }\r\n public set source(val) {\r\n val = toUnsigned(val);\r\n if (val <= 4294967295) {\r\n this._bufpkt[8] = (val >>> 24);\r\n this._bufpkt[9] = (val >>> 16 & 0xFF);\r\n this._bufpkt[10] = (val >>> 8 & 0xFF);\r\n this._bufpkt[11] = (val & 0xFF);\r\n }\r\n }\r\n\r\n // Gets/Sets the payload of an existing RTP packet (without any RTP Headers)\r\n public get payload() { return (this._bufpkt.slice(12, this._bufpkt.length)); }\r\n public set payload(val) {\r\n if (Buffer.isBuffer(val)) {\r\n const newsize = 12 + val.length;\r\n if (this._bufpkt.length == newsize)\r\n val.copy(this._bufpkt, 12, 0);\r\n else {\r\n const newbuf = Buffer.alloc(newsize);\r\n this._bufpkt.copy(newbuf, 0, 0, 12); // copy the RTP header\r\n val.copy(newbuf, 12, 0);\r\n this._bufpkt = newbuf;\r\n }\r\n }\r\n }\r\n\r\n // gets/sets the RTP Header and RTP Payload\r\n public get packet() { return this._bufpkt; }\r\n public set packet(val) {\r\n this._bufpkt = val;\r\n }\r\n}\r\n\r\nexport default RTPPacket;"]} \ No newline at end of file diff --git a/examples/demo.js b/examples/demo.js index e16ca5c..6b878bb 100644 --- a/examples/demo.js +++ b/examples/demo.js @@ -13,7 +13,7 @@ // Used to connect to Wowza Demo URL but they have taken it away, and the replacement URL on their web site does not work. -const { RTSPClient, H264Transport, H265Transport, AV1Transport, AACTransport } = require("../dist"); +const { RTSPClient, H264Transport, H265Transport, H266Transport, AV1Transport, AACTransport } = require("../dist"); const fs = require("fs"); const { exit } = require("process"); const { program } = require("commander"); @@ -74,6 +74,12 @@ client.connect(url, { connection: "tcp", secure: false }) // This class subscribes to the client 'data' event, looking for the video payload const h265 = new H265Transport(client, videoFile, details); } + if (details.codec == "H266") { + const videoFile = fs.createWriteStream(filename + '.266'); + // Step 4: Create H266Transport passing in the client, file, and details + // This class subscribes to the client 'data' event, looking for the video payload + const h266 = new H266Transport(client, videoFile, details); + } if (details.codec == "AV1") { const videoFile = fs.createWriteStream(filename + '.obu'); // Step 4: Create AV1Transport passing in the client, file, and details diff --git a/lib/RTSPClient.ts b/lib/RTSPClient.ts index 12f8a6e..6ad039d 100644 --- a/lib/RTSPClient.ts +++ b/lib/RTSPClient.ts @@ -314,6 +314,19 @@ export default class RTSPClient extends EventEmitter { } } + if ( + mediaSource.type === "video" && + mediaSource.protocol === RTP_AVP && + mediaSource.rtp[0].codec === "H266" + ) { + this.emit("log", "H266 Video Stream Found in SDP", ""); + if (hasVideo == false) { + needSetup = true; + hasVideo = true; + codec = "H266"; + } + } + if ( mediaSource.type === "video" && (mediaSource.protocol === RTP_AVP || mediaSource.protocol === RTP_AVPF) && @@ -327,7 +340,6 @@ export default class RTSPClient extends EventEmitter { } } - if ( mediaSource.type === "audio" && (mediaSource.direction === "recvonly" || mediaSource.direction === "sendrecv") && diff --git a/lib/index.ts b/lib/index.ts index ea91e89..f263eaa 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,6 @@ import H264Transport from "./transports/H264Transport"; import H265Transport from "./transports/H265Transport"; +import H266Transport from "./transports/H266Transport"; import AV1Transport from "./transports/AV1Transport"; import AACTransport from "./transports/AACTransport"; import ONVIFMetadataTransport from "./transports/ONVIFMetadataTransport"; @@ -10,6 +11,7 @@ import {RTPPacket, RTCPPacket} from "./util"; export { H264Transport, H265Transport, + H266Transport, AV1Transport, AACTransport, ONVIFMetadataTransport, diff --git a/lib/transports/H266Transport.ts b/lib/transports/H266Transport.ts new file mode 100644 index 0000000..88afba0 --- /dev/null +++ b/lib/transports/H266Transport.ts @@ -0,0 +1,215 @@ +// Process SDP and RTP packets +// De-packetize RTP packets to re-create H266 NAL Units +// Write H266 NAL units to a .266 file + +// (c) 2026 Roger Hardiman +// Based on my Yellowstone H265 Parser which in turn is based on my SharpRTSP H265 RTP Parser I wrote in September 2018 + +import RTSPClient from "../RTSPClient"; +import { RTPPacket } from "../util"; + +import * as transform from "sdp-transform"; +import { Writable } from "stream"; + +// .h266 file header (Annex B) +const H266_HEADER = Buffer.from([0x00, 0x00, 0x00, 0x01]); + +interface Details { + codec: string + mediaSource: transform.MediaDescription + rtpChannel: number, + rtcpChannel: number +} + +export default class H266Transport { + client: RTSPClient; + stream: Writable; + has_donl = false; // We have to check the SDP to see if DONL bytes are being set in the RTP packet + + rtpPackets: Buffer[] = []; + + constructor(client: RTSPClient, stream: Writable, details: Details) { + this.client = client; + this.stream = stream; + + // process 'fmtp' (which is optional in the SDP) + this.processConnectionDetails(details); + + client.on("data", (channel, data, packet) => { + if (channel == details.rtpChannel) { + this.processRTPPacket(packet); + } + }); + + } + + processConnectionDetails(details: Details): void { + // Extract the DCI, VPS, SPS and PPS from the MediaSource part of the SDP. + // NOTE the H266 RTP standard makes this optional and we may need to extract this from the RTP payload + // as inband DCI/VPS/SPS/PPS instead + const fmtp = (details.mediaSource.fmtp)[0]; + + if (!fmtp) { + return; + } + + const fmtpConfig = transform.parseParams(fmtp.config); + const dci = ('sprop-dci' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-dci']?.toString(), "base64") : null; + const vps = ('sprop-vps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-vps']?.toString(), "base64") : null; + const sps = ('sprop-sps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-sps']?.toString(), "base64") : null; + const pps = ('sprop-pps' in fmtpConfig) ? Buffer.from(fmtpConfig['sprop-pps']?.toString(), "base64") : null + + if (dci != null) { + this.stream.write(H266_HEADER); + this.stream.write(dci); + } + if (vps != null) { + this.stream.write(H266_HEADER); + this.stream.write(vps); + } + if (sps != null) { + this.stream.write(H266_HEADER); + this.stream.write(sps); + } + if (pps != null) { + this.stream.write(H266_HEADER); + this.stream.write(pps); + } + } + + processRTPPacket(packet: RTPPacket): void { + // Accumatate RTP packets + this.rtpPackets.push(packet.payload); + + // When Marker is set to 1 pass the group of packets to processRTPFrame() + if (packet.marker == 1) { + this.processRTPFrame(this.rtpPackets); + this.rtpPackets = []; + + // Write out the AUD + this.stream.write(H266_HEADER); + this.stream.write(Buffer.from([0x00, 0xA1, 0x88])); + + } + } + + processRTPFrame(rtpPackets: Buffer[]): void { + const nals = []; + let partialNal = []; + + for (let i = 0; i < rtpPackets.length; i++) { + + // Examine the first two bytes of the RTP data, the Payload Header + // F (Forbidden Bit), must be 0 + // Z (Zero Bit), must be 0 + // LayerId (6 bits) + // Type of NAL Unit (5 bits) + // TID (TemporalID = TID - 1) + /*+---------------+---------------+ + *|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + *+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + *|F|Z| LayerID | Type | TID | + *+-------------+-----------------+ + */ + const packet = rtpPackets[i]; + + const payload_header = (packet[0] << 8) | (packet[1]); + const payload_header_f_bit = (payload_header >> 15) & 0x01; + const payload_header_z_bit = (payload_header >> 14) & 0x01; + const payload_header_layer_id = (payload_header >> 8) & 0x3F; + const payload_header_type = (payload_header >> 3) & 0x1F; + const payload_header_tid = payload_header & 0x7; + + // There are three ways to Packetize NAL units into RTP Packets + // Single NAL Unit Packet + // Aggregation Packet + // Fragmentation Unit + + + // Note H266/VVC has a concept of a GDR - Gradual Decoder Refresh + // and of the IDR - Instantaneous Decoding Refresh + + + // Single NAL Unit Packet + if (payload_header_type != 28 && payload_header_type != 29) { + //TODO - Handle DONL + nals.push(packet); + } + + // Aggregation Packet + else if (payload_header_type == 28) { + // TODO. This is an Agregation Packet so we need to extract + // the 2 or more NALs from the packet. + console.log("eek - we have not implemented Agregation Packets yet"); + } + + + // Fragmentation Unit + else if (payload_header_type == 29) { + //Console.WriteLine("Fragmentation Unit"); + + // 0 1 2 3 4 5 6 7 + // +-+-+-+-+-+-+-+-+ + // |S|E|P| FuType | + // +---------------+ + // + + // Parse Fragmentation Unit Header + const fu_header_s = (packet[2] >> 7) & 0x01; // start marker + const fu_header_e = (packet[2] >> 6) & 0x01; // end marker + const fu_header_p = (packet[2] >> 5) & 0x01; // P = last FU of a Coded Picture + const fu_header_type = (packet[2] >> 0) & 0x1F; // fu type (5 bits) + + // Console.WriteLine("Frag FU-A s=" + fu_header_s + "e=" + fu_header_e); + + // Check Start and End flags + if (fu_header_s == 1 && fu_header_e == 0) { + // Start Fragment. + // Initiise the partialNal byte array + + // Empty the partial NAL array + partialNal = []; + + // Reconstrut the NAL header from the rtp_payload_header, replacing the Type with FU Type + let nal_header = (payload_header & 0xFF07); // strip out existing 'type' which is the "FU Type" + nal_header = nal_header | (fu_header_type << 3); // and replace it with the fu_header_type + + partialNal.push((nal_header >> 8) & 0xFF); + partialNal.push((nal_header >> 0) & 0xFF); + } + + + // Copy the video data + if (this.has_donl) { + // start copying after the DONL data + for (let x = 5; x < packet.length; x++) { + partialNal.push(packet[x]); // not very efficient, copying one byte at a time + } + } + else { + // there is no DONL data + for (let x = 3; x < packet.length; x++) { + partialNal.push(packet[x]); // not very efficient, copying one byte at a time + } + } + + if (fu_header_s == 0 && fu_header_e == 1) { + // End Fragment + // Append this payload to the fragmented_nal + + // Add the NAL to the array of NAL units + nals.push(Buffer.from(partialNal)); + } + } + else { + //Console.WriteLine("Unknown Payload Header Type = " + payload_header_type); + } + } + + // Write out all the NALs + for (let x = 0; x < nals.length; x++) { + this.stream.write(H266_HEADER); + this.stream.write(nals[x]); + } + } +} diff --git a/package.json b/package.json index b063497..2f6c2db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yellowstone", - "version": "3.0.6", + "version": "3.0.7", "description": "An RTSP client for node.js.", "main": "dist/index.js", "repository": {