@scrypted/prebuffer-mixin 0.1.259 → 0.1.262

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.
@@ -0,0 +1,251 @@
1
+ import { ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
2
+ import { closeQuiet, createBindZero } from "@scrypted/common/src/listen-cluster";
3
+ import { parseSemicolonDelimited, RtspClient, 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
+
16
+ export async function startRtspSession(console: Console, url: string, mediaStreamOptions: ResponseMediaStreamOptions, options: {
17
+ useUdp: boolean,
18
+ audioSoftMuted: boolean,
19
+ rtspRequestTimeout: number,
20
+ }): Promise<ParserSession<"rtsp">> {
21
+ let isActive = true;
22
+ const events = new EventEmitter();
23
+ // need this to prevent kill from throwing due to uncaught Error during cleanup
24
+ events.on('error', e => console.error('rebroadcast error', e));
25
+
26
+ let servers: dgram.Socket[] = [];
27
+ const rtspClient = new RtspClient(url, console);
28
+ rtspClient.requestTimeout = options.rtspRequestTimeout;
29
+
30
+ const cleanupSockets = () => {
31
+ for (const server of servers) {
32
+ closeQuiet(server);
33
+ }
34
+ rtspClient.safeTeardown();
35
+ }
36
+
37
+ let sessionKilled: any;
38
+ const killed = new Promise<void>(resolve => {
39
+ sessionKilled = resolve;
40
+ });
41
+
42
+ const kill = () => {
43
+ if (isActive) {
44
+ events.emit('killed');
45
+ events.emit('error', new Error('killed'));
46
+ }
47
+ isActive = false;
48
+ sessionKilled();
49
+ cleanupSockets();
50
+ };
51
+
52
+ rtspClient.client.on('close', () => {
53
+ kill();
54
+ });
55
+ rtspClient.client.on('error', () => {
56
+ kill();
57
+ });
58
+
59
+ const { resetActivityTimer } = setupActivityTimer('rtsp', kill, events, options?.rtspRequestTimeout);
60
+
61
+ try {
62
+ await rtspClient.options();
63
+ const sdpResponse = await rtspClient.describe();
64
+ const contentBase = sdpResponse.headers['content-base'];
65
+ if (contentBase) {
66
+ const url = new URL(contentBase);
67
+ const existing = new URL(rtspClient.url);
68
+ url.username = existing.username;
69
+ url.password = existing.password;
70
+ rtspClient.url = url.toString();
71
+ }
72
+ let sdp = sdpResponse.body.toString().trim();
73
+ console.log('sdp', sdp);
74
+
75
+ const parsedSdp = parseSdp(sdp);
76
+ let channel = 0;
77
+ const mapping: RtspChannelCodecMapping = {};
78
+ let udpSessionTimeout: number;
79
+ const { useUdp } = options;
80
+ const checkUdpSessionTimeout = (headers: { [key: string]: string }) => {
81
+ if (useUdp && headers.session && !udpSessionTimeout) {
82
+ const sessionDict = parseSemicolonDelimited(headers.session);
83
+ udpSessionTimeout = parseInt(sessionDict['timeout']);
84
+ }
85
+ }
86
+
87
+ const doSetup = async (control: string, codec: string) => {
88
+ let setupChannel = channel;
89
+ let udp: dgram.Socket;
90
+ if (useUdp) {
91
+ const rtspChannel = channel;
92
+ const { port, server } = await createBindZero();
93
+ udp = server;
94
+ servers.push(server);
95
+ setupChannel = port;
96
+ server.on('message', data => {
97
+ const prefix = Buffer.alloc(4);
98
+ prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
99
+ prefix.writeUInt8(rtspChannel, 1);
100
+ prefix.writeUInt16BE(data.length, 2);
101
+ const chunk: StreamChunk = {
102
+ chunks: [prefix, data],
103
+ type: codec,
104
+ };
105
+ events.emit('rtsp', chunk);
106
+ resetActivityTimer?.();
107
+ });
108
+
109
+ const setupResult = await rtspClient.setup({
110
+ path: control,
111
+ type: 'udp',
112
+ port: setupChannel,
113
+ });
114
+ checkUdpSessionTimeout(setupResult.headers);
115
+
116
+ const punch = Buffer.alloc(1);
117
+ const transport = setupResult.headers['transport'];
118
+ const match = transport.match(/.*?server_port=([0-9]+)-([0-9]+)/);
119
+ const [_, rtp, rtcp] = match;
120
+ const { hostname } = new URL(rtspClient.url);
121
+ udp.send(punch, parseInt(rtp), hostname)
122
+
123
+ mapping[channel] = codec;
124
+ }
125
+ else {
126
+ const setupResult = await rtspClient.setup({
127
+ path: control,
128
+ type: 'tcp',
129
+ port: setupChannel,
130
+ onRtp: (header, data) => {
131
+ const chunk: StreamChunk = {
132
+ chunks: [header, data],
133
+ type: codec,
134
+ };
135
+ events.emit('rtsp', chunk);
136
+ resetActivityTimer?.();
137
+ },
138
+ });
139
+
140
+ if (setupResult.interleaved)
141
+ mapping[setupResult.interleaved.begin] = codec;
142
+ else
143
+ mapping[channel] = codec;
144
+ }
145
+
146
+ channel += 2;
147
+ }
148
+
149
+ let setupVideoSection = false;
150
+
151
+ parsedSdp.msections = parsedSdp.msections.filter(section => {
152
+ if (section.type === 'video') {
153
+ if (setupVideoSection) {
154
+ console.warn('additional video section found. skipping.');
155
+ return false;
156
+ }
157
+ setupVideoSection = true;
158
+ }
159
+ else if (section.type !== 'audio') {
160
+ console.warn('unknown section', section.type);
161
+ return false;
162
+ }
163
+ else if (options.audioSoftMuted) {
164
+ return false;
165
+ }
166
+ return true;
167
+ });
168
+
169
+ for (const section of parsedSdp.msections) {
170
+ await doSetup(section.control, section.codec)
171
+ }
172
+
173
+ // sdp may contain multiple audio/video sections. take only the first video section.
174
+ sdp = [...parsedSdp.header.lines, ...parsedSdp.msections.map(msection => msection.lines).flat()].join('\r\n');
175
+
176
+ await rtspClient.play();
177
+
178
+ // don't start parsing until next tick when this function returns to allow
179
+ // event handlers to be set prior to parsing.
180
+ process.nextTick(() => rtspClient.readLoop().finally(kill));
181
+
182
+ // this return block is intentional, to ensure that the remaining code happens sync.
183
+ return (() => {
184
+ const audioSection = parsedSdp.msections.find(msection => msection.type === 'audio');
185
+ const videoSection = parsedSdp.msections.find(msection => msection.type === 'video');
186
+
187
+ const inputAudioCodec = audioSection?.codec;
188
+ const inputVideoCodec = videoSection.codec;
189
+
190
+ let inputVideoResolution: {
191
+ width: number;
192
+ height: number;
193
+ };
194
+
195
+ const sprop = videoSection
196
+ ?.fmtp?.[0]?.parameters?.['sprop-parameter-sets'];
197
+ const sdpSps = sprop?.split(',')?.[0];
198
+ // const sdpPps = sprop?.split(',')?.[1];
199
+
200
+ if (sdpSps) {
201
+ try {
202
+ const sps = Buffer.from(sdpSps, 'base64');
203
+ const parsedSps = spsParse(sps);
204
+ inputVideoResolution = getSpsResolution(parsedSps);
205
+ console.log('parsed sdp sps', parsedSps);
206
+ }
207
+ catch (e) {
208
+ console.warn('sdp sps parsing failed');
209
+ }
210
+ }
211
+
212
+ return {
213
+ sdp: Promise.resolve([Buffer.from(sdp)]),
214
+ inputAudioCodec,
215
+ inputVideoCodec,
216
+ get inputVideoResolution() {
217
+ return inputVideoResolution;
218
+ },
219
+ get isActive() { return isActive },
220
+ kill() {
221
+ kill();
222
+ },
223
+ killed,
224
+ resetActivityTimer,
225
+ negotiateMediaStream: (requestMediaStream) => {
226
+ return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream);
227
+ },
228
+ emit(container: 'rtsp', chunk: StreamChunk) {
229
+ events.emit(container, chunk);
230
+ return this;
231
+ },
232
+ on(event: string, cb: any) {
233
+ events.on(event, cb);
234
+ return this;
235
+ },
236
+ once(event: any, cb: any) {
237
+ events.once(event, cb);
238
+ return this;
239
+ },
240
+ removeListener(event, cb) {
241
+ events.removeListener(event, cb);
242
+ return this;
243
+ }
244
+ }
245
+ })();
246
+ }
247
+ catch (e) {
248
+ cleanupSockets();
249
+ throw e;
250
+ }
251
+ }
@@ -0,0 +1,50 @@
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
+ }
@@ -1,6 +1,6 @@
1
1
  import { getH264DecoderArgs } from "@scrypted/common/src/ffmpeg-hardware-acceleration";
