@scrypted/prebuffer-mixin 0.9.43 → 0.9.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin.zip +0 -0
- package/package.json +1 -1
- package/.vscode/launch.json +0 -24
- package/.vscode/settings.json +0 -3
- package/.vscode/tasks.json +0 -20
- package/dist/main.nodejs.js +0 -2
- package/src/file-rtsp-server.ts +0 -61
- package/src/main.ts +0 -1808
- package/src/rfc4571.ts +0 -224
- package/src/rtsp-session.ts +0 -302
- package/src/sps-resolution.ts +0 -50
- package/src/stream-settings.ts +0 -247
- package/src/transcode-settings.ts +0 -51
- package/tsconfig.json +0 -11
package/src/rfc4571.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { cloneDeep } from "@scrypted/common/src/clone-deep";
|
|
2
|
-
import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
|
3
|
-
import { read16BELengthLoop } from "@scrypted/common/src/read-stream";
|
|
4
|
-
import { findH264NaluType, H264_NAL_TYPE_SPS, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
|
|
5
|
-
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
|
6
|
-
import { sleep } from "@scrypted/common/src/sleep";
|
|
7
|
-
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
|
8
|
-
import { MediaStreamOptions, ResponseMediaStreamOptions } from "@scrypted/sdk";
|
|
9
|
-
import { parse as spsParse } from "h264-sps-parser";
|
|
10
|
-
import net from 'net';
|
|
11
|
-
import { EventEmitter, Readable } from "stream";
|
|
12
|
-
import { getSpsResolution } from "./sps-resolution";
|
|
13
|
-
|
|
14
|
-
export function negotiateMediaStream(sdp: string, mediaStreamOptions: MediaStreamOptions, inputVideoCodec: string, inputAudioCodec: string, requestMediaStream: MediaStreamOptions) {
|
|
15
|
-
const parsedSdp = parseSdp(sdp);
|
|
16
|
-
const ret: ResponseMediaStreamOptions = cloneDeep(mediaStreamOptions) || {
|
|
17
|
-
id: undefined,
|
|
18
|
-
name: undefined,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// if the source doesn't provide a video codec, dummy one up
|
|
22
|
-
if (ret.video === undefined)
|
|
23
|
-
ret.video = {};
|
|
24
|
-
|
|
25
|
-
// the requests does not want video
|
|
26
|
-
if (requestMediaStream?.video === null)
|
|
27
|
-
ret.video = null;
|
|
28
|
-
|
|
29
|
-
if (ret.video)
|
|
30
|
-
ret.video.codec = inputVideoCodec;
|
|
31
|
-
|
|
32
|
-
// some rtsp like unifi offer alternate audio tracks (aac and opus).
|
|
33
|
-
if (requestMediaStream?.audio?.codec && requestMediaStream?.audio?.codec !== inputAudioCodec) {
|
|
34
|
-
const alternateAudio = parsedSdp.msections.find(msection => msection.type === 'audio' && msection.codec === requestMediaStream?.audio?.codec);
|
|
35
|
-
if (alternateAudio) {
|
|
36
|
-
ret.audio = {
|
|
37
|
-
codec: requestMediaStream?.audio?.codec,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
return ret;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// reported codecs may be wrong/cached/etc, so before blindly copying the audio codec info,
|
|
45
|
-
// verify what was found.
|
|
46
|
-
if (ret?.audio?.codec === inputAudioCodec) {
|
|
47
|
-
ret.audio = mediaStreamOptions?.audio;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
ret.audio = {
|
|
51
|
-
codec: inputAudioCodec,
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return ret;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function connectRFC4571Parser(url: string) {
|
|
59
|
-
const u = new URL(url);
|
|
60
|
-
if (!u.protocol.startsWith('tcp'))
|
|
61
|
-
throw new Error('rfc4751 url must be tcp');
|
|
62
|
-
|
|
63
|
-
const socket = net.connect(parseInt(u.port), u.hostname);
|
|
64
|
-
return socket;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: ResponseMediaStreamOptions, options?: ParserOptions<"rtsp">): ParserSession<"rtsp"> {
|
|
68
|
-
let isActive = true;
|
|
69
|
-
const events = new EventEmitter();
|
|
70
|
-
// need this to prevent kill from throwing due to uncaught Error during cleanup
|
|
71
|
-
events.on('error', e => console.error('rebroadcast error', e));
|
|
72
|
-
|
|
73
|
-
const parsedSdp = parseSdp(sdp);
|
|
74
|
-
const audioSection = parsedSdp.msections.find(msection => msection.type === 'audio');
|
|
75
|
-
const videoSection = parsedSdp.msections.find(msection => msection.type === 'video');
|
|
76
|
-
const audioPt = audioSection?.payloadTypes?.[0];
|
|
77
|
-
const videoPt = videoSection?.payloadTypes?.[0];
|
|
78
|
-
|
|
79
|
-
const inputAudioCodec = audioSection?.codec;
|
|
80
|
-
const inputVideoCodec = videoSection.codec;
|
|
81
|
-
|
|
82
|
-
let sessionKilled: any;
|
|
83
|
-
const killed = new Promise<void>(resolve => {
|
|
84
|
-
sessionKilled = resolve;
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const kill = (error?: Error) => {
|
|
88
|
-
if (isActive) {
|
|
89
|
-
events.emit('killed');
|
|
90
|
-
events.emit('error', error || new Error('killed'));
|
|
91
|
-
}
|
|
92
|
-
isActive = false;
|
|
93
|
-
sessionKilled();
|
|
94
|
-
socket.destroy();
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
socket.on('close', () => {
|
|
98
|
-
kill(new Error('rfc4751 socket closed'));
|
|
99
|
-
});
|
|
100
|
-
socket.on('error', e => {
|
|
101
|
-
kill(e);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.timeout);
|
|
105
|
-
|
|
106
|
-
let inputVideoResolution: {
|
|
107
|
-
width: number;
|
|
108
|
-
height: number;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const sprop = videoSection
|
|
112
|
-
?.fmtp?.[0]?.parameters?.['sprop-parameter-sets'];
|
|
113
|
-
const sdpSps = sprop?.split(',')?.[0];
|
|
114
|
-
// const sdpPps = sprop?.split(',')?.[1];
|
|
115
|
-
|
|
116
|
-
if (sdpSps) {
|
|
117
|
-
try {
|
|
118
|
-
const sps = Buffer.from(sdpSps, 'base64');
|
|
119
|
-
const parsedSps = spsParse(sps);
|
|
120
|
-
inputVideoResolution = getSpsResolution(parsedSps);
|
|
121
|
-
console.log('parsed sdp sps', parsedSps);
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
console.warn('sdp sps parsing failed');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const start = () => {
|
|
129
|
-
// don't start parsing until next tick, to prevent missed packets.
|
|
130
|
-
read16BELengthLoop(socket, {
|
|
131
|
-
headerLength: 2,
|
|
132
|
-
skipHeader: undefined,
|
|
133
|
-
callback: (header, data) => {
|
|
134
|
-
let type: string;
|
|
135
|
-
const pt = data[1] & 0x7f;
|
|
136
|
-
|
|
137
|
-
const prefix = Buffer.alloc(2);
|
|
138
|
-
prefix[0] = RTSP_FRAME_MAGIC;
|
|
139
|
-
if (pt === audioPt) {
|
|
140
|
-
prefix[1] = 0;
|
|
141
|
-
}
|
|
142
|
-
else if (pt === videoPt) {
|
|
143
|
-
prefix[1] = 2;
|
|
144
|
-
}
|
|
145
|
-
header = Buffer.concat([prefix, header]);
|
|
146
|
-
|
|
147
|
-
if (pt === audioPt)
|
|
148
|
-
type = inputAudioCodec;
|
|
149
|
-
else if (pt === videoPt)
|
|
150
|
-
type = inputVideoCodec;
|
|
151
|
-
|
|
152
|
-
const chunk: StreamChunk = {
|
|
153
|
-
chunks: [header, data],
|
|
154
|
-
type,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
if (!inputVideoResolution) {
|
|
158
|
-
const sps = findH264NaluType(chunk, H264_NAL_TYPE_SPS);
|
|
159
|
-
if (sps) {
|
|
160
|
-
try {
|
|
161
|
-
const parsedSps = spsParse(sps);
|
|
162
|
-
inputVideoResolution = getSpsResolution(parsedSps);
|
|
163
|
-
console.log(inputVideoResolution);
|
|
164
|
-
console.log('parsed bitstream sps', parsedSps);
|
|
165
|
-
}
|
|
166
|
-
catch (e) {
|
|
167
|
-
console.warn('sps parsing failed');
|
|
168
|
-
inputVideoResolution = {
|
|
169
|
-
width: NaN,
|
|
170
|
-
height: NaN,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
events.emit('rtsp', chunk);
|
|
177
|
-
resetActivityTimer();
|
|
178
|
-
}
|
|
179
|
-
})
|
|
180
|
-
.catch(e => {
|
|
181
|
-
throw e;
|
|
182
|
-
})
|
|
183
|
-
.finally(() => {
|
|
184
|
-
kill(new Error('parser exited'));
|
|
185
|
-
});
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
start,
|
|
192
|
-
sdp: Promise.resolve([Buffer.from(sdp)]),
|
|
193
|
-
inputAudioCodec,
|
|
194
|
-
inputVideoCodec,
|
|
195
|
-
get inputVideoResolution() {
|
|
196
|
-
return inputVideoResolution;
|
|
197
|
-
},
|
|
198
|
-
get isActive() { return isActive },
|
|
199
|
-
kill(error?: Error) {
|
|
200
|
-
kill(error);
|
|
201
|
-
},
|
|
202
|
-
killed,
|
|
203
|
-
resetActivityTimer,
|
|
204
|
-
negotiateMediaStream: (requestMediaStream) => {
|
|
205
|
-
return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream);
|
|
206
|
-
},
|
|
207
|
-
emit(container: 'rtsp', chunk: StreamChunk) {
|
|
208
|
-
events.emit(container, chunk);
|
|
209
|
-
return this;
|
|
210
|
-
},
|
|
211
|
-
on(event: string, cb: any) {
|
|
212
|
-
events.on(event, cb);
|
|
213
|
-
return this;
|
|
214
|
-
},
|
|
215
|
-
once(event: any, cb: any) {
|
|
216
|
-
events.once(event, cb);
|
|
217
|
-
return this;
|
|
218
|
-
},
|
|
219
|
-
removeListener(event, cb) {
|
|
220
|
-
events.removeListener(event, cb);
|
|
221
|
-
return this;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
package/src/rtsp-session.ts
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import { ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
|
|
2
|
-
import { closeQuiet, createBindZero } from "@scrypted/common/src/listen-cluster";
|
|
3
|
-
import { findH264NaluType, H264_NAL_TYPE_SPS, parseSemicolonDelimited, RtspClient, RtspClientUdpSetupOptions, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
|
|
4
|
-
import { parseSdp } from "@scrypted/common/src/sdp-utils";
|
|
5
|
-
import { StreamChunk } from "@scrypted/common/src/stream-parser";
|
|
6
|
-
import { ResponseMediaStreamOptions } from "@scrypted/sdk";
|
|
7
|
-
import dgram from 'dgram';
|
|
8
|
-
import { parse as spsParse } from "h264-sps-parser";
|
|
9
|
-
import { EventEmitter } from "stream";
|
|
10
|
-
import { negotiateMediaStream } from "./rfc4571";
|
|
11
|
-
import { getSpsResolution } from "./sps-resolution";
|
|
12
|
-
|
|
13
|
-
export type RtspChannelCodecMapping = { [key: number]: string };
|
|
14
|
-
|
|
15
|
-
export interface RtspSessionParserSpecific {
|
|
16
|
-
interleaved: Map<string, number>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function startRtspSession(console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: {
|
|
20
|
-
useUdp: boolean,
|
|
21
|
-
audioSoftMuted: boolean,
|
|
22
|
-
rtspRequestTimeout: number,
|
|
23
|
-
}): Promise<ParserSession<"rtsp">> {
|
|
24
|
-
let isActive = true;
|
|
25
|
-
const events = new EventEmitter();
|
|
26
|
-
// need this to prevent kill from throwing due to uncaught Error during cleanup
|
|
27
|
-
events.on('error', e => console.error('rebroadcast error', e));
|
|
28
|
-
|
|
29
|
-
let servers: dgram.Socket[] = [];
|
|
30
|
-
const rtspClient = new RtspClient(url);
|
|
31
|
-
rtspClient.console = console;
|
|
32
|
-
rtspClient.requestTimeout = options.rtspRequestTimeout;
|
|
33
|
-
|
|
34
|
-
const cleanupSockets = () => {
|
|
35
|
-
for (const server of servers) {
|
|
36
|
-
closeQuiet(server);
|
|
37
|
-
}
|
|
38
|
-
rtspClient.safeTeardown();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let sessionKilled: any;
|
|
42
|
-
const killed = new Promise<void>(resolve => {
|
|
43
|
-
sessionKilled = resolve;
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const kill = (error?: Error) => {
|
|
47
|
-
if (isActive) {
|
|
48
|
-
events.emit('killed');
|
|
49
|
-
events.emit('error', error || new Error('killed'));
|
|
50
|
-
}
|
|
51
|
-
isActive = false;
|
|
52
|
-
sessionKilled();
|
|
53
|
-
cleanupSockets();
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
rtspClient.client.on('close', () => {
|
|
57
|
-
kill(new Error('rtsp socket closed'));
|
|
58
|
-
});
|
|
59
|
-
rtspClient.client.on('error', e => {
|
|
60
|
-
kill(e);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.rtspRequestTimeout);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
await rtspClient.options();
|
|
67
|
-
const sdpResponse = await rtspClient.describe();
|
|
68
|
-
const contentBase = sdpResponse.headers['content-base'];
|
|
69
|
-
if (contentBase) {
|
|
70
|
-
const url = new URL(contentBase, rtspClient.url);
|
|
71
|
-
const existing = new URL(rtspClient.url);
|
|
72
|
-
for (const p of existing.searchParams) {
|
|
73
|
-
url.searchParams.append(p[0], p[1]);
|
|
74
|
-
}
|
|
75
|
-
url.username = existing.username;
|
|
76
|
-
url.password = existing.password;
|
|
77
|
-
rtspClient.url = url.toString();
|
|
78
|
-
}
|
|
79
|
-
let sdp = sdpResponse.body.toString().trim();
|
|
80
|
-
console.log('sdp', sdp);
|
|
81
|
-
|
|
82
|
-
const parsedSdp = parseSdp(sdp);
|
|
83
|
-
let channel = 0;
|
|
84
|
-
const mapping: RtspChannelCodecMapping = {};
|
|
85
|
-
let udpSessionTimeout: number;
|
|
86
|
-
const { useUdp } = options;
|
|
87
|
-
const checkUdpSessionTimeout = (headers: { [key: string]: string }) => {
|
|
88
|
-
if (useUdp && headers.session && !udpSessionTimeout) {
|
|
89
|
-
const sessionDict = parseSemicolonDelimited(headers.session);
|
|
90
|
-
udpSessionTimeout = parseInt(sessionDict['timeout']);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let parserSpecific: RtspSessionParserSpecific;
|
|
95
|
-
if (!useUdp) {
|
|
96
|
-
parserSpecific = {
|
|
97
|
-
interleaved: new Map(),
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const doSetup = async (control: string, codec: string) => {
|
|
102
|
-
let udp: dgram.Socket;
|
|
103
|
-
if (useUdp) {
|
|
104
|
-
const rtspChannel = channel;
|
|
105
|
-
|
|
106
|
-
const setup: RtspClientUdpSetupOptions = {
|
|
107
|
-
path: control,
|
|
108
|
-
type: 'udp',
|
|
109
|
-
onRtp: (header, data) => {
|
|
110
|
-
const prefix = Buffer.alloc(4);
|
|
111
|
-
prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
|
|
112
|
-
prefix.writeUInt8(rtspChannel, 1);
|
|
113
|
-
prefix.writeUInt16BE(data.length, 2);
|
|
114
|
-
const chunk: StreamChunk = {
|
|
115
|
-
chunks: [prefix, data],
|
|
116
|
-
type: codec,
|
|
117
|
-
};
|
|
118
|
-
events.emit('rtsp', chunk);
|
|
119
|
-
resetActivityTimer?.();
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
const setupResult = await rtspClient.setup(setup);
|
|
123
|
-
udp = setup.dgram;
|
|
124
|
-
checkUdpSessionTimeout(setupResult.headers);
|
|
125
|
-
|
|
126
|
-
const punch = Buffer.alloc(1);
|
|
127
|
-
const transport = setupResult.headers['transport'];
|
|
128
|
-
const match = transport.match(/.*?server_port=([0-9]+)-([0-9]+)/);
|
|
129
|
-
const [_, rtp, rtcp] = match;
|
|
130
|
-
const { hostname } = new URL(rtspClient.url);
|
|
131
|
-
udp.send(punch, parseInt(rtp), hostname)
|
|
132
|
-
|
|
133
|
-
mapping[channel] = codec;
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
const setupResult = await rtspClient.setup({
|
|
137
|
-
path: control,
|
|
138
|
-
type: 'tcp',
|
|
139
|
-
port: channel,
|
|
140
|
-
onRtp: (header, data) => {
|
|
141
|
-
const chunk: StreamChunk = {
|
|
142
|
-
chunks: [header, data],
|
|
143
|
-
type: codec,
|
|
144
|
-
};
|
|
145
|
-
events.emit('rtsp', chunk);
|
|
146
|
-
resetActivityTimer?.();
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const resultChannel = setupResult.interleaved ? setupResult.interleaved.begin : channel;
|
|
151
|
-
mapping[resultChannel] = codec;
|
|
152
|
-
parserSpecific.interleaved.set(codec, resultChannel);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
channel += 2;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let setupVideoSection = false;
|
|
159
|
-
|
|
160
|
-
parsedSdp.msections = parsedSdp.msections.filter(section => {
|
|
161
|
-
if (section.type === 'video') {
|
|
162
|
-
if (setupVideoSection) {
|
|
163
|
-
console.warn('additional video section found. skipping.');
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
setupVideoSection = true;
|
|
167
|
-
}
|
|
168
|
-
else if (section.type !== 'audio') {
|
|
169
|
-
console.warn('unknown section', section.type);
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
else if (options.audioSoftMuted) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
return true;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
for (const section of parsedSdp.msections) {
|
|
179
|
-
await doSetup(section.control, section.codec)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// sdp may contain multiple audio/video sections. take only the first video section.
|
|
183
|
-
sdp = [...parsedSdp.header.lines, ...parsedSdp.msections.map(msection => msection.lines).flat()].join('\r\n');
|
|
184
|
-
|
|
185
|
-
// don't start parsing until next tick when this function returns to allow
|
|
186
|
-
// event handlers to be set prior to parsing.
|
|
187
|
-
const start = async () => {
|
|
188
|
-
try {
|
|
189
|
-
await rtspClient.play();
|
|
190
|
-
rtspClient.console = undefined;
|
|
191
|
-
await rtspClient.readLoop();
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
kill(e);
|
|
195
|
-
}
|
|
196
|
-
finally {
|
|
197
|
-
kill(new Error('rtsp read loop exited'));
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// this return block is intentional, to ensure that the remaining code happens sync.
|
|
202
|
-
return (() => {
|
|
203
|
-
const audioSection = parsedSdp.msections.find(msection => msection.type === 'audio');
|
|
204
|
-
const videoSection = parsedSdp.msections.find(msection => msection.type === 'video');
|
|
205
|
-
|
|
206
|
-
if (!videoSection)
|
|
207
|
-
throw new Error('SDP does not contain a video section!');
|
|
208
|
-
|
|
209
|
-
const inputAudioCodec = audioSection?.codec;
|
|
210
|
-
const inputVideoCodec = videoSection.codec;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
let inputVideoResolution: {
|
|
214
|
-
width: number;
|
|
215
|
-
height: number;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
const probeStart = Date.now();
|
|
219
|
-
const probe = (chunk: StreamChunk) => {
|
|
220
|
-
if (Date.now() - probeStart > 6000)
|
|
221
|
-
events.removeListener('rtsp', probe);
|
|
222
|
-
const sps = findH264NaluType(chunk, H264_NAL_TYPE_SPS);
|
|
223
|
-
if (sps) {
|
|
224
|
-
try {
|
|
225
|
-
const parsedSps = spsParse(sps);
|
|
226
|
-
inputVideoResolution = getSpsResolution(parsedSps);
|
|
227
|
-
// console.log(inputVideoResolution);
|
|
228
|
-
console.log('parsed bitstream sps', inputVideoResolution);
|
|
229
|
-
}
|
|
230
|
-
catch (e) {
|
|
231
|
-
console.warn('sps parsing failed');
|
|
232
|
-
inputVideoResolution = {
|
|
233
|
-
width: NaN,
|
|
234
|
-
height: NaN,
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
events.removeListener('rtsp', probe);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!inputVideoResolution)
|
|
242
|
-
events.on('rtsp', probe);
|
|
243
|
-
|
|
244
|
-
const sprop = videoSection
|
|
245
|
-
?.fmtp?.[0]?.parameters?.['sprop-parameter-sets'];
|
|
246
|
-
const sdpSps = sprop?.split(',')?.[0];
|
|
247
|
-
// const sdpPps = sprop?.split(',')?.[1];
|
|
248
|
-
|
|
249
|
-
if (sdpSps) {
|
|
250
|
-
try {
|
|
251
|
-
const sps = Buffer.from(sdpSps, 'base64');
|
|
252
|
-
const parsedSps = spsParse(sps);
|
|
253
|
-
inputVideoResolution = getSpsResolution(parsedSps);
|
|
254
|
-
console.log('parsed sdp sps', inputVideoResolution);
|
|
255
|
-
}
|
|
256
|
-
catch (e) {
|
|
257
|
-
console.warn('sdp sps parsing failed');
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return {
|
|
262
|
-
parserSpecific,
|
|
263
|
-
start,
|
|
264
|
-
sdp: Promise.resolve([Buffer.from(sdp)]),
|
|
265
|
-
inputAudioCodec,
|
|
266
|
-
inputVideoCodec,
|
|
267
|
-
get inputVideoResolution() {
|
|
268
|
-
return inputVideoResolution;
|
|
269
|
-
},
|
|
270
|
-
get isActive() { return isActive },
|
|
271
|
-
kill(error?: Error) {
|
|
272
|
-
kill(error);
|
|
273
|
-
},
|
|
274
|
-
killed,
|
|
275
|
-
resetActivityTimer,
|
|
276
|
-
negotiateMediaStream: (requestMediaStream) => {
|
|
277
|
-
return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream);
|
|
278
|
-
},
|
|
279
|
-
emit(container: 'rtsp', chunk: StreamChunk) {
|
|
280
|
-
events.emit(container, chunk);
|
|
281
|
-
return this;
|
|
282
|
-
},
|
|
283
|
-
on(event: string, cb: any) {
|
|
284
|
-
events.on(event, cb);
|
|
285
|
-
return this;
|
|
286
|
-
},
|
|
287
|
-
once(event: any, cb: any) {
|
|
288
|
-
events.once(event, cb);
|
|
289
|
-
return this;
|
|
290
|
-
},
|
|
291
|
-
removeListener(event, cb) {
|
|
292
|
-
events.removeListener(event, cb);
|
|
293
|
-
return this;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
})();
|
|
297
|
-
}
|
|
298
|
-
catch (e) {
|
|
299
|
-
cleanupSockets();
|
|
300
|
-
throw e;
|
|
301
|
-
}
|
|
302
|
-
}
|
package/src/sps-resolution.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { SPSInfo } from "h264-sps-parser";
|
|
2
|
-
|
|
3
|
-
export function getSpsResolution(sps: SPSInfo) {
|
|
4
|
-
let SubWidthC: number;
|
|
5
|
-
let SubHeightC: number;
|
|
6
|
-
|
|
7
|
-
if (sps.chroma_format_idc == 0 && sps.color_plane_flag == 0) { //monochrome
|
|
8
|
-
SubWidthC = SubHeightC = 0;
|
|
9
|
-
}
|
|
10
|
-
else if (sps.chroma_format_idc == 1 && sps.color_plane_flag == 0) { //4:2:0
|
|
11
|
-
SubWidthC = SubHeightC = 2;
|
|
12
|
-
}
|
|
13
|
-
else if (sps.chroma_format_idc == 2 && sps.color_plane_flag == 0) { //4:2:2
|
|
14
|
-
SubWidthC = 2;
|
|
15
|
-
SubHeightC = 1;
|
|
16
|
-
}
|
|
17
|
-
else if (sps.chroma_format_idc == 3) { //4:4:4
|
|
18
|
-
if (sps.color_plane_flag == 0) {
|
|
19
|
-
SubWidthC = SubHeightC = 1;
|
|
20
|
-
}
|
|
21
|
-
else if (sps.color_plane_flag == 1) {
|
|
22
|
-
SubWidthC = SubHeightC = 0;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let PicWidthInMbs = sps.pic_width_in_mbs;
|
|
27
|
-
|
|
28
|
-
let PicHeightInMapUnits = sps.pic_height_in_map_units;
|
|
29
|
-
let FrameHeightInMbs = (2 - sps.frame_mbs_only_flag) * PicHeightInMapUnits;
|
|
30
|
-
|
|
31
|
-
let crop_left = 0;
|
|
32
|
-
let crop_right = 0;
|
|
33
|
-
let crop_top = 0;
|
|
34
|
-
let crop_bottom = 0;
|
|
35
|
-
|
|
36
|
-
if (sps.frame_cropping_flag) {
|
|
37
|
-
crop_left = sps.frame_cropping.left;
|
|
38
|
-
crop_right = sps.frame_cropping.right;
|
|
39
|
-
crop_top = sps.frame_cropping.top;
|
|
40
|
-
crop_bottom = sps.frame_cropping.bottom;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
let width = PicWidthInMbs * 16 - SubWidthC * (crop_left + crop_right);
|
|
44
|
-
let height = FrameHeightInMbs * 16 - SubHeightC * (2 - sps.frame_mbs_only_flag) * (crop_top + crop_bottom);
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
width,
|
|
48
|
-
height,
|
|
49
|
-
};
|
|
50
|
-
}
|