2
2
  import { StorageSetting, StorageSettings } from "@scrypted/common/src/settings";
3
- import sdk, { MixinDeviceBase, ResponseMediaStreamOptions, VideoCamera } from "@scrypted/sdk";
3
+ import { MixinDeviceBase, ResponseMediaStreamOptions, VideoCamera } from "@scrypted/sdk";
4
4
  import { getTranscodeMixinProviderId } from "./transcode-settings";
5
5
 
6
6
 
@@ -1,75 +0,0 @@
1
- function ExpGolomInit(view: DataView, bitoffset: number): { zeros: number; skip: number; byt: number; byteoffset: number } {
2
- let bit = 0;
3
- let byteoffset = bitoffset >> 3;
4
- let skip = bitoffset & 7;
5
- let zeros = -1;
6
-
7
- let byt = view.getUint8(byteoffset) << skip;
8
- do {
9
- bit = byt & 0x80;
10
- byt <<= 1;
11
- zeros++;
12
- skip++;
13
- if (skip === 8) {
14
- skip = 0;
15
- byteoffset++;
16
- byt = view.getUint8(byteoffset);
17
- }
18
- } while (!bit);
19
-
20
- return { zeros, skip, byt, byteoffset };
21
- }
22
-
23
- export class Bitstream {
24
- public bitoffset = 0;
25
- constructor (public view: DataView) {}
26
-
27
- ExpGolomb(): number {
28
- const { view } = this;
29
- let {
30
- zeros, skip, byt, byteoffset,
31
- } = ExpGolomInit(view, this.bitoffset);
32
-
33
- let code = 1;
34
- while (zeros > 0) {
35
- code = (code << 1) | ((byt & 0x80) >>> 7);
36
- byt <<= 1;
37
- skip++;
38
- zeros--;
39
- if (skip === 8) {
40
- skip = 0;
41
- byteoffset++;
42
- byt = view.getUint8(byteoffset);
43
- }
44
- }
45
-
46
- this.bitoffset = (byteoffset << 3) | skip;
47
- return code - 1;
48
- }
49
-
50
- SignedExpGolomb(): number {
51
- const code = this.ExpGolomb();
52
- return code & 1 ? (code + 1) >>> 1 : -(code >>> 1);
53
- }
54
-
55
- readBit(): 0 | 1 {
56
- const skip = this.bitoffset & 7;
57
- const byteoffset = this.bitoffset >> 3;
58
- this.bitoffset++;
59
- return ((this.view.getUint8(byteoffset) >> (7 - skip)) & 1) as 0|1;
60
- }
61
-
62
- readByte(): number {
63
- const skip = this.bitoffset & 7;
64
- const byteoffset = this.bitoffset >> 3;
65
- this.bitoffset += 8;
66
-
67
- const high = this.view.getUint8(byteoffset);
68
- if (skip === 0) return high;
69
-
70
- const low = this.view.getUint8(byteoffset + 1);
71
-
72
- return (high << skip) | (low >> (8 - skip));
73
- }
74
- }
75
-
@@ -1,237 +0,0 @@
1
- import { Bitstream } from "./bitstream";
2
- import { getVUIParams, VUIParams } from "./vui";
3
-
4
- type FrameCropping = {
5
- left: number;
6
- right: number;
7
- top: number;
8
- bottom: number;
9
- };
10
-
11
- export type SPSInfo = {
12
- sps_id: number;
13
- profile_idc: number;
14
- level_idc: number;
15
- profile_compatibility: number;
16
- frame_mbs_only_flag: 0|1;
17
- pic_width_in_mbs: number;
18
- pic_height_in_map_units: number;
19
- frame_cropping_flag: 0|1;
20
- frame_cropping: FrameCropping;
21
-
22
- chroma_format_idc: number;
23
- bit_depth_luma: number;
24
- bit_depth_chroma: number;
25
- color_plane_flag: 0|1;
26
- qpprime_y_zero_transform_bypass_flag: 0|1;
27
- seq_scaling_matrix_present_flag: 0|1;
28
- seq_scaling_matrix: number[][];
29
- log2_max_frame_num: number;
30
- pic_order_cnt_type: number;
31
- delta_pic_order_always_zero_flag: 0|1;
32
- offset_for_non_ref_pic: number;
33
- offset_for_top_to_bottom_field: number;
34
- offset_for_ref_frame: number[];
35
- log2_max_pic_order_cnt_lsb: number;
36
-
37
- max_num_ref_frames: number;
38
- gaps_in_frame_num_value_allowed_flag: 0|1;
39
- mb_adaptive_frame_field_flag: 0|1;
40
- direct_8x8_inference_flag: 0|1;
41
- vui_parameters_present_flag: 0|1;
42
- vui_parameters: VUIParams;
43
- };
44
-
45
- function scaling_list(stream: Bitstream, sizeOfScalingList: number): number[] {
46
- let lastScale = 8;
47
- let nextScale = 8;
48
- const scaling_list = [];
49
- for (let j = 0; j < sizeOfScalingList; j++) {
50
- if (nextScale !== 0) {
51
- const deltaScale = stream.SignedExpGolomb();
52
- nextScale = (lastScale + deltaScale + 256) % 256;
53
- }
54
- if (nextScale) { lastScale = nextScale; }
55
- scaling_list.push(nextScale);
56
- }
57
- return scaling_list;
58
- }
59
-
60
- function getFrameCropping(flag: 0|1, stream: Bitstream): FrameCropping {
61
- if (!flag) return { left: 0, right: 0, top: 0, bottom: 0 };
62
-
63
- const left = stream.ExpGolomb();
64
- const right = stream.ExpGolomb();
65
- const top = stream.ExpGolomb();
66
- const bottom = stream.ExpGolomb();
67
- return { left, right, top, bottom };
68
- }
69
-
70
- export function parse(nalu: Uint8Array): SPSInfo {
71
- if ((nalu[0] & 0x1F) !== 7) throw new Error("Not an SPS unit");
72
-
73
- const stream = new Bitstream(new DataView(nalu.buffer, nalu.byteOffset + 4));
74
-
75
- const profile_idc = nalu[1];
76
- const profile_compatibility = nalu[2];
77
- const level_idc = nalu[3];
78
- const sps_id = stream.ExpGolomb();
79
-
80
- let chroma_format_idc = 1;
81
- let bit_depth_luma = 0;
82
- let bit_depth_chroma = 0;
83
- let color_plane_flag: 0|1 = 0;
84
- let qpprime_y_zero_transform_bypass_flag: 0|1 = 0;
85
- let seq_scaling_matrix_present_flag: 0|1 = 0;
86
-
87
- const seq_scaling_matrix = [];
88
-
89
- if ( profile_idc === 100 || profile_idc === 110 ||
90
- profile_idc === 122 || profile_idc === 244 || profile_idc === 44 ||
91
- profile_idc === 83 || profile_idc === 86 || profile_idc === 118 ||
92
- profile_idc === 128 ) {
93
- chroma_format_idc = stream.ExpGolomb();
94
- let limit = 8;
95
- if (chroma_format_idc === 3) {
96
- limit = 12;
97
- color_plane_flag = stream.readBit();
98
- }
99
- bit_depth_luma = stream.ExpGolomb() + 8;
100
- bit_depth_chroma = stream.ExpGolomb() + 8;
101
- qpprime_y_zero_transform_bypass_flag = stream.readBit();
102
- seq_scaling_matrix_present_flag = stream.readBit();
103
- if (seq_scaling_matrix_present_flag) {
104
- let i = 0;
105
- for (; i < 6; i++) {
106
- if (stream.readBit()) { //seq_scaling_list_present_flag
107
- seq_scaling_matrix.push(scaling_list(stream, 16));
108
- }
109
- }
110
- for (; i < limit; i++) {
111
- if (stream.readBit()) { //seq_scaling_list_present_flag
112
- seq_scaling_matrix.push(scaling_list(stream, 64));
113
- }
114
- }
115
- }
116
- }
117
-
118
- const log2_max_frame_num = stream.ExpGolomb() + 4;
119
- const pic_order_cnt_type = stream.ExpGolomb();
120
-
121
- let delta_pic_order_always_zero_flag: 0|1 = 0;
122
- let offset_for_non_ref_pic = 0;
123
- let offset_for_top_to_bottom_field = 0;
124
-
125
- const offset_for_ref_frame = [];
126
-
127
- let log2_max_pic_order_cnt_lsb = 0;
128
- if (pic_order_cnt_type === 0) {
129
- log2_max_pic_order_cnt_lsb = stream.ExpGolomb() + 4;
130
- } else if (pic_order_cnt_type === 1) {
131
- delta_pic_order_always_zero_flag = stream.readBit();
132
- offset_for_non_ref_pic = stream.SignedExpGolomb();
133
- offset_for_top_to_bottom_field = stream.SignedExpGolomb();
134
- const num_ref_frames_in_pic_order_cnt_cycle = stream.SignedExpGolomb();
135
- for (let i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) {
136
- offset_for_ref_frame.push(stream.SignedExpGolomb());
137
- }
138
- }
139
-
140
- const max_num_ref_frames = stream.ExpGolomb();
141
- const gaps_in_frame_num_value_allowed_flag = stream.readBit();
142
- const pic_width_in_mbs = stream.ExpGolomb() + 1;
143
- const pic_height_in_map_units = stream.ExpGolomb() + 1;
144
- const frame_mbs_only_flag = stream.readBit();
145
- let mb_adaptive_frame_field_flag: 0|1 = 0;
146
- if (!frame_mbs_only_flag) {
147
- mb_adaptive_frame_field_flag = stream.readBit();
148
- }
149
-
150
- const direct_8x8_inference_flag = stream.readBit();
151
- const frame_cropping_flag = stream.readBit();
152
- const frame_cropping = getFrameCropping(frame_cropping_flag, stream);
153
-
154
- const vui_parameters_present_flag = stream.readBit();
155
- const vui_parameters = getVUIParams(vui_parameters_present_flag, stream);
156
-
157
- return {
158
- sps_id,
159
- profile_compatibility,
160
- profile_idc,
161
- level_idc,
162
- chroma_format_idc,
163
- bit_depth_luma,
164
- bit_depth_chroma,
165
- color_plane_flag,
166
- qpprime_y_zero_transform_bypass_flag,
167
- seq_scaling_matrix_present_flag,
168
- seq_scaling_matrix,
169
- log2_max_frame_num,
170
- pic_order_cnt_type,
171
- delta_pic_order_always_zero_flag,
172
- offset_for_non_ref_pic,
173
- offset_for_top_to_bottom_field,
174
- offset_for_ref_frame,
175
- log2_max_pic_order_cnt_lsb,
176
- max_num_ref_frames,
177
- gaps_in_frame_num_value_allowed_flag,
178
- pic_width_in_mbs,
179
- pic_height_in_map_units,
180
- frame_mbs_only_flag,
181
- mb_adaptive_frame_field_flag,
182
- direct_8x8_inference_flag,
183
- frame_cropping_flag,
184
- frame_cropping,
185
- vui_parameters_present_flag,
186
- vui_parameters,
187
- };
188
- }
189
-
190
- export function getSpsResolution(sps: SPSInfo) {
191
- let SubWidthC: number;
192
- let SubHeightC: number;
193
-
194
- if (sps.chroma_format_idc == 0 && sps.color_plane_flag == 0) { //monochrome
195
- SubWidthC = SubHeightC = 0;
196
- }
197
- else if (sps.chroma_format_idc == 1 && sps.color_plane_flag == 0) { //4:2:0
198
- SubWidthC = SubHeightC = 2;
199
- }
200
- else if (sps.chroma_format_idc == 2 && sps.color_plane_flag == 0) { //4:2:2
201
- SubWidthC = 2;
202
- SubHeightC = 1;
203
- }
204
- else if (sps.chroma_format_idc == 3) { //4:4:4
205
- if (sps.color_plane_flag == 0) {
206
- SubWidthC = SubHeightC = 1;
207
- }
208
- else if (sps.color_plane_flag == 1) {
209
- SubWidthC = SubHeightC = 0;
210
- }
211
- }
212
-
213
- let PicWidthInMbs = sps.pic_width_in_mbs;
214
-
215
- let PicHeightInMapUnits = sps.pic_height_in_map_units;
216
- let FrameHeightInMbs = (2 - sps.frame_mbs_only_flag) * PicHeightInMapUnits;
217
-
218
- let crop_left = 0;
219
- let crop_right = 0;
220
- let crop_top = 0;
221
- let crop_bottom = 0;
222
-
223
- if (sps.frame_cropping_flag) {
224
- crop_left = sps.frame_cropping.left;
225
- crop_right = sps.frame_cropping.right;
226
- crop_top = sps.frame_cropping.top;
227
- crop_bottom = sps.frame_cropping.bottom;
228
- }
229
-
230
- let width = PicWidthInMbs * 16 - SubWidthC * (crop_left + crop_right);
231
- let height = FrameHeightInMbs * 16 - SubHeightC * (2 - sps.frame_mbs_only_flag) * (crop_top + crop_bottom);
232
-
233
- return {
234
- width,
235
- height,
236
- };
237
- }