@apocaliss92/nodelink-js 0.4.11 → 0.4.12
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/BaichuanVideoStream-PHQG4A2L.js +7 -0
- package/dist/{DiagnosticsTools-RNIDFEJK.js → DiagnosticsTools-HGJGVQXZ.js} +3 -2
- package/dist/DiagnosticsTools-HGJGVQXZ.js.map +1 -0
- package/dist/{chunk-EDLMKBG2.js → chunk-IJG45AOT.js} +151 -2733
- package/dist/chunk-IJG45AOT.js.map +1 -0
- package/dist/{chunk-HGQ53FB3.js → chunk-ND73IJIB.js} +765 -45
- package/dist/chunk-ND73IJIB.js.map +1 -0
- package/dist/chunk-W2ANCJVM.js +2703 -0
- package/dist/chunk-W2ANCJVM.js.map +1 -0
- package/dist/cli/rtsp-server.cjs +834 -10
- package/dist/cli/rtsp-server.cjs.map +1 -1
- package/dist/cli/rtsp-server.js +3 -2
- package/dist/cli/rtsp-server.js.map +1 -1
- package/dist/index.cjs +1322 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +708 -142
- package/dist/index.d.ts +601 -20
- package/dist/index.js +527 -85
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-EDLMKBG2.js.map +0 -1
- package/dist/chunk-HGQ53FB3.js.map +0 -1
- /package/dist/{DiagnosticsTools-RNIDFEJK.js.map → BaichuanVideoStream-PHQG4A2L.js.map} +0 -0
|
@@ -1,67 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import {
|
|
2
|
+
BC_CLASS_MODERN_24,
|
|
3
|
+
BC_CLASS_MODERN_24_ALT,
|
|
4
|
+
BC_CMD_ID_GET_WHITE_LED,
|
|
5
|
+
BC_CMD_ID_VIDEO,
|
|
6
|
+
BaichuanVideoStream,
|
|
7
|
+
getH265NalType,
|
|
8
|
+
recordingsTraceLog,
|
|
9
|
+
splitAnnexBToNalPayloads,
|
|
10
|
+
splitAnnexBToNalPayloads2
|
|
11
|
+
} from "./chunk-W2ANCJVM.js";
|
|
7
12
|
|
|
8
13
|
// src/debug/DiagnosticsTools.ts
|
|
9
|
-
import * as
|
|
10
|
-
import * as
|
|
14
|
+
import * as fs2 from "fs";
|
|
15
|
+
import * as path2 from "path";
|
|
11
16
|
import { spawn } from "child_process";
|
|
12
17
|
|
|
13
|
-
// src/debug/DebugConfig.ts
|
|
14
|
-
import * as fs from "fs";
|
|
15
|
-
import * as path from "path";
|
|
16
|
-
function normalizeDebugOptions(opts) {
|
|
17
|
-
const general = opts?.general === true;
|
|
18
|
-
const debugRtsp = opts?.debugRtsp === true;
|
|
19
|
-
const traceNativeStream = opts?.traceNativeStream === true;
|
|
20
|
-
const traceRecordings = opts?.traceRecordings === true;
|
|
21
|
-
const traceTalk = opts?.traceTalk === true;
|
|
22
|
-
const traceEvents = opts?.traceEvents === true;
|
|
23
|
-
const dumpEnabled = opts?.dump?.enabled === true;
|
|
24
|
-
const dumpDir = opts?.dump?.dir && opts.dump.dir.trim() || path.join(process.cwd(), "test", "frames-debug");
|
|
25
|
-
const dumpBcMedia = opts?.dump?.bcmedia ?? dumpEnabled;
|
|
26
|
-
const dumpNals = opts?.dump?.nals ?? dumpEnabled;
|
|
27
|
-
return {
|
|
28
|
-
general,
|
|
29
|
-
debugRtsp,
|
|
30
|
-
traceNativeStream,
|
|
31
|
-
traceRecordings,
|
|
32
|
-
traceTalk,
|
|
33
|
-
traceEvents,
|
|
34
|
-
dumpEnabled,
|
|
35
|
-
dumpDir,
|
|
36
|
-
dumpBcMedia,
|
|
37
|
-
dumpNals
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function recordingsTraceLog(cfg, logger, tag, message) {
|
|
41
|
-
if (!cfg?.traceRecordings || !logger) return;
|
|
42
|
-
logger.log(`[${tag}] ${message}`);
|
|
43
|
-
}
|
|
44
|
-
function ensureDumpDir(cfg) {
|
|
45
|
-
if (!cfg.dumpEnabled) return;
|
|
46
|
-
fs.mkdirSync(cfg.dumpDir, { recursive: true });
|
|
47
|
-
}
|
|
48
|
-
function debugLog(cfg, logger, tag, message) {
|
|
49
|
-
if (!cfg.general) return;
|
|
50
|
-
logger.debug(`[${tag}] ${message}`);
|
|
51
|
-
}
|
|
52
|
-
function traceLog(cfg, logger, tag, message) {
|
|
53
|
-
if (!cfg.traceNativeStream) return;
|
|
54
|
-
logger.debug(`[${tag}] ${message}`);
|
|
55
|
-
}
|
|
56
|
-
function talkTraceLog(cfg, logger, tag, message) {
|
|
57
|
-
if (!cfg.traceTalk) return;
|
|
58
|
-
logger.debug(`[${tag}] ${message}`);
|
|
59
|
-
}
|
|
60
|
-
function eventTraceLog(cfg, logger, tag, message) {
|
|
61
|
-
if (!cfg.traceEvents) return;
|
|
62
|
-
logger.debug(`[${tag}] ${message}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
18
|
// src/reolink/baichuan/recordingFileName.ts
|
|
66
19
|
var FLAGS_CAM_V2 = {
|
|
67
20
|
resolution_index: [0, 7],
|
|
@@ -2339,18 +2292,18 @@ var ReolinkCgiApi = class _ReolinkCgiApi {
|
|
|
2339
2292
|
};
|
|
2340
2293
|
|
|
2341
2294
|
// src/debug/DiagnosticsTools.ts
|
|
2342
|
-
import { join as
|
|
2295
|
+
import { join as join3 } from "path";
|
|
2343
2296
|
|
|
2344
2297
|
// src/debug/zip.ts
|
|
2345
|
-
import * as
|
|
2346
|
-
import * as
|
|
2298
|
+
import * as fs from "fs";
|
|
2299
|
+
import * as path from "path";
|
|
2347
2300
|
import yazl from "yazl";
|
|
2348
2301
|
async function walkDir(rootDir, currentDir) {
|
|
2349
|
-
const entries = await
|
|
2302
|
+
const entries = await fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
2350
2303
|
const out = [];
|
|
2351
2304
|
for (const ent of entries) {
|
|
2352
|
-
const absPath =
|
|
2353
|
-
const relPath =
|
|
2305
|
+
const absPath = path.join(currentDir, ent.name);
|
|
2306
|
+
const relPath = path.relative(rootDir, absPath).replace(/\\/g, "/");
|
|
2354
2307
|
if (ent.isDirectory()) {
|
|
2355
2308
|
out.push({ absPath, relPath, isDir: true });
|
|
2356
2309
|
out.push(...await walkDir(rootDir, absPath));
|
|
@@ -2365,7 +2318,7 @@ async function walkDir(rootDir, currentDir) {
|
|
|
2365
2318
|
}
|
|
2366
2319
|
async function zipDirectory(params) {
|
|
2367
2320
|
const { sourceDir, zipPath } = params;
|
|
2368
|
-
await
|
|
2321
|
+
await fs.promises.mkdir(path.dirname(zipPath), { recursive: true });
|
|
2369
2322
|
const zipFile = new yazl.ZipFile();
|
|
2370
2323
|
const entries = await walkDir(sourceDir, sourceDir);
|
|
2371
2324
|
const dirs = entries.filter((e) => e.isDir);
|
|
@@ -2377,7 +2330,7 @@ async function zipDirectory(params) {
|
|
|
2377
2330
|
for (const f of files) {
|
|
2378
2331
|
zipFile.addFile(f.absPath, f.relPath);
|
|
2379
2332
|
}
|
|
2380
|
-
const out =
|
|
2333
|
+
const out = fs.createWriteStream(zipPath);
|
|
2381
2334
|
await new Promise((resolve, reject) => {
|
|
2382
2335
|
out.on("error", reject);
|
|
2383
2336
|
zipFile.outputStream.on("error", reject);
|
|
@@ -2387,2414 +2340,6 @@ async function zipDirectory(params) {
|
|
|
2387
2340
|
});
|
|
2388
2341
|
}
|
|
2389
2342
|
|
|
2390
|
-
// src/baichuan/stream/BaichuanVideoStream.ts
|
|
2391
|
-
import { EventEmitter } from "events";
|
|
2392
|
-
import * as fs3 from "fs";
|
|
2393
|
-
import * as path3 from "path";
|
|
2394
|
-
|
|
2395
|
-
// src/protocol/crypto.ts
|
|
2396
|
-
import { createCipheriv, createDecipheriv, createHash } from "crypto";
|
|
2397
|
-
|
|
2398
|
-
// src/protocol/constants.ts
|
|
2399
|
-
var BC_TCP_DEFAULT_PORT = 9e3;
|
|
2400
|
-
var BC_MAGIC = Buffer.from([240, 222, 188, 10]);
|
|
2401
|
-
var BC_MAGIC_REV = Buffer.from([160, 203, 237, 15]);
|
|
2402
|
-
var BC_XML_KEY = Uint8Array.from([
|
|
2403
|
-
31,
|
|
2404
|
-
45,
|
|
2405
|
-
60,
|
|
2406
|
-
75,
|
|
2407
|
-
90,
|
|
2408
|
-
105,
|
|
2409
|
-
120,
|
|
2410
|
-
255
|
|
2411
|
-
]);
|
|
2412
|
-
var BC_AES_IV = Buffer.from("0123456789abcdef", "utf8");
|
|
2413
|
-
var BC_CLASS_LEGACY = 25876;
|
|
2414
|
-
var BC_CLASS_MODERN_20 = 26132;
|
|
2415
|
-
var BC_CLASS_MODERN_24 = 25620;
|
|
2416
|
-
var BC_CLASS_MODERN_24_ALT = 0;
|
|
2417
|
-
var BC_CLASS_FILE_DOWNLOAD = 25730;
|
|
2418
|
-
function bcHeaderHasPayloadOffset(messageClass) {
|
|
2419
|
-
return messageClass === BC_CLASS_MODERN_24 || messageClass === BC_CLASS_MODERN_24_ALT || messageClass === BC_CLASS_FILE_DOWNLOAD;
|
|
2420
|
-
}
|
|
2421
|
-
var BC_CMD_ID_LOGIN = 1;
|
|
2422
|
-
var BC_CMD_ID_LOGOUT = 2;
|
|
2423
|
-
var BC_CMD_ID_VIDEO = 3;
|
|
2424
|
-
var BC_CMD_ID_VIDEO_STOP = 4;
|
|
2425
|
-
var BC_CMD_ID_FILE_INFO_LIST_REPLAY = 5;
|
|
2426
|
-
var BC_CMD_ID_FILE_INFO_LIST_STOP = 7;
|
|
2427
|
-
var BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO = 8;
|
|
2428
|
-
var BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD = 13;
|
|
2429
|
-
var BC_CMD_ID_FILE_INFO_LIST_OPEN = 14;
|
|
2430
|
-
var BC_CMD_ID_FILE_INFO_LIST_GET = 15;
|
|
2431
|
-
var BC_CMD_ID_FILE_INFO_LIST_CLOSE = 16;
|
|
2432
|
-
var BC_CMD_ID_FIND_REC_VIDEO_OPEN = 272;
|
|
2433
|
-
var BC_CMD_ID_FIND_REC_VIDEO_GET = 273;
|
|
2434
|
-
var BC_CMD_ID_FIND_REC_VIDEO_CLOSE = 274;
|
|
2435
|
-
var BC_CMD_ID_COVER_PREVIEW = 298;
|
|
2436
|
-
var BC_CMD_ID_COVER_STANDALONE_458 = 458;
|
|
2437
|
-
var BC_CMD_ID_COVER_STANDALONE_459 = 459;
|
|
2438
|
-
var BC_CMD_ID_COVER_STANDALONE_460 = 460;
|
|
2439
|
-
var BC_CMD_ID_COVER_STANDALONE_461 = 461;
|
|
2440
|
-
var BC_CMD_ID_COVER_STANDALONE_462 = 462;
|
|
2441
|
-
var BC_CMD_ID_COVER_RESPONSE = 138;
|
|
2442
|
-
var BC_CMD_ID_TALK_ABILITY = 10;
|
|
2443
|
-
var BC_CMD_ID_TALK_RESET = 11;
|
|
2444
|
-
var BC_CMD_ID_TALK_CONFIG = 201;
|
|
2445
|
-
var BC_CMD_ID_TALK = 202;
|
|
2446
|
-
var BC_CMD_ID_PTZ_CONTROL = 18;
|
|
2447
|
-
var BC_CMD_ID_PTZ_CONTROL_PRESET = 19;
|
|
2448
|
-
var BC_CMD_ID_GET_PTZ_PRESET = 190;
|
|
2449
|
-
var BC_CMD_ID_GET_PTZ_POSITION = 433;
|
|
2450
|
-
var BC_CMD_ID_GET_ZOOM_FOCUS = 294;
|
|
2451
|
-
var BC_CMD_ID_SET_ZOOM_FOCUS = 295;
|
|
2452
|
-
var BC_CMD_ID_GET_BATTERY_INFO_LIST = 252;
|
|
2453
|
-
var BC_CMD_ID_GET_BATTERY_INFO = 253;
|
|
2454
|
-
var BC_CMD_ID_UDP_KEEP_ALIVE = 234;
|
|
2455
|
-
var BC_CMD_ID_GET_PIR_INFO = 212;
|
|
2456
|
-
var BC_CMD_ID_SET_PIR_INFO = 213;
|
|
2457
|
-
var BC_CMD_ID_GET_MOTION_ALARM = 46;
|
|
2458
|
-
var BC_CMD_ID_SET_MOTION_ALARM = 47;
|
|
2459
|
-
var BC_CMD_ID_ALARM_EVENT_LIST = 33;
|
|
2460
|
-
var BC_CMD_ID_GET_AI_ALARM = 342;
|
|
2461
|
-
var BC_CMD_ID_SET_AI_ALARM = 343;
|
|
2462
|
-
var BC_CMD_ID_GET_AUDIO_ALARM = 547;
|
|
2463
|
-
var BC_CMD_ID_AUDIO_ALARM_PLAY = 263;
|
|
2464
|
-
var BC_CMD_ID_GET_WHITE_LED = 289;
|
|
2465
|
-
var BC_CMD_ID_SET_WHITE_LED_STATE = 288;
|
|
2466
|
-
var BC_CMD_ID_SET_WHITE_LED_TASK = 290;
|
|
2467
|
-
var BC_CMD_ID_FLOODLIGHT_STATUS_LIST = 291;
|
|
2468
|
-
var BC_CMD_ID_ABILITY_INFO = 151;
|
|
2469
|
-
var BC_CMD_ID_SUPPORT = 199;
|
|
2470
|
-
var BC_CMD_ID_PING = 93;
|
|
2471
|
-
var BC_CMD_ID_CHANNEL_INFO_ALL = 145;
|
|
2472
|
-
var BC_CMD_ID_GET_OSD_DATETIME = 44;
|
|
2473
|
-
var BC_CMD_ID_GET_RECORD_CFG = 54;
|
|
2474
|
-
var BC_CMD_ID_GET_ABILITY_SUPPORT = 58;
|
|
2475
|
-
var BC_CMD_ID_GET_FTP_TASK = 70;
|
|
2476
|
-
var BC_CMD_ID_GET_RECORD = 81;
|
|
2477
|
-
var BC_CMD_ID_GET_HDD_INFO_LIST = 102;
|
|
2478
|
-
var BC_CMD_ID_GET_WIFI_SIGNAL = 115;
|
|
2479
|
-
var BC_CMD_ID_GET_WIFI = 116;
|
|
2480
|
-
var BC_CMD_ID_GET_ONLINE_USER_LIST = 120;
|
|
2481
|
-
var BC_CMD_ID_GET_DAY_RECORDS = 142;
|
|
2482
|
-
var BC_CMD_ID_GET_STREAM_INFO_LIST = 146;
|
|
2483
|
-
var BC_CMD_ID_GET_LED_STATE = 208;
|
|
2484
|
-
var BC_CMD_ID_GET_EMAIL_TASK = 217;
|
|
2485
|
-
var BC_CMD_ID_GET_AUDIO_TASK = 232;
|
|
2486
|
-
var BC_CMD_ID_GET_AUDIO_CFG = 264;
|
|
2487
|
-
var BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD = 296;
|
|
2488
|
-
var BC_CMD_ID_GET_TIMELAPSE_CFG = 319;
|
|
2489
|
-
var BC_CMD_ID_GET_AI_DENOISE = 439;
|
|
2490
|
-
var BC_CMD_ID_GET_KIT_AP_CFG = 481;
|
|
2491
|
-
var BC_CMD_ID_GET_REC_ENC_CFG = 507;
|
|
2492
|
-
var BC_CMD_ID_GET_ACCESS_USER_LIST = 511;
|
|
2493
|
-
var BC_CMD_ID_GET_SLEEP_STATE = 574;
|
|
2494
|
-
var BC_CMD_ID_GET_VIDEO_INPUT = 26;
|
|
2495
|
-
var BC_CMD_ID_GET_SYSTEM_GENERAL = 104;
|
|
2496
|
-
var BC_CMD_ID_GET_SUPPORT = 199;
|
|
2497
|
-
var BC_CMD_ID_GET_AI_CFG = 299;
|
|
2498
|
-
var BC_CMD_ID_SET_AI_CFG = 300;
|
|
2499
|
-
var BC_CMD_ID_GET_SIREN_STATUS = 547;
|
|
2500
|
-
var BC_CMD_ID_SET_AUDIO_TASK = 231;
|
|
2501
|
-
var BC_CMD_ID_SET_VIDEO_INPUT = 25;
|
|
2502
|
-
var BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD = 297;
|
|
2503
|
-
var BC_CMD_ID_GET_ENC = 56;
|
|
2504
|
-
var BC_CMD_ID_SET_ENC = 57;
|
|
2505
|
-
var BC_CMD_ID_GET_PRIVACY_MASK = 52;
|
|
2506
|
-
var BC_CMD_ID_SET_PRIVACY_MASK = 53;
|
|
2507
|
-
var BC_CMD_ID_SET_AI_DENOISE = 440;
|
|
2508
|
-
var BC_CMD_ID_SET_LED_STATE = 209;
|
|
2509
|
-
var BC_CMD_ID_SET_AUDIO_CFG = 265;
|
|
2510
|
-
var BC_CMD_ID_SET_RECORD = 82;
|
|
2511
|
-
var BC_CMD_ID_SET_RECORD_CFG = 55;
|
|
2512
|
-
var BC_CMD_ID_SET_EMAIL_TASK = 216;
|
|
2513
|
-
var BC_CMD_ID_GET_PUSH_TASK = 219;
|
|
2514
|
-
var BC_CMD_ID_SET_PUSH_TASK = 218;
|
|
2515
|
-
var BC_CMD_ID_GET_AUTO_FOCUS = 224;
|
|
2516
|
-
var BC_CMD_ID_SET_AUTO_FOCUS = 225;
|
|
2517
|
-
var BC_CMD_ID_CMD_123 = 123;
|
|
2518
|
-
var BC_CMD_ID_CMD_209 = 209;
|
|
2519
|
-
var BC_CMD_ID_CMD_265 = 265;
|
|
2520
|
-
var BC_CMD_ID_CMD_440 = 440;
|
|
2521
|
-
var BC_CMD_ID_PUSH_VIDEO_INPUT = 78;
|
|
2522
|
-
var BC_CMD_ID_PUSH_SERIAL = 79;
|
|
2523
|
-
var BC_CMD_ID_PUSH_NET_INFO = 464;
|
|
2524
|
-
var BC_CMD_ID_PUSH_DINGDONG_LIST = 484;
|
|
2525
|
-
var BC_CMD_ID_PUSH_SLEEP_STATUS = 623;
|
|
2526
|
-
var BC_CMD_ID_PUSH_COORDINATE_POINT_LIST = 723;
|
|
2527
|
-
var BC_CMD_ID_DING_DONG_CTRL = 483;
|
|
2528
|
-
var BC_CMD_ID_GET_DING_DONG_LIST = 484;
|
|
2529
|
-
var BC_CMD_ID_DING_DONG_OPT = 485;
|
|
2530
|
-
var BC_CMD_ID_GET_DING_DONG_CFG = 486;
|
|
2531
|
-
var BC_CMD_ID_SET_DING_DONG_CFG = 487;
|
|
2532
|
-
var BC_CMD_ID_QUICK_REPLY_PLAY = 349;
|
|
2533
|
-
var BC_CMD_ID_GET_DING_DONG_SILENT = 609;
|
|
2534
|
-
var BC_CMD_ID_SET_DING_DONG_SILENT = 610;
|
|
2535
|
-
|
|
2536
|
-
// src/protocol/crypto.ts
|
|
2537
|
-
function md5HexUpper(input) {
|
|
2538
|
-
return createHash("md5").update(input, "utf8").digest("hex").toUpperCase();
|
|
2539
|
-
}
|
|
2540
|
-
function md5StrModern(input) {
|
|
2541
|
-
return md5HexUpper(input).slice(0, 31);
|
|
2542
|
-
}
|
|
2543
|
-
function deriveAesKey(nonce, password) {
|
|
2544
|
-
const keyStr = md5StrModern(`${nonce}-${password}`).slice(0, 16);
|
|
2545
|
-
return Buffer.from(keyStr, "utf8");
|
|
2546
|
-
}
|
|
2547
|
-
function bcEncrypt(buf, offset) {
|
|
2548
|
-
const off = offset & 255;
|
|
2549
|
-
const out = Buffer.allocUnsafe(buf.length);
|
|
2550
|
-
for (let i = 0; i < buf.length; i++) {
|
|
2551
|
-
const key = BC_XML_KEY[(off + i) % BC_XML_KEY.length];
|
|
2552
|
-
out[i] = buf[i] ^ key ^ off;
|
|
2553
|
-
}
|
|
2554
|
-
return out;
|
|
2555
|
-
}
|
|
2556
|
-
function bcDecrypt(buf, offset) {
|
|
2557
|
-
return bcEncrypt(buf, offset);
|
|
2558
|
-
}
|
|
2559
|
-
function aesEncrypt(buf, key) {
|
|
2560
|
-
if (buf.length === 0) return Buffer.alloc(0);
|
|
2561
|
-
const cipher = createCipheriv("aes-128-cfb", key, BC_AES_IV);
|
|
2562
|
-
cipher.setAutoPadding(false);
|
|
2563
|
-
return Buffer.concat([cipher.update(buf), cipher.final()]);
|
|
2564
|
-
}
|
|
2565
|
-
function aesDecrypt(buf, key) {
|
|
2566
|
-
if (buf.length === 0) return Buffer.alloc(0);
|
|
2567
|
-
const decipher = createDecipheriv("aes-128-cfb", key, BC_AES_IV);
|
|
2568
|
-
decipher.setAutoPadding(false);
|
|
2569
|
-
return Buffer.concat([decipher.update(buf), decipher.final()]);
|
|
2570
|
-
}
|
|
2571
|
-
var AesStreamDecryptor = class {
|
|
2572
|
-
decipher = null;
|
|
2573
|
-
key;
|
|
2574
|
-
constructor(key) {
|
|
2575
|
-
this.key = key;
|
|
2576
|
-
}
|
|
2577
|
-
/**
|
|
2578
|
-
* Reset the cipher state (start decrypting with fresh IV).
|
|
2579
|
-
* Call this when a new BcMedia packet is detected.
|
|
2580
|
-
*/
|
|
2581
|
-
reset() {
|
|
2582
|
-
this.decipher = createDecipheriv("aes-128-cfb", this.key, BC_AES_IV);
|
|
2583
|
-
this.decipher.setAutoPadding(false);
|
|
2584
|
-
}
|
|
2585
|
-
/**
|
|
2586
|
-
* Decrypt a chunk using the current cipher state.
|
|
2587
|
-
* Automatically resets if no cipher exists.
|
|
2588
|
-
*
|
|
2589
|
-
* @param buf - Encrypted buffer to decrypt
|
|
2590
|
-
* @returns Decrypted buffer
|
|
2591
|
-
*/
|
|
2592
|
-
update(buf) {
|
|
2593
|
-
if (buf.length === 0) return Buffer.alloc(0);
|
|
2594
|
-
if (!this.decipher) this.reset();
|
|
2595
|
-
return this.decipher.update(buf);
|
|
2596
|
-
}
|
|
2597
|
-
/**
|
|
2598
|
-
* Check if the decryptor has been initialized.
|
|
2599
|
-
*/
|
|
2600
|
-
isInitialized() {
|
|
2601
|
-
return this.decipher !== null;
|
|
2602
|
-
}
|
|
2603
|
-
};
|
|
2604
|
-
|
|
2605
|
-
// src/baichuan/stream/BcMediaParser.ts
|
|
2606
|
-
var MAGIC_INFO_V1 = 825241649;
|
|
2607
|
-
var MAGIC_INFO_V2 = 842018865;
|
|
2608
|
-
var MAGIC_IFRAME_START = 1667510320;
|
|
2609
|
-
var MAGIC_IFRAME_END = 1667510329;
|
|
2610
|
-
var MAGIC_PFRAME_START = 1667510576;
|
|
2611
|
-
var MAGIC_PFRAME_END = 1667510585;
|
|
2612
|
-
var MAGIC_AAC = 1651979568;
|
|
2613
|
-
var MAGIC_ADPCM = 1651978544;
|
|
2614
|
-
var PAD_SIZE = 8;
|
|
2615
|
-
function parseBcMedia(buf) {
|
|
2616
|
-
if (buf.length < 4) return null;
|
|
2617
|
-
const magic = buf.readUInt32LE(0);
|
|
2618
|
-
if (magic === MAGIC_INFO_V1) {
|
|
2619
|
-
return parseInfoV1(buf);
|
|
2620
|
-
} else if (magic === MAGIC_INFO_V2) {
|
|
2621
|
-
return parseInfoV2(buf);
|
|
2622
|
-
} else if (magic >= MAGIC_IFRAME_START && magic <= MAGIC_IFRAME_END) {
|
|
2623
|
-
return parseIframe(buf);
|
|
2624
|
-
} else if (magic >= MAGIC_PFRAME_START && magic <= MAGIC_PFRAME_END) {
|
|
2625
|
-
return parsePframe(buf);
|
|
2626
|
-
} else if (magic === MAGIC_AAC) {
|
|
2627
|
-
return parseAac(buf);
|
|
2628
|
-
} else if (magic === MAGIC_ADPCM) {
|
|
2629
|
-
return parseAdpcm(buf);
|
|
2630
|
-
}
|
|
2631
|
-
return null;
|
|
2632
|
-
}
|
|
2633
|
-
function parseInfoV1(buf) {
|
|
2634
|
-
if (buf.length < 32) return null;
|
|
2635
|
-
const headerSize = buf.readUInt32LE(4);
|
|
2636
|
-
if (headerSize !== 32) return null;
|
|
2637
|
-
const media = {
|
|
2638
|
-
type: "InfoV1",
|
|
2639
|
-
videoWidth: buf.readUInt32LE(8),
|
|
2640
|
-
videoHeight: buf.readUInt32LE(12),
|
|
2641
|
-
fps: buf.readUInt8(17),
|
|
2642
|
-
startYear: buf.readUInt8(18),
|
|
2643
|
-
startMonth: buf.readUInt8(19),
|
|
2644
|
-
startDay: buf.readUInt8(20),
|
|
2645
|
-
startHour: buf.readUInt8(21),
|
|
2646
|
-
startMin: buf.readUInt8(22),
|
|
2647
|
-
startSeconds: buf.readUInt8(23),
|
|
2648
|
-
endYear: buf.readUInt8(24),
|
|
2649
|
-
endMonth: buf.readUInt8(25),
|
|
2650
|
-
endDay: buf.readUInt8(26),
|
|
2651
|
-
endHour: buf.readUInt8(27),
|
|
2652
|
-
endMin: buf.readUInt8(28),
|
|
2653
|
-
endSeconds: buf.readUInt8(29)
|
|
2654
|
-
};
|
|
2655
|
-
return { media, consumed: 32 };
|
|
2656
|
-
}
|
|
2657
|
-
function parseInfoV2(buf) {
|
|
2658
|
-
if (buf.length < 32) return null;
|
|
2659
|
-
const headerSize = buf.readUInt32LE(4);
|
|
2660
|
-
if (headerSize !== 32) return null;
|
|
2661
|
-
const media = {
|
|
2662
|
-
type: "InfoV2",
|
|
2663
|
-
videoWidth: buf.readUInt32LE(8),
|
|
2664
|
-
videoHeight: buf.readUInt32LE(12),
|
|
2665
|
-
fps: buf.readUInt8(17),
|
|
2666
|
-
startYear: buf.readUInt8(18),
|
|
2667
|
-
startMonth: buf.readUInt8(19),
|
|
2668
|
-
startDay: buf.readUInt8(20),
|
|
2669
|
-
startHour: buf.readUInt8(21),
|
|
2670
|
-
startMin: buf.readUInt8(22),
|
|
2671
|
-
startSeconds: buf.readUInt8(23),
|
|
2672
|
-
endYear: buf.readUInt8(24),
|
|
2673
|
-
endMonth: buf.readUInt8(25),
|
|
2674
|
-
endDay: buf.readUInt8(26),
|
|
2675
|
-
endHour: buf.readUInt8(27),
|
|
2676
|
-
endMin: buf.readUInt8(28),
|
|
2677
|
-
endSeconds: buf.readUInt8(29)
|
|
2678
|
-
};
|
|
2679
|
-
return { media, consumed: 32 };
|
|
2680
|
-
}
|
|
2681
|
-
function parseIframe(buf) {
|
|
2682
|
-
if (buf.length < 20) return null;
|
|
2683
|
-
const videoTypeStr = buf.toString("utf8", 4, 8);
|
|
2684
|
-
if (videoTypeStr !== "H264" && videoTypeStr !== "H265") return null;
|
|
2685
|
-
const videoType = videoTypeStr;
|
|
2686
|
-
const payloadSize = buf.readUInt32LE(8);
|
|
2687
|
-
const additionalHeaderSize = buf.readUInt32LE(12);
|
|
2688
|
-
const microseconds = buf.readUInt32LE(16);
|
|
2689
|
-
let offset = 20;
|
|
2690
|
-
const unknown = buf.readUInt32LE(offset);
|
|
2691
|
-
offset += 4;
|
|
2692
|
-
let time;
|
|
2693
|
-
if (buf.length < offset + additionalHeaderSize) return null;
|
|
2694
|
-
const additionalHeader = buf.subarray(offset, offset + additionalHeaderSize);
|
|
2695
|
-
if (additionalHeaderSize >= 4) {
|
|
2696
|
-
time = additionalHeader.readUInt32LE(0);
|
|
2697
|
-
}
|
|
2698
|
-
offset += additionalHeaderSize;
|
|
2699
|
-
if (buf.length < offset + payloadSize) return null;
|
|
2700
|
-
const data = buf.subarray(offset, offset + payloadSize);
|
|
2701
|
-
offset += payloadSize;
|
|
2702
|
-
const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
|
|
2703
|
-
if (buf.length < offset + padSize) return null;
|
|
2704
|
-
offset += padSize;
|
|
2705
|
-
const media = {
|
|
2706
|
-
type: "Iframe",
|
|
2707
|
-
videoType,
|
|
2708
|
-
microseconds,
|
|
2709
|
-
...time !== void 0 ? { time } : {},
|
|
2710
|
-
additionalHeader,
|
|
2711
|
-
additionalHeaderSize,
|
|
2712
|
-
unknown,
|
|
2713
|
-
data
|
|
2714
|
-
};
|
|
2715
|
-
return { media, consumed: offset };
|
|
2716
|
-
}
|
|
2717
|
-
function parsePframe(buf) {
|
|
2718
|
-
if (buf.length < 20) return null;
|
|
2719
|
-
const videoTypeStr = buf.toString("utf8", 4, 8);
|
|
2720
|
-
if (videoTypeStr !== "H264" && videoTypeStr !== "H265") return null;
|
|
2721
|
-
const videoType = videoTypeStr;
|
|
2722
|
-
const payloadSize = buf.readUInt32LE(8);
|
|
2723
|
-
const additionalHeaderSize = buf.readUInt32LE(12);
|
|
2724
|
-
const microseconds = buf.readUInt32LE(16);
|
|
2725
|
-
let offset = 20;
|
|
2726
|
-
const unknown = buf.readUInt32LE(offset);
|
|
2727
|
-
offset += 4;
|
|
2728
|
-
if (buf.length < offset + additionalHeaderSize) return null;
|
|
2729
|
-
const additionalHeader = buf.subarray(offset, offset + additionalHeaderSize);
|
|
2730
|
-
offset += additionalHeaderSize;
|
|
2731
|
-
if (buf.length < offset + payloadSize) return null;
|
|
2732
|
-
const data = buf.subarray(offset, offset + payloadSize);
|
|
2733
|
-
offset += payloadSize;
|
|
2734
|
-
const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
|
|
2735
|
-
if (buf.length < offset + padSize) return null;
|
|
2736
|
-
offset += padSize;
|
|
2737
|
-
const media = {
|
|
2738
|
-
type: "Pframe",
|
|
2739
|
-
videoType,
|
|
2740
|
-
microseconds,
|
|
2741
|
-
additionalHeader,
|
|
2742
|
-
additionalHeaderSize,
|
|
2743
|
-
unknown,
|
|
2744
|
-
data
|
|
2745
|
-
};
|
|
2746
|
-
return { media, consumed: offset };
|
|
2747
|
-
}
|
|
2748
|
-
function parseAac(buf) {
|
|
2749
|
-
if (buf.length < 12) return null;
|
|
2750
|
-
const payloadSize = buf.readUInt16LE(4);
|
|
2751
|
-
const payloadSizeB = buf.readUInt16LE(6);
|
|
2752
|
-
if (payloadSize !== payloadSizeB) return null;
|
|
2753
|
-
const headerLen = 8;
|
|
2754
|
-
const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
|
|
2755
|
-
const totalLen = headerLen + payloadSize + padSize;
|
|
2756
|
-
if (buf.length < totalLen) return null;
|
|
2757
|
-
const data = buf.subarray(headerLen, headerLen + payloadSize);
|
|
2758
|
-
const media = {
|
|
2759
|
-
type: "Aac",
|
|
2760
|
-
data
|
|
2761
|
-
};
|
|
2762
|
-
return { media, consumed: totalLen };
|
|
2763
|
-
}
|
|
2764
|
-
function parseAdpcm(buf) {
|
|
2765
|
-
if (buf.length < 12) return null;
|
|
2766
|
-
const payloadSize = buf.readUInt16LE(4);
|
|
2767
|
-
const payloadSizeB = buf.readUInt16LE(6);
|
|
2768
|
-
if (payloadSize !== payloadSizeB) return null;
|
|
2769
|
-
const magicData = buf.readUInt16LE(8);
|
|
2770
|
-
if (magicData !== 256) return null;
|
|
2771
|
-
const halfBlockSize = buf.readUInt16LE(10);
|
|
2772
|
-
void halfBlockSize;
|
|
2773
|
-
const subHeaderSize = 4;
|
|
2774
|
-
if (payloadSize < subHeaderSize) return null;
|
|
2775
|
-
const blockSize = payloadSize - subHeaderSize;
|
|
2776
|
-
const headerLen = 12;
|
|
2777
|
-
const padSize = payloadSize % PAD_SIZE === 0 ? 0 : PAD_SIZE - payloadSize % PAD_SIZE;
|
|
2778
|
-
const totalLen = headerLen + blockSize + padSize;
|
|
2779
|
-
if (buf.length < totalLen) return null;
|
|
2780
|
-
const data = buf.subarray(headerLen, headerLen + blockSize);
|
|
2781
|
-
const media = {
|
|
2782
|
-
type: "Adpcm",
|
|
2783
|
-
data
|
|
2784
|
-
};
|
|
2785
|
-
return { media, consumed: totalLen };
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
// src/baichuan/stream/BcMediaCodec.ts
|
|
2789
|
-
var BcMediaCodec = class {
|
|
2790
|
-
buffer = Buffer.alloc(0);
|
|
2791
|
-
strict;
|
|
2792
|
-
amountSkipped = 0;
|
|
2793
|
-
logger;
|
|
2794
|
-
constructor(strict = false, logger) {
|
|
2795
|
-
this.strict = strict;
|
|
2796
|
-
this.logger = logger;
|
|
2797
|
-
}
|
|
2798
|
-
/**
|
|
2799
|
-
* Push data into the codec buffer and try to parse complete BcMedia packets.
|
|
2800
|
-
* Returns an array of complete BcMedia packets found.
|
|
2801
|
-
*
|
|
2802
|
-
* @param chunk - New data chunk to add to buffer
|
|
2803
|
-
* @returns Array of complete BcMedia packets (empty if none complete yet)
|
|
2804
|
-
*/
|
|
2805
|
-
decode(chunk) {
|
|
2806
|
-
this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk]);
|
|
2807
|
-
const results = [];
|
|
2808
|
-
while (this.buffer.length >= 4) {
|
|
2809
|
-
const result = parseBcMedia(this.buffer);
|
|
2810
|
-
if (result) {
|
|
2811
|
-
if (this.amountSkipped > 0) {
|
|
2812
|
-
if (this.strict) {
|
|
2813
|
-
this.logger?.warn(`[BcMediaCodec] Recovered stream after skipping ${this.amountSkipped} bytes`);
|
|
2814
|
-
} else {
|
|
2815
|
-
this.logger?.warn(`[BcMediaCodec] Recovered stream after skipping ${this.amountSkipped} bytes`);
|
|
2816
|
-
}
|
|
2817
|
-
this.amountSkipped = 0;
|
|
2818
|
-
}
|
|
2819
|
-
results.push(result.media);
|
|
2820
|
-
this.buffer = this.buffer.subarray(result.consumed);
|
|
2821
|
-
} else {
|
|
2822
|
-
const isKnownMagic = (magic2) => {
|
|
2823
|
-
const isInfoV1 = magic2 === 825241649;
|
|
2824
|
-
const isInfoV2 = magic2 === 842018865;
|
|
2825
|
-
const isIFrame = magic2 >= 1667510320 && magic2 <= 1667510329;
|
|
2826
|
-
const isPFrame = magic2 >= 1667510576 && magic2 <= 1667510585;
|
|
2827
|
-
const isAac = magic2 === 1651979568;
|
|
2828
|
-
const isAdpcm = magic2 === 1651978544;
|
|
2829
|
-
return isInfoV1 || isInfoV2 || isIFrame || isPFrame || isAac || isAdpcm;
|
|
2830
|
-
};
|
|
2831
|
-
const magic = this.buffer.readUInt32LE(0);
|
|
2832
|
-
const startsWithKnownMagic = isKnownMagic(magic);
|
|
2833
|
-
if (startsWithKnownMagic) {
|
|
2834
|
-
break;
|
|
2835
|
-
}
|
|
2836
|
-
if (this.strict) {
|
|
2837
|
-
throw new Error(`[BcMediaCodec] Invalid data in stream (no valid magic at buffer start, len=${this.buffer.length})`);
|
|
2838
|
-
}
|
|
2839
|
-
if (this.amountSkipped === 0) {
|
|
2840
|
-
this.logger?.warn(`[BcMediaCodec] Error in stream, attempting to recover...`);
|
|
2841
|
-
}
|
|
2842
|
-
let next = -1;
|
|
2843
|
-
for (const off of [528, 1056, 1584]) {
|
|
2844
|
-
if (this.buffer.length >= off + 4 && isKnownMagic(this.buffer.readUInt32LE(off))) {
|
|
2845
|
-
next = off;
|
|
2846
|
-
break;
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
if (next < 0) {
|
|
2850
|
-
for (let i = 1; i <= this.buffer.length - 4; i++) {
|
|
2851
|
-
if (isKnownMagic(this.buffer.readUInt32LE(i))) {
|
|
2852
|
-
next = i;
|
|
2853
|
-
break;
|
|
2854
|
-
}
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
2857
|
-
if (next > 0) {
|
|
2858
|
-
this.amountSkipped += next;
|
|
2859
|
-
this.buffer = this.buffer.subarray(next);
|
|
2860
|
-
continue;
|
|
2861
|
-
}
|
|
2862
|
-
if (this.buffer.length > 3) {
|
|
2863
|
-
const keep = 3;
|
|
2864
|
-
this.amountSkipped += this.buffer.length - keep;
|
|
2865
|
-
this.buffer = this.buffer.subarray(this.buffer.length - keep);
|
|
2866
|
-
}
|
|
2867
|
-
break;
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
return results;
|
|
2871
|
-
}
|
|
2872
|
-
/**
|
|
2873
|
-
* Get remaining buffer (for debugging)
|
|
2874
|
-
*/
|
|
2875
|
-
getRemainingBuffer() {
|
|
2876
|
-
return this.buffer;
|
|
2877
|
-
}
|
|
2878
|
-
/**
|
|
2879
|
-
* Clear the buffer (useful for resetting the codec)
|
|
2880
|
-
*/
|
|
2881
|
-
clear() {
|
|
2882
|
-
this.buffer = Buffer.alloc(0);
|
|
2883
|
-
this.amountSkipped = 0;
|
|
2884
|
-
}
|
|
2885
|
-
};
|
|
2886
|
-
|
|
2887
|
-
// src/baichuan/stream/H264Converter.ts
|
|
2888
|
-
var NAL_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
|
|
2889
|
-
var NAL_START_CODE_3B = Buffer.from([0, 0, 1]);
|
|
2890
|
-
function hasStartCodes(data) {
|
|
2891
|
-
if (data.length < 4) return false;
|
|
2892
|
-
if (data.subarray(0, 4).equals(NAL_START_CODE_4B)) return true;
|
|
2893
|
-
if (data.subarray(0, 3).equals(NAL_START_CODE_3B)) return true;
|
|
2894
|
-
return false;
|
|
2895
|
-
}
|
|
2896
|
-
function tryConvertWithLengthReader(data, readLen) {
|
|
2897
|
-
const result = [];
|
|
2898
|
-
let offset = 0;
|
|
2899
|
-
let nalCount = 0;
|
|
2900
|
-
while (offset < data.length) {
|
|
2901
|
-
if (offset + 4 > data.length) return null;
|
|
2902
|
-
const nalLength = readLen(data, offset);
|
|
2903
|
-
offset += 4;
|
|
2904
|
-
if (nalLength <= 0) return null;
|
|
2905
|
-
if (nalLength > data.length - offset) return null;
|
|
2906
|
-
result.push(NAL_START_CODE_4B);
|
|
2907
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
2908
|
-
offset += nalLength;
|
|
2909
|
-
nalCount++;
|
|
2910
|
-
}
|
|
2911
|
-
if (nalCount === 0) return null;
|
|
2912
|
-
return Buffer.concat(result);
|
|
2913
|
-
}
|
|
2914
|
-
function tryConvertWithLengthReader16(data, readLen) {
|
|
2915
|
-
const result = [];
|
|
2916
|
-
let offset = 0;
|
|
2917
|
-
let nalCount = 0;
|
|
2918
|
-
while (offset < data.length) {
|
|
2919
|
-
if (offset + 2 > data.length) return null;
|
|
2920
|
-
const nalLength = readLen(data, offset);
|
|
2921
|
-
offset += 2;
|
|
2922
|
-
if (nalLength <= 0) return null;
|
|
2923
|
-
if (nalLength > data.length - offset) return null;
|
|
2924
|
-
result.push(NAL_START_CODE_4B);
|
|
2925
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
2926
|
-
offset += nalLength;
|
|
2927
|
-
nalCount++;
|
|
2928
|
-
}
|
|
2929
|
-
if (nalCount === 0) return null;
|
|
2930
|
-
return Buffer.concat(result);
|
|
2931
|
-
}
|
|
2932
|
-
function tryConvertWithLengthReader24(data, endian) {
|
|
2933
|
-
const result = [];
|
|
2934
|
-
let offset = 0;
|
|
2935
|
-
let nalCount = 0;
|
|
2936
|
-
const readLen24 = (buf, at) => {
|
|
2937
|
-
if (at + 3 > buf.length) return 0;
|
|
2938
|
-
const b0 = buf[at];
|
|
2939
|
-
const b1 = buf[at + 1];
|
|
2940
|
-
const b2 = buf[at + 2];
|
|
2941
|
-
return endian === "be" ? (b0 << 16 | b1 << 8 | b2) >>> 0 : (b2 << 16 | b1 << 8 | b0) >>> 0;
|
|
2942
|
-
};
|
|
2943
|
-
while (offset < data.length) {
|
|
2944
|
-
if (offset + 3 > data.length) return null;
|
|
2945
|
-
const nalLength = readLen24(data, offset);
|
|
2946
|
-
offset += 3;
|
|
2947
|
-
if (nalLength <= 0) return null;
|
|
2948
|
-
if (nalLength > data.length - offset) return null;
|
|
2949
|
-
result.push(NAL_START_CODE_4B);
|
|
2950
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
2951
|
-
offset += nalLength;
|
|
2952
|
-
nalCount++;
|
|
2953
|
-
}
|
|
2954
|
-
if (nalCount === 0) return null;
|
|
2955
|
-
return Buffer.concat(result);
|
|
2956
|
-
}
|
|
2957
|
-
function looksLikeSingleH264Nal(nalPayload) {
|
|
2958
|
-
if (nalPayload.length < 1) return false;
|
|
2959
|
-
const b0 = nalPayload[0];
|
|
2960
|
-
if (b0 === void 0) return false;
|
|
2961
|
-
if ((b0 & 128) !== 0) return false;
|
|
2962
|
-
const nalType = b0 & 31;
|
|
2963
|
-
return nalType >= 1 && nalType <= 23;
|
|
2964
|
-
}
|
|
2965
|
-
function depacketizeRtpAggregationToAnnexB(payload) {
|
|
2966
|
-
if (payload.length < 1) return null;
|
|
2967
|
-
const nalHeader = payload[0];
|
|
2968
|
-
const nalType = nalHeader & 31;
|
|
2969
|
-
const out = [];
|
|
2970
|
-
const pushNal = (nal) => {
|
|
2971
|
-
if (nal.length === 0) return;
|
|
2972
|
-
out.push(NAL_START_CODE_4B, nal);
|
|
2973
|
-
};
|
|
2974
|
-
if (nalType === 24) {
|
|
2975
|
-
let off = 1;
|
|
2976
|
-
while (off + 2 <= payload.length) {
|
|
2977
|
-
const size = payload.readUInt16BE(off);
|
|
2978
|
-
off += 2;
|
|
2979
|
-
if (size <= 0 || off + size > payload.length) return null;
|
|
2980
|
-
pushNal(payload.subarray(off, off + size));
|
|
2981
|
-
off += size;
|
|
2982
|
-
}
|
|
2983
|
-
return out.length ? Buffer.concat(out) : null;
|
|
2984
|
-
}
|
|
2985
|
-
if (nalType === 25) {
|
|
2986
|
-
let off = 1 + 2;
|
|
2987
|
-
if (off > payload.length) return null;
|
|
2988
|
-
while (off + 2 <= payload.length) {
|
|
2989
|
-
const size = payload.readUInt16BE(off);
|
|
2990
|
-
off += 2;
|
|
2991
|
-
if (size <= 0 || off + size > payload.length) return null;
|
|
2992
|
-
pushNal(payload.subarray(off, off + size));
|
|
2993
|
-
off += size;
|
|
2994
|
-
}
|
|
2995
|
-
return out.length ? Buffer.concat(out) : null;
|
|
2996
|
-
}
|
|
2997
|
-
if (nalType === 26) {
|
|
2998
|
-
let off = 1 + 2;
|
|
2999
|
-
if (off > payload.length) return null;
|
|
3000
|
-
while (off + 2 <= payload.length) {
|
|
3001
|
-
const size = payload.readUInt16BE(off);
|
|
3002
|
-
off += 2;
|
|
3003
|
-
if (off + 1 + 2 > payload.length) return null;
|
|
3004
|
-
off += 1;
|
|
3005
|
-
off += 2;
|
|
3006
|
-
if (size <= 0 || off + size > payload.length) return null;
|
|
3007
|
-
pushNal(payload.subarray(off, off + size));
|
|
3008
|
-
off += size;
|
|
3009
|
-
}
|
|
3010
|
-
return out.length ? Buffer.concat(out) : null;
|
|
3011
|
-
}
|
|
3012
|
-
if (nalType === 27) {
|
|
3013
|
-
let off = 1 + 2;
|
|
3014
|
-
if (off > payload.length) return null;
|
|
3015
|
-
while (off + 2 <= payload.length) {
|
|
3016
|
-
const size = payload.readUInt16BE(off);
|
|
3017
|
-
off += 2;
|
|
3018
|
-
if (off + 1 + 3 > payload.length) return null;
|
|
3019
|
-
off += 1;
|
|
3020
|
-
off += 3;
|
|
3021
|
-
if (size <= 0 || off + size > payload.length) return null;
|
|
3022
|
-
pushNal(payload.subarray(off, off + size));
|
|
3023
|
-
off += size;
|
|
3024
|
-
}
|
|
3025
|
-
return out.length ? Buffer.concat(out) : null;
|
|
3026
|
-
}
|
|
3027
|
-
return null;
|
|
3028
|
-
}
|
|
3029
|
-
function convertToAnnexB(data) {
|
|
3030
|
-
if (hasStartCodes(data)) {
|
|
3031
|
-
return data;
|
|
3032
|
-
}
|
|
3033
|
-
const sc4 = Buffer.from([0, 0, 0, 1]);
|
|
3034
|
-
const sc3 = Buffer.from([0, 0, 1]);
|
|
3035
|
-
const maxScan = Math.min(64, data.length);
|
|
3036
|
-
const idx4 = data.subarray(0, maxScan).indexOf(sc4);
|
|
3037
|
-
if (idx4 > 0) return data.subarray(idx4);
|
|
3038
|
-
const idx3 = data.subarray(0, maxScan).indexOf(sc3);
|
|
3039
|
-
if (idx3 > 0) return data.subarray(idx3);
|
|
3040
|
-
const be = tryConvertWithLengthReader(data, (b, o) => b.readUInt32BE(o));
|
|
3041
|
-
if (be) return be;
|
|
3042
|
-
const le = tryConvertWithLengthReader(data, (b, o) => b.readUInt32LE(o));
|
|
3043
|
-
if (le) return le;
|
|
3044
|
-
const be24 = tryConvertWithLengthReader24(data, "be");
|
|
3045
|
-
if (be24) return be24;
|
|
3046
|
-
const le24 = tryConvertWithLengthReader24(data, "le");
|
|
3047
|
-
if (le24) return le24;
|
|
3048
|
-
const be16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16BE(o));
|
|
3049
|
-
if (be16) return be16;
|
|
3050
|
-
const le16 = tryConvertWithLengthReader16(data, (b, o) => b.readUInt16LE(o));
|
|
3051
|
-
if (le16) return le16;
|
|
3052
|
-
const agg = depacketizeRtpAggregationToAnnexB(data);
|
|
3053
|
-
if (agg) return agg;
|
|
3054
|
-
if (looksLikeSingleH264Nal(data)) {
|
|
3055
|
-
return Buffer.concat([NAL_START_CODE_4B, data]);
|
|
3056
|
-
}
|
|
3057
|
-
return data;
|
|
3058
|
-
}
|
|
3059
|
-
function splitAnnexBToNalPayloads(annexB) {
|
|
3060
|
-
const starts = [];
|
|
3061
|
-
for (let i = 0; i < annexB.length - 3; i++) {
|
|
3062
|
-
if (annexB[i] === 0 && annexB[i + 1] === 0) {
|
|
3063
|
-
if (annexB[i + 2] === 1) {
|
|
3064
|
-
starts.push({ idx: i, len: 3 });
|
|
3065
|
-
i += 2;
|
|
3066
|
-
} else if (annexB[i + 2] === 0 && annexB[i + 3] === 1) {
|
|
3067
|
-
starts.push({ idx: i, len: 4 });
|
|
3068
|
-
i += 3;
|
|
3069
|
-
}
|
|
3070
|
-
}
|
|
3071
|
-
}
|
|
3072
|
-
if (starts.length === 0) return [];
|
|
3073
|
-
const out = [];
|
|
3074
|
-
for (let s = 0; s < starts.length; s++) {
|
|
3075
|
-
const st = starts[s];
|
|
3076
|
-
const start = st.idx + st.len;
|
|
3077
|
-
const end = starts[s + 1] ? starts[s + 1].idx : annexB.length;
|
|
3078
|
-
if (end > start) out.push(annexB.subarray(start, end));
|
|
3079
|
-
}
|
|
3080
|
-
return out;
|
|
3081
|
-
}
|
|
3082
|
-
function isValidH264AnnexBAccessUnit(annexB) {
|
|
3083
|
-
if (!hasStartCodes(annexB)) return false;
|
|
3084
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
3085
|
-
if (nals.length === 0) return false;
|
|
3086
|
-
for (const nal of nals) {
|
|
3087
|
-
if (nal.length < 1) return false;
|
|
3088
|
-
const b0 = nal[0];
|
|
3089
|
-
if (b0 === void 0) return false;
|
|
3090
|
-
if ((b0 & 128) !== 0) return false;
|
|
3091
|
-
const nalType = b0 & 31;
|
|
3092
|
-
if (nalType === 0 || nalType >= 24) return false;
|
|
3093
|
-
}
|
|
3094
|
-
return true;
|
|
3095
|
-
}
|
|
3096
|
-
function isH264KeyframeAnnexB(annexB) {
|
|
3097
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
3098
|
-
let hasSps = false;
|
|
3099
|
-
let hasPps = false;
|
|
3100
|
-
let hasIdr = false;
|
|
3101
|
-
for (const nal of nals) {
|
|
3102
|
-
const t = (nal[0] ?? 0) & 31;
|
|
3103
|
-
if (t === 7) hasSps = true;
|
|
3104
|
-
if (t === 8) hasPps = true;
|
|
3105
|
-
if (t === 5) hasIdr = true;
|
|
3106
|
-
}
|
|
3107
|
-
return hasIdr && hasSps && hasPps;
|
|
3108
|
-
}
|
|
3109
|
-
var H264RtpDepacketizer = class _H264RtpDepacketizer {
|
|
3110
|
-
fuNalHeader = null;
|
|
3111
|
-
fuParts = [];
|
|
3112
|
-
static parseRtpPayload(packet) {
|
|
3113
|
-
if (!packet || packet.length < 12) return null;
|
|
3114
|
-
const version = packet[0] >> 6 & 3;
|
|
3115
|
-
if (version !== 2) return null;
|
|
3116
|
-
const padding = (packet[0] & 32) !== 0;
|
|
3117
|
-
const extension = (packet[0] & 16) !== 0;
|
|
3118
|
-
const csrcCount = packet[0] & 15;
|
|
3119
|
-
let offset = 12 + csrcCount * 4;
|
|
3120
|
-
if (offset > packet.length) return null;
|
|
3121
|
-
if (extension) {
|
|
3122
|
-
if (offset + 4 > packet.length) return null;
|
|
3123
|
-
const extLenWords = packet.readUInt16BE(offset + 2);
|
|
3124
|
-
offset += 4 + extLenWords * 4;
|
|
3125
|
-
if (offset > packet.length) return null;
|
|
3126
|
-
}
|
|
3127
|
-
let end = packet.length;
|
|
3128
|
-
if (padding) {
|
|
3129
|
-
const padLen = packet[packet.length - 1];
|
|
3130
|
-
if (padLen <= 0 || padLen > packet.length) return null;
|
|
3131
|
-
end = packet.length - padLen;
|
|
3132
|
-
if (end < offset) return null;
|
|
3133
|
-
}
|
|
3134
|
-
if (end <= offset) return null;
|
|
3135
|
-
return packet.subarray(offset, end);
|
|
3136
|
-
}
|
|
3137
|
-
reset() {
|
|
3138
|
-
this.fuNalHeader = null;
|
|
3139
|
-
this.fuParts = [];
|
|
3140
|
-
}
|
|
3141
|
-
push(payload) {
|
|
3142
|
-
if (payload.length === 0) return [];
|
|
3143
|
-
const rtpPayload = _H264RtpDepacketizer.parseRtpPayload(payload);
|
|
3144
|
-
if (rtpPayload) payload = rtpPayload;
|
|
3145
|
-
if (hasStartCodes(payload)) return [payload];
|
|
3146
|
-
const b0 = payload[0];
|
|
3147
|
-
if ((b0 & 128) !== 0) return [];
|
|
3148
|
-
const nalType = b0 & 31;
|
|
3149
|
-
if (nalType >= 1 && nalType <= 23) {
|
|
3150
|
-
return [Buffer.concat([NAL_START_CODE_4B, payload])];
|
|
3151
|
-
}
|
|
3152
|
-
if (nalType === 24) {
|
|
3153
|
-
if (payload.length < 1 + 2) return [];
|
|
3154
|
-
let off = 1;
|
|
3155
|
-
const out = [];
|
|
3156
|
-
while (off + 2 <= payload.length) {
|
|
3157
|
-
const size = payload.readUInt16BE(off);
|
|
3158
|
-
off += 2;
|
|
3159
|
-
if (size <= 0 || off + size > payload.length) return [];
|
|
3160
|
-
const nal = payload.subarray(off, off + size);
|
|
3161
|
-
off += size;
|
|
3162
|
-
if (nal.length < 1) return [];
|
|
3163
|
-
if ((nal[0] & 128) !== 0) return [];
|
|
3164
|
-
const t = nal[0] & 31;
|
|
3165
|
-
if (t === 0 || t >= 24) return [];
|
|
3166
|
-
out.push(Buffer.concat([NAL_START_CODE_4B, nal]));
|
|
3167
|
-
}
|
|
3168
|
-
return out;
|
|
3169
|
-
}
|
|
3170
|
-
if (nalType === 28 || nalType === 29) {
|
|
3171
|
-
if (payload.length < 2) return [];
|
|
3172
|
-
const fuIndicator = payload[0];
|
|
3173
|
-
const fuHeader = payload[1];
|
|
3174
|
-
const start = (fuHeader & 128) !== 0;
|
|
3175
|
-
const end = (fuHeader & 64) !== 0;
|
|
3176
|
-
const origType = fuHeader & 31;
|
|
3177
|
-
const reconstructedHeader = fuIndicator & 224 | origType;
|
|
3178
|
-
let off = 2;
|
|
3179
|
-
if (nalType === 29) {
|
|
3180
|
-
if (payload.length < off + 2) return [];
|
|
3181
|
-
off += 2;
|
|
3182
|
-
}
|
|
3183
|
-
const frag = payload.subarray(off);
|
|
3184
|
-
if (start) {
|
|
3185
|
-
this.fuNalHeader = reconstructedHeader;
|
|
3186
|
-
this.fuParts = [frag];
|
|
3187
|
-
} else if (this.fuNalHeader != null) {
|
|
3188
|
-
this.fuParts.push(frag);
|
|
3189
|
-
} else {
|
|
3190
|
-
return [];
|
|
3191
|
-
}
|
|
3192
|
-
if (end && this.fuNalHeader != null) {
|
|
3193
|
-
const nal = Buffer.concat([Buffer.from([this.fuNalHeader]), ...this.fuParts]);
|
|
3194
|
-
this.reset();
|
|
3195
|
-
return [Buffer.concat([NAL_START_CODE_4B, nal])];
|
|
3196
|
-
}
|
|
3197
|
-
return [];
|
|
3198
|
-
}
|
|
3199
|
-
return [];
|
|
3200
|
-
}
|
|
3201
|
-
};
|
|
3202
|
-
function convertToLengthPrefixed(data) {
|
|
3203
|
-
const result = [];
|
|
3204
|
-
let offset = 0;
|
|
3205
|
-
while (offset < data.length) {
|
|
3206
|
-
let startCodeOffset = -1;
|
|
3207
|
-
let startCodeLength = 0;
|
|
3208
|
-
if (offset + 4 <= data.length && data.subarray(offset, offset + 4).equals(NAL_START_CODE_4B)) {
|
|
3209
|
-
startCodeOffset = offset;
|
|
3210
|
-
startCodeLength = 4;
|
|
3211
|
-
} else if (offset + 3 <= data.length && data.subarray(offset, offset + 3).equals(NAL_START_CODE_3B)) {
|
|
3212
|
-
startCodeOffset = offset;
|
|
3213
|
-
startCodeLength = 3;
|
|
3214
|
-
}
|
|
3215
|
-
if (startCodeOffset === -1) {
|
|
3216
|
-
result.push(data.subarray(offset));
|
|
3217
|
-
break;
|
|
3218
|
-
}
|
|
3219
|
-
offset = startCodeOffset + startCodeLength;
|
|
3220
|
-
let nextStartCode = -1;
|
|
3221
|
-
let nextStartCodeLength = 0;
|
|
3222
|
-
for (let i = offset; i < data.length - 3; i++) {
|
|
3223
|
-
if (i + 4 <= data.length && data.subarray(i, i + 4).equals(NAL_START_CODE_4B)) {
|
|
3224
|
-
nextStartCode = i;
|
|
3225
|
-
nextStartCodeLength = 4;
|
|
3226
|
-
break;
|
|
3227
|
-
}
|
|
3228
|
-
if (i + 3 <= data.length && data.subarray(i, i + 3).equals(NAL_START_CODE_3B)) {
|
|
3229
|
-
nextStartCode = i;
|
|
3230
|
-
nextStartCodeLength = 3;
|
|
3231
|
-
break;
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
const nalEnd = nextStartCode !== -1 ? nextStartCode : data.length;
|
|
3235
|
-
const nalData = data.subarray(offset, nalEnd);
|
|
3236
|
-
const lengthBuf = Buffer.alloc(4);
|
|
3237
|
-
lengthBuf.writeUInt32BE(nalData.length, 0);
|
|
3238
|
-
result.push(lengthBuf);
|
|
3239
|
-
result.push(nalData);
|
|
3240
|
-
offset = nalEnd;
|
|
3241
|
-
}
|
|
3242
|
-
return Buffer.concat(result);
|
|
3243
|
-
}
|
|
3244
|
-
|
|
3245
|
-
// src/baichuan/stream/H265Converter.ts
|
|
3246
|
-
var NAL_START_CODE_4B2 = Buffer.from([0, 0, 0, 1]);
|
|
3247
|
-
var NAL_START_CODE_3B2 = Buffer.from([0, 0, 1]);
|
|
3248
|
-
function hasStartCodes2(data) {
|
|
3249
|
-
if (data.length < 4) return false;
|
|
3250
|
-
if (data.subarray(0, 4).equals(NAL_START_CODE_4B2)) return true;
|
|
3251
|
-
if (data.subarray(0, 3).equals(NAL_START_CODE_3B2)) return true;
|
|
3252
|
-
return false;
|
|
3253
|
-
}
|
|
3254
|
-
function tryConvertWithLengthReader2(data, readLen) {
|
|
3255
|
-
const result = [];
|
|
3256
|
-
let offset = 0;
|
|
3257
|
-
let nalCount = 0;
|
|
3258
|
-
while (offset < data.length) {
|
|
3259
|
-
if (offset + 4 > data.length) return null;
|
|
3260
|
-
const nalLength = readLen(data, offset);
|
|
3261
|
-
offset += 4;
|
|
3262
|
-
if (nalLength <= 0) return null;
|
|
3263
|
-
if (nalLength > data.length - offset) return null;
|
|
3264
|
-
result.push(NAL_START_CODE_4B2);
|
|
3265
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
3266
|
-
offset += nalLength;
|
|
3267
|
-
nalCount++;
|
|
3268
|
-
}
|
|
3269
|
-
if (nalCount === 0) return null;
|
|
3270
|
-
return Buffer.concat(result);
|
|
3271
|
-
}
|
|
3272
|
-
function tryConvertWithLengthReader162(data, readLen) {
|
|
3273
|
-
const result = [];
|
|
3274
|
-
let offset = 0;
|
|
3275
|
-
let nalCount = 0;
|
|
3276
|
-
while (offset < data.length) {
|
|
3277
|
-
if (offset + 2 > data.length) return null;
|
|
3278
|
-
const nalLength = readLen(data, offset);
|
|
3279
|
-
offset += 2;
|
|
3280
|
-
if (nalLength <= 0) return null;
|
|
3281
|
-
if (nalLength > data.length - offset) return null;
|
|
3282
|
-
result.push(NAL_START_CODE_4B2);
|
|
3283
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
3284
|
-
offset += nalLength;
|
|
3285
|
-
nalCount++;
|
|
3286
|
-
}
|
|
3287
|
-
if (nalCount === 0) return null;
|
|
3288
|
-
return Buffer.concat(result);
|
|
3289
|
-
}
|
|
3290
|
-
function tryConvertWithLengthReader242(data, endian) {
|
|
3291
|
-
const result = [];
|
|
3292
|
-
let offset = 0;
|
|
3293
|
-
let nalCount = 0;
|
|
3294
|
-
const readLen24 = (buf, at) => {
|
|
3295
|
-
if (at + 3 > buf.length) return 0;
|
|
3296
|
-
const b0 = buf[at];
|
|
3297
|
-
const b1 = buf[at + 1];
|
|
3298
|
-
const b2 = buf[at + 2];
|
|
3299
|
-
return endian === "be" ? (b0 << 16 | b1 << 8 | b2) >>> 0 : (b2 << 16 | b1 << 8 | b0) >>> 0;
|
|
3300
|
-
};
|
|
3301
|
-
while (offset < data.length) {
|
|
3302
|
-
if (offset + 3 > data.length) return null;
|
|
3303
|
-
const nalLength = readLen24(data, offset);
|
|
3304
|
-
offset += 3;
|
|
3305
|
-
if (nalLength <= 0) return null;
|
|
3306
|
-
if (nalLength > data.length - offset) return null;
|
|
3307
|
-
result.push(NAL_START_CODE_4B2);
|
|
3308
|
-
result.push(data.subarray(offset, offset + nalLength));
|
|
3309
|
-
offset += nalLength;
|
|
3310
|
-
nalCount++;
|
|
3311
|
-
}
|
|
3312
|
-
if (nalCount === 0) return null;
|
|
3313
|
-
return Buffer.concat(result);
|
|
3314
|
-
}
|
|
3315
|
-
function looksLikeSingleH265Nal(nalPayload) {
|
|
3316
|
-
if (nalPayload.length < 2) return false;
|
|
3317
|
-
const b0 = nalPayload[0];
|
|
3318
|
-
const b1 = nalPayload[1];
|
|
3319
|
-
if (b0 === void 0 || b1 === void 0) return false;
|
|
3320
|
-
if ((b0 & 128) !== 0) return false;
|
|
3321
|
-
const nalType = b0 >> 1 & 63;
|
|
3322
|
-
return nalType <= 40;
|
|
3323
|
-
}
|
|
3324
|
-
function convertToAnnexB2(data) {
|
|
3325
|
-
if (hasStartCodes2(data)) {
|
|
3326
|
-
return data;
|
|
3327
|
-
}
|
|
3328
|
-
const sc4 = Buffer.from([0, 0, 0, 1]);
|
|
3329
|
-
const sc3 = Buffer.from([0, 0, 1]);
|
|
3330
|
-
const maxScan = Math.min(64, data.length);
|
|
3331
|
-
const idx4 = data.subarray(0, maxScan).indexOf(sc4);
|
|
3332
|
-
if (idx4 > 0) return data.subarray(idx4);
|
|
3333
|
-
const idx3 = data.subarray(0, maxScan).indexOf(sc3);
|
|
3334
|
-
if (idx3 > 0) return data.subarray(idx3);
|
|
3335
|
-
const be = tryConvertWithLengthReader2(data, (b, o) => b.readUInt32BE(o));
|
|
3336
|
-
if (be) return be;
|
|
3337
|
-
const le = tryConvertWithLengthReader2(data, (b, o) => b.readUInt32LE(o));
|
|
3338
|
-
if (le) return le;
|
|
3339
|
-
const be24 = tryConvertWithLengthReader242(data, "be");
|
|
3340
|
-
if (be24) return be24;
|
|
3341
|
-
const le24 = tryConvertWithLengthReader242(data, "le");
|
|
3342
|
-
if (le24) return le24;
|
|
3343
|
-
const be16 = tryConvertWithLengthReader162(data, (b, o) => b.readUInt16BE(o));
|
|
3344
|
-
if (be16) return be16;
|
|
3345
|
-
const le16 = tryConvertWithLengthReader162(data, (b, o) => b.readUInt16LE(o));
|
|
3346
|
-
if (le16) return le16;
|
|
3347
|
-
if (looksLikeSingleH265Nal(data)) {
|
|
3348
|
-
return Buffer.concat([NAL_START_CODE_4B2, data]);
|
|
3349
|
-
}
|
|
3350
|
-
return data;
|
|
3351
|
-
}
|
|
3352
|
-
var H265RtpDepacketizer = class _H265RtpDepacketizer {
|
|
3353
|
-
fuParts = null;
|
|
3354
|
-
reset() {
|
|
3355
|
-
this.fuParts = null;
|
|
3356
|
-
}
|
|
3357
|
-
static parseRtpPayload(packet) {
|
|
3358
|
-
if (!packet || packet.length < 12) return null;
|
|
3359
|
-
const version = packet[0] >> 6 & 3;
|
|
3360
|
-
if (version !== 2) return null;
|
|
3361
|
-
const padding = (packet[0] & 32) !== 0;
|
|
3362
|
-
const extension = (packet[0] & 16) !== 0;
|
|
3363
|
-
const csrcCount = packet[0] & 15;
|
|
3364
|
-
let offset = 12 + csrcCount * 4;
|
|
3365
|
-
if (offset > packet.length) return null;
|
|
3366
|
-
if (extension) {
|
|
3367
|
-
if (offset + 4 > packet.length) return null;
|
|
3368
|
-
const extLenWords = packet.readUInt16BE(offset + 2);
|
|
3369
|
-
offset += 4 + extLenWords * 4;
|
|
3370
|
-
if (offset > packet.length) return null;
|
|
3371
|
-
}
|
|
3372
|
-
let end = packet.length;
|
|
3373
|
-
if (padding) {
|
|
3374
|
-
const padLen = packet[packet.length - 1];
|
|
3375
|
-
if (padLen <= 0 || padLen > packet.length) return null;
|
|
3376
|
-
end = packet.length - padLen;
|
|
3377
|
-
if (end < offset) return null;
|
|
3378
|
-
}
|
|
3379
|
-
if (end <= offset) return null;
|
|
3380
|
-
return packet.subarray(offset, end);
|
|
3381
|
-
}
|
|
3382
|
-
push(payload) {
|
|
3383
|
-
if (!payload || payload.length < 2) return [];
|
|
3384
|
-
const rtpPayload = _H265RtpDepacketizer.parseRtpPayload(payload);
|
|
3385
|
-
if (rtpPayload) payload = rtpPayload;
|
|
3386
|
-
const h0 = payload[0];
|
|
3387
|
-
const h1 = payload[1];
|
|
3388
|
-
if ((h0 & 128) !== 0) return [];
|
|
3389
|
-
const nalType = h0 >> 1 & 63;
|
|
3390
|
-
if (nalType === 48) {
|
|
3391
|
-
let off = 2;
|
|
3392
|
-
const out = [];
|
|
3393
|
-
while (off + 2 <= payload.length) {
|
|
3394
|
-
const size = payload.readUInt16BE(off);
|
|
3395
|
-
off += 2;
|
|
3396
|
-
if (size <= 0 || off + size > payload.length) return [];
|
|
3397
|
-
const nal = payload.subarray(off, off + size);
|
|
3398
|
-
off += size;
|
|
3399
|
-
if (nal.length) out.push(NAL_START_CODE_4B2, nal);
|
|
3400
|
-
}
|
|
3401
|
-
return out.length ? [Buffer.concat(out)] : [];
|
|
3402
|
-
}
|
|
3403
|
-
if (nalType === 49) {
|
|
3404
|
-
if (payload.length < 3) return [];
|
|
3405
|
-
const fuHeader = payload[2];
|
|
3406
|
-
const start = (fuHeader & 128) !== 0;
|
|
3407
|
-
const end = (fuHeader & 64) !== 0;
|
|
3408
|
-
const origType = fuHeader & 63;
|
|
3409
|
-
const orig0 = h0 & 129 | (origType & 63) << 1;
|
|
3410
|
-
const orig1 = h1;
|
|
3411
|
-
const frag = payload.subarray(3);
|
|
3412
|
-
if (start) {
|
|
3413
|
-
this.fuParts = [NAL_START_CODE_4B2, Buffer.from([orig0, orig1]), frag];
|
|
3414
|
-
} else {
|
|
3415
|
-
if (!this.fuParts) return [];
|
|
3416
|
-
this.fuParts.push(frag);
|
|
3417
|
-
}
|
|
3418
|
-
if (end) {
|
|
3419
|
-
if (!this.fuParts) return [];
|
|
3420
|
-
const out = Buffer.concat(this.fuParts);
|
|
3421
|
-
this.fuParts = null;
|
|
3422
|
-
return [out];
|
|
3423
|
-
}
|
|
3424
|
-
return [];
|
|
3425
|
-
}
|
|
3426
|
-
return [Buffer.concat([NAL_START_CODE_4B2, payload])];
|
|
3427
|
-
}
|
|
3428
|
-
};
|
|
3429
|
-
function splitAnnexBToNalPayloads2(annexB) {
|
|
3430
|
-
const starts = [];
|
|
3431
|
-
for (let i = 0; i < annexB.length - 3; i++) {
|
|
3432
|
-
if (annexB[i] === 0 && annexB[i + 1] === 0) {
|
|
3433
|
-
if (annexB[i + 2] === 1) {
|
|
3434
|
-
starts.push({ idx: i, len: 3 });
|
|
3435
|
-
i += 2;
|
|
3436
|
-
} else if (annexB[i + 2] === 0 && annexB[i + 3] === 1) {
|
|
3437
|
-
starts.push({ idx: i, len: 4 });
|
|
3438
|
-
i += 3;
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
if (starts.length === 0) return [];
|
|
3443
|
-
const out = [];
|
|
3444
|
-
for (let s = 0; s < starts.length; s++) {
|
|
3445
|
-
const st = starts[s];
|
|
3446
|
-
const start = st.idx + st.len;
|
|
3447
|
-
const end = starts[s + 1] ? starts[s + 1].idx : annexB.length;
|
|
3448
|
-
if (end > start) out.push(annexB.subarray(start, end));
|
|
3449
|
-
}
|
|
3450
|
-
return out;
|
|
3451
|
-
}
|
|
3452
|
-
function getH265NalType(nalPayload) {
|
|
3453
|
-
if (nalPayload.length < 1) return null;
|
|
3454
|
-
const b0 = nalPayload[0];
|
|
3455
|
-
if (b0 === void 0) return null;
|
|
3456
|
-
if ((b0 & 128) !== 0) return null;
|
|
3457
|
-
return b0 >> 1 & 63;
|
|
3458
|
-
}
|
|
3459
|
-
function isH265Irap(nalType) {
|
|
3460
|
-
return nalType >= 16 && nalType <= 23;
|
|
3461
|
-
}
|
|
3462
|
-
function isValidH265AnnexBAccessUnit(annexB) {
|
|
3463
|
-
if (!hasStartCodes2(annexB)) return false;
|
|
3464
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
3465
|
-
if (nals.length === 0) return false;
|
|
3466
|
-
for (const nal of nals) {
|
|
3467
|
-
if (nal.length < 2) return false;
|
|
3468
|
-
const b0 = nal[0];
|
|
3469
|
-
if (b0 === void 0) return false;
|
|
3470
|
-
if ((b0 & 128) !== 0) return false;
|
|
3471
|
-
const nalType = getH265NalType(nal);
|
|
3472
|
-
if (nalType === null) return false;
|
|
3473
|
-
if (nalType > 40) return false;
|
|
3474
|
-
}
|
|
3475
|
-
return true;
|
|
3476
|
-
}
|
|
3477
|
-
function isH265KeyframeAnnexB(annexB) {
|
|
3478
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
3479
|
-
let hasVps = false;
|
|
3480
|
-
let hasSps = false;
|
|
3481
|
-
let hasPps = false;
|
|
3482
|
-
let hasIrap = false;
|
|
3483
|
-
for (const nal of nals) {
|
|
3484
|
-
const nalType = getH265NalType(nal);
|
|
3485
|
-
if (nalType === null) continue;
|
|
3486
|
-
if (nalType === 32) hasVps = true;
|
|
3487
|
-
if (nalType === 33) hasSps = true;
|
|
3488
|
-
if (nalType === 34) hasPps = true;
|
|
3489
|
-
if (isH265Irap(nalType)) hasIrap = true;
|
|
3490
|
-
}
|
|
3491
|
-
return hasIrap && hasVps && hasSps && hasPps;
|
|
3492
|
-
}
|
|
3493
|
-
function extractVpsFromAnnexB(annexB) {
|
|
3494
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
3495
|
-
for (const nal of nals) {
|
|
3496
|
-
const nalType = getH265NalType(nal);
|
|
3497
|
-
if (nalType === 32) {
|
|
3498
|
-
return nal;
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3501
|
-
return null;
|
|
3502
|
-
}
|
|
3503
|
-
function extractSpsFromAnnexB(annexB) {
|
|
3504
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
3505
|
-
for (const nal of nals) {
|
|
3506
|
-
const nalType = getH265NalType(nal);
|
|
3507
|
-
if (nalType === 33) {
|
|
3508
|
-
return nal;
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
return null;
|
|
3512
|
-
}
|
|
3513
|
-
function extractPpsFromAnnexB(annexB) {
|
|
3514
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
3515
|
-
for (const nal of nals) {
|
|
3516
|
-
const nalType = getH265NalType(nal);
|
|
3517
|
-
if (nalType === 34) {
|
|
3518
|
-
return nal;
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
return null;
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3524
|
-
// src/baichuan/stream/BcMediaAnnexBDecoder.ts
|
|
3525
|
-
var ANNEXB_START_CODE_4B = Buffer.from([0, 0, 0, 1]);
|
|
3526
|
-
function detectVideoCodecFromNal(data) {
|
|
3527
|
-
if (!data || data.length < 5) return null;
|
|
3528
|
-
let nalStart = -1;
|
|
3529
|
-
for (let i = 0; i < Math.min(data.length - 4, 100); i++) {
|
|
3530
|
-
if (data[i] === 0 && data[i + 1] === 0) {
|
|
3531
|
-
if (data[i + 2] === 0 && data[i + 3] === 1) {
|
|
3532
|
-
nalStart = i + 4;
|
|
3533
|
-
break;
|
|
3534
|
-
}
|
|
3535
|
-
if (data[i + 2] === 1) {
|
|
3536
|
-
nalStart = i + 3;
|
|
3537
|
-
break;
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
if (nalStart < 0 && data.length >= 5) {
|
|
3542
|
-
const len = data.readUInt32BE(0);
|
|
3543
|
-
if (len > 0 && len < data.length - 4) {
|
|
3544
|
-
nalStart = 4;
|
|
3545
|
-
}
|
|
3546
|
-
}
|
|
3547
|
-
if (nalStart < 0 || nalStart >= data.length) return null;
|
|
3548
|
-
const nalByte = data[nalStart];
|
|
3549
|
-
if (nalByte === void 0) return null;
|
|
3550
|
-
const forbiddenBit264 = nalByte >> 7 & 1;
|
|
3551
|
-
const h264Type = nalByte & 31;
|
|
3552
|
-
if (forbiddenBit264 === 0 && h264Type > 0 && h264Type <= 12) {
|
|
3553
|
-
if (h264Type === 7 || h264Type === 8) {
|
|
3554
|
-
return "H264";
|
|
3555
|
-
}
|
|
3556
|
-
if (h264Type === 5) {
|
|
3557
|
-
return "H264";
|
|
3558
|
-
}
|
|
3559
|
-
if (h264Type === 1) {
|
|
3560
|
-
const nalRefIdc = nalByte >> 5 & 3;
|
|
3561
|
-
if (nalRefIdc >= 1) {
|
|
3562
|
-
return "H264";
|
|
3563
|
-
}
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
if (nalStart + 1 < data.length) {
|
|
3567
|
-
const nalByte2 = data[nalStart + 1];
|
|
3568
|
-
if (nalByte2 !== void 0) {
|
|
3569
|
-
const forbiddenBit = nalByte >> 7 & 1;
|
|
3570
|
-
const hevcType = nalByte >> 1 & 63;
|
|
3571
|
-
const temporalId = nalByte2 & 7;
|
|
3572
|
-
if (forbiddenBit === 0 && temporalId > 0 && hevcType <= 40) {
|
|
3573
|
-
if (hevcType === 32 || hevcType === 33 || hevcType === 34) {
|
|
3574
|
-
return "H265";
|
|
3575
|
-
}
|
|
3576
|
-
if (hevcType === 19 || hevcType === 20 || hevcType === 21) {
|
|
3577
|
-
return "H265";
|
|
3578
|
-
}
|
|
3579
|
-
if (hevcType <= 1 && nalByte <= 3) {
|
|
3580
|
-
return "H265";
|
|
3581
|
-
}
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
}
|
|
3585
|
-
return null;
|
|
3586
|
-
}
|
|
3587
|
-
var BcMediaAnnexBDecoder = class {
|
|
3588
|
-
codec;
|
|
3589
|
-
logger;
|
|
3590
|
-
onVideoAccessUnit;
|
|
3591
|
-
onAudioFrame;
|
|
3592
|
-
stats = {
|
|
3593
|
-
bytesIn: 0,
|
|
3594
|
-
bytesOut: 0,
|
|
3595
|
-
audioBytesOut: 0,
|
|
3596
|
-
packets: 0,
|
|
3597
|
-
videoPackets: 0,
|
|
3598
|
-
audioPackets: 0,
|
|
3599
|
-
aacPackets: 0,
|
|
3600
|
-
adpcmPackets: 0,
|
|
3601
|
-
keyframes: 0,
|
|
3602
|
-
videoType: null,
|
|
3603
|
-
audioType: null,
|
|
3604
|
-
infos: [],
|
|
3605
|
-
recoveredSkips: 0
|
|
3606
|
-
};
|
|
3607
|
-
lastH264Sps = null;
|
|
3608
|
-
lastH264Pps = null;
|
|
3609
|
-
lastH265Vps = null;
|
|
3610
|
-
lastH265Sps = null;
|
|
3611
|
-
lastH265Pps = null;
|
|
3612
|
-
constructor(params) {
|
|
3613
|
-
this.logger = params?.logger;
|
|
3614
|
-
this.onVideoAccessUnit = params?.onVideoAccessUnit;
|
|
3615
|
-
this.onAudioFrame = params?.onAudioFrame;
|
|
3616
|
-
this.codec = new BcMediaCodec(params?.strict ?? false, this.logger);
|
|
3617
|
-
}
|
|
3618
|
-
getStats() {
|
|
3619
|
-
return { ...this.stats, infos: [...this.stats.infos] };
|
|
3620
|
-
}
|
|
3621
|
-
/**
|
|
3622
|
-
* Push arbitrary bytes from a Baichuan/BcMedia transport into the decoder.
|
|
3623
|
-
* Emits complete Annex-B access units via callback.
|
|
3624
|
-
*/
|
|
3625
|
-
push(chunk) {
|
|
3626
|
-
if (!chunk || chunk.length === 0) return;
|
|
3627
|
-
this.stats.bytesIn += chunk.length;
|
|
3628
|
-
const packets = this.codec.decode(chunk);
|
|
3629
|
-
this.stats.packets += packets.length;
|
|
3630
|
-
for (const media of packets) {
|
|
3631
|
-
this.handleMedia(media);
|
|
3632
|
-
}
|
|
3633
|
-
}
|
|
3634
|
-
handleMedia(media) {
|
|
3635
|
-
if (media.type === "InfoV1" || media.type === "InfoV2") {
|
|
3636
|
-
const info = media;
|
|
3637
|
-
this.stats.infos.push({
|
|
3638
|
-
type: media.type,
|
|
3639
|
-
videoWidth: info.videoWidth,
|
|
3640
|
-
videoHeight: info.videoHeight,
|
|
3641
|
-
fps: info.fps,
|
|
3642
|
-
startYear: info.startYear,
|
|
3643
|
-
startMonth: info.startMonth,
|
|
3644
|
-
startDay: info.startDay,
|
|
3645
|
-
startHour: info.startHour,
|
|
3646
|
-
startMin: info.startMin,
|
|
3647
|
-
startSeconds: info.startSeconds,
|
|
3648
|
-
endYear: info.endYear,
|
|
3649
|
-
endMonth: info.endMonth,
|
|
3650
|
-
endDay: info.endDay,
|
|
3651
|
-
endHour: info.endHour,
|
|
3652
|
-
endMin: info.endMin,
|
|
3653
|
-
endSeconds: info.endSeconds
|
|
3654
|
-
});
|
|
3655
|
-
return;
|
|
3656
|
-
}
|
|
3657
|
-
if (media.type === "Aac" || media.type === "Adpcm") {
|
|
3658
|
-
const audioMedia = media;
|
|
3659
|
-
this.stats.audioPackets++;
|
|
3660
|
-
if (media.type === "Aac") {
|
|
3661
|
-
this.stats.aacPackets++;
|
|
3662
|
-
} else {
|
|
3663
|
-
this.stats.adpcmPackets++;
|
|
3664
|
-
}
|
|
3665
|
-
if (this.stats.audioType == null) {
|
|
3666
|
-
this.stats.audioType = media.type;
|
|
3667
|
-
}
|
|
3668
|
-
if (this.onAudioFrame) {
|
|
3669
|
-
this.stats.audioBytesOut += audioMedia.data.length;
|
|
3670
|
-
this.onAudioFrame({
|
|
3671
|
-
audioType: media.type,
|
|
3672
|
-
data: audioMedia.data
|
|
3673
|
-
});
|
|
3674
|
-
}
|
|
3675
|
-
return;
|
|
3676
|
-
}
|
|
3677
|
-
if (media.type !== "Iframe" && media.type !== "Pframe") return;
|
|
3678
|
-
this.stats.videoPackets++;
|
|
3679
|
-
const microseconds = media.microseconds;
|
|
3680
|
-
const raw = media.data;
|
|
3681
|
-
let videoType = media.videoType;
|
|
3682
|
-
const detectedType = detectVideoCodecFromNal(raw);
|
|
3683
|
-
if (detectedType != null && detectedType !== videoType) {
|
|
3684
|
-
this.logger?.debug?.(
|
|
3685
|
-
`[BcMediaAnnexBDecoder] Codec mismatch: reported ${videoType}, detected ${detectedType}`
|
|
3686
|
-
);
|
|
3687
|
-
videoType = detectedType;
|
|
3688
|
-
}
|
|
3689
|
-
if (this.stats.videoType == null) this.stats.videoType = videoType;
|
|
3690
|
-
let annexB = videoType === "H265" ? convertToAnnexB2(raw) : convertToAnnexB(raw);
|
|
3691
|
-
const isKeyframe = media.type === "Iframe" || (videoType === "H265" ? isH265KeyframeAnnexB(annexB) : isH264KeyframeAnnexB(annexB));
|
|
3692
|
-
if (videoType === "H264") {
|
|
3693
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
3694
|
-
for (const nal of nals) {
|
|
3695
|
-
const t = (nal[0] ?? 0) & 31;
|
|
3696
|
-
if (t === 7) this.lastH264Sps = nal;
|
|
3697
|
-
if (t === 8) this.lastH264Pps = nal;
|
|
3698
|
-
}
|
|
3699
|
-
if (isKeyframe) {
|
|
3700
|
-
const hasSps = nals.some((nal) => ((nal[0] ?? 0) & 31) === 7);
|
|
3701
|
-
const hasPps = nals.some((nal) => ((nal[0] ?? 0) & 31) === 8);
|
|
3702
|
-
const toPrepend = [];
|
|
3703
|
-
if (!hasSps && this.lastH264Sps)
|
|
3704
|
-
toPrepend.push(ANNEXB_START_CODE_4B, this.lastH264Sps);
|
|
3705
|
-
if (!hasPps && this.lastH264Pps)
|
|
3706
|
-
toPrepend.push(ANNEXB_START_CODE_4B, this.lastH264Pps);
|
|
3707
|
-
if (toPrepend.length > 0)
|
|
3708
|
-
annexB = Buffer.concat([...toPrepend, annexB]);
|
|
3709
|
-
}
|
|
3710
|
-
} else {
|
|
3711
|
-
const vps = extractVpsFromAnnexB(annexB);
|
|
3712
|
-
const sps = extractSpsFromAnnexB(annexB);
|
|
3713
|
-
const pps = extractPpsFromAnnexB(annexB);
|
|
3714
|
-
if (vps) this.lastH265Vps = vps;
|
|
3715
|
-
if (sps) this.lastH265Sps = sps;
|
|
3716
|
-
if (pps) this.lastH265Pps = pps;
|
|
3717
|
-
if (isKeyframe) {
|
|
3718
|
-
const hasVps = vps != null;
|
|
3719
|
-
const hasSps = sps != null;
|
|
3720
|
-
const hasPps = pps != null;
|
|
3721
|
-
const toPrepend = [];
|
|
3722
|
-
if (!hasVps && this.lastH265Vps)
|
|
3723
|
-
toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Vps);
|
|
3724
|
-
if (!hasSps && this.lastH265Sps)
|
|
3725
|
-
toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Sps);
|
|
3726
|
-
if (!hasPps && this.lastH265Pps)
|
|
3727
|
-
toPrepend.push(ANNEXB_START_CODE_4B, this.lastH265Pps);
|
|
3728
|
-
if (toPrepend.length > 0)
|
|
3729
|
-
annexB = Buffer.concat([...toPrepend, annexB]);
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
if (isKeyframe) this.stats.keyframes++;
|
|
3733
|
-
this.stats.bytesOut += annexB.length;
|
|
3734
|
-
this.onVideoAccessUnit?.({ videoType, annexB, microseconds, isKeyframe });
|
|
3735
|
-
}
|
|
3736
|
-
};
|
|
3737
|
-
|
|
3738
|
-
// src/baichuan/stream/BaichuanVideoStream.ts
|
|
3739
|
-
var NAL_START_CODE_4B3 = Buffer.from([0, 0, 0, 1]);
|
|
3740
|
-
var WATCHDOG_TICK_MS = 1e3;
|
|
3741
|
-
var WATCHDOG_MAX_RESTARTS_PER_MINUTE = 3;
|
|
3742
|
-
var AsyncFsQueue = class {
|
|
3743
|
-
running = false;
|
|
3744
|
-
queue = [];
|
|
3745
|
-
maxQueueSize;
|
|
3746
|
-
constructor(maxQueueSize) {
|
|
3747
|
-
this.maxQueueSize = maxQueueSize;
|
|
3748
|
-
}
|
|
3749
|
-
enqueue(task) {
|
|
3750
|
-
if (this.queue.length >= this.maxQueueSize) return;
|
|
3751
|
-
this.queue.push(task);
|
|
3752
|
-
this.pump();
|
|
3753
|
-
}
|
|
3754
|
-
pump() {
|
|
3755
|
-
if (this.running) return;
|
|
3756
|
-
this.running = true;
|
|
3757
|
-
void this.run();
|
|
3758
|
-
}
|
|
3759
|
-
async run() {
|
|
3760
|
-
try {
|
|
3761
|
-
while (this.queue.length > 0) {
|
|
3762
|
-
const task = this.queue.shift();
|
|
3763
|
-
if (!task) continue;
|
|
3764
|
-
try {
|
|
3765
|
-
await task();
|
|
3766
|
-
} catch {
|
|
3767
|
-
}
|
|
3768
|
-
}
|
|
3769
|
-
} finally {
|
|
3770
|
-
this.running = false;
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
};
|
|
3774
|
-
function removeEmulationPreventionBytes(rbsp) {
|
|
3775
|
-
const out = [];
|
|
3776
|
-
for (let i = 0; i < rbsp.length; i++) {
|
|
3777
|
-
if (i >= 2 && rbsp[i] === 3 && rbsp[i - 1] === 0 && rbsp[i - 2] === 0) {
|
|
3778
|
-
continue;
|
|
3779
|
-
}
|
|
3780
|
-
out.push(rbsp[i]);
|
|
3781
|
-
}
|
|
3782
|
-
return Buffer.from(out);
|
|
3783
|
-
}
|
|
3784
|
-
var BitReader = class {
|
|
3785
|
-
b;
|
|
3786
|
-
bitPos = 0;
|
|
3787
|
-
constructor(buf) {
|
|
3788
|
-
this.b = buf;
|
|
3789
|
-
}
|
|
3790
|
-
readBits(n) {
|
|
3791
|
-
if (n <= 0) return 0;
|
|
3792
|
-
let v = 0;
|
|
3793
|
-
for (let i = 0; i < n; i++) {
|
|
3794
|
-
const bytePos = this.bitPos >> 3;
|
|
3795
|
-
if (bytePos >= this.b.length) return null;
|
|
3796
|
-
const bitInByte = 7 - (this.bitPos & 7);
|
|
3797
|
-
const bit = this.b[bytePos] >> bitInByte & 1;
|
|
3798
|
-
v = v << 1 | bit;
|
|
3799
|
-
this.bitPos++;
|
|
3800
|
-
}
|
|
3801
|
-
return v >>> 0;
|
|
3802
|
-
}
|
|
3803
|
-
readUE() {
|
|
3804
|
-
let zeros = 0;
|
|
3805
|
-
while (true) {
|
|
3806
|
-
const bit = this.readBits(1);
|
|
3807
|
-
if (bit == null) return null;
|
|
3808
|
-
if (bit === 0) zeros++;
|
|
3809
|
-
else break;
|
|
3810
|
-
if (zeros > 31) return null;
|
|
3811
|
-
}
|
|
3812
|
-
const rest = zeros ? this.readBits(zeros) : 0;
|
|
3813
|
-
if (rest == null) return null;
|
|
3814
|
-
return (1 << zeros) - 1 + rest >>> 0;
|
|
3815
|
-
}
|
|
3816
|
-
};
|
|
3817
|
-
function parseSpsIdFromNal(nalPayload) {
|
|
3818
|
-
if (((nalPayload[0] ?? 0) & 31) !== 7) return null;
|
|
3819
|
-
const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
|
|
3820
|
-
const r = new BitReader(rbsp);
|
|
3821
|
-
if (r.readBits(8) == null) return null;
|
|
3822
|
-
if (r.readBits(8) == null) return null;
|
|
3823
|
-
if (r.readBits(8) == null) return null;
|
|
3824
|
-
return r.readUE();
|
|
3825
|
-
}
|
|
3826
|
-
function parsePpsIdsFromNal(nalPayload) {
|
|
3827
|
-
if (((nalPayload[0] ?? 0) & 31) !== 8) return null;
|
|
3828
|
-
const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
|
|
3829
|
-
const r = new BitReader(rbsp);
|
|
3830
|
-
const ppsId = r.readUE();
|
|
3831
|
-
const spsId = r.readUE();
|
|
3832
|
-
if (ppsId == null || spsId == null) return null;
|
|
3833
|
-
return { ppsId, spsId };
|
|
3834
|
-
}
|
|
3835
|
-
function parseSlicePpsIdFromNal(nalPayload) {
|
|
3836
|
-
const t = (nalPayload[0] ?? 0) & 31;
|
|
3837
|
-
if (t !== 1 && t !== 5) return null;
|
|
3838
|
-
const rbsp = removeEmulationPreventionBytes(nalPayload.subarray(1));
|
|
3839
|
-
const r = new BitReader(rbsp);
|
|
3840
|
-
if (r.readUE() == null) return null;
|
|
3841
|
-
if (r.readUE() == null) return null;
|
|
3842
|
-
return r.readUE();
|
|
3843
|
-
}
|
|
3844
|
-
var BaichuanVideoStream = class _BaichuanVideoStream extends EventEmitter {
|
|
3845
|
-
client;
|
|
3846
|
-
api;
|
|
3847
|
-
channel;
|
|
3848
|
-
profile;
|
|
3849
|
-
variant;
|
|
3850
|
-
logger;
|
|
3851
|
-
active = false;
|
|
3852
|
-
videoFrameHandler;
|
|
3853
|
-
expectedStreamTypes;
|
|
3854
|
-
activeMsgNum;
|
|
3855
|
-
cmdId;
|
|
3856
|
-
acceptAnyStreamType;
|
|
3857
|
-
lockedChannelId;
|
|
3858
|
-
bcMediaCodec;
|
|
3859
|
-
debugH264LogsLeft;
|
|
3860
|
-
debugSavedSamples;
|
|
3861
|
-
warnedNonAnnexBOnce = false;
|
|
3862
|
-
// "RTP-like" depacketizer (some models encapsulate NAL units in FU-A/STAP)
|
|
3863
|
-
depacketizer = new H264RtpDepacketizer();
|
|
3864
|
-
depacketizerH265 = new H265RtpDepacketizer();
|
|
3865
|
-
dumpChunkIdx = 0;
|
|
3866
|
-
dumpNalLines = 0;
|
|
3867
|
-
dumpIo = new AsyncFsQueue(200);
|
|
3868
|
-
spsById = /* @__PURE__ */ new Map();
|
|
3869
|
-
// NAL payload (without start code) - H.264
|
|
3870
|
-
ppsById = /* @__PURE__ */ new Map();
|
|
3871
|
-
// NAL payload + mapping - H.264
|
|
3872
|
-
lastSps = null;
|
|
3873
|
-
// H.264
|
|
3874
|
-
lastPps = null;
|
|
3875
|
-
// H.264
|
|
3876
|
-
lastPrependedPpsId = null;
|
|
3877
|
-
// H.264
|
|
3878
|
-
// H.265 parameter sets
|
|
3879
|
-
lastVps = null;
|
|
3880
|
-
// H.265 VPS
|
|
3881
|
-
lastSpsH265 = null;
|
|
3882
|
-
// H.265 SPS
|
|
3883
|
-
lastPpsH265 = null;
|
|
3884
|
-
// H.265 PPS
|
|
3885
|
-
lastPrependedParamSetsH265 = false;
|
|
3886
|
-
// Track if we've prepended H.265 param sets
|
|
3887
|
-
// Stateful AES decryptor for fragmented BcMedia packets (full_aes mode)
|
|
3888
|
-
// In CFB mode, continuation frames must use the cipher state from previous frames.
|
|
3889
|
-
aesStreamDecryptor = null;
|
|
3890
|
-
/**
|
|
3891
|
-
* Pending startup error stashed when emitSafeError is called before any
|
|
3892
|
-
* "error" listener is registered (e.g. camera returns 400 during start()).
|
|
3893
|
-
* The rfc4571-server's waitForKeyframe can consume this immediately instead
|
|
3894
|
-
* of waiting for the full keyframe timeout.
|
|
3895
|
-
*/
|
|
3896
|
-
_pendingStartupError;
|
|
3897
|
-
/** Consume and clear any pending startup error. */
|
|
3898
|
-
consumePendingStartupError() {
|
|
3899
|
-
const err = this._pendingStartupError;
|
|
3900
|
-
this._pendingStartupError = void 0;
|
|
3901
|
-
return err;
|
|
3902
|
-
}
|
|
3903
|
-
emitSafeError(err) {
|
|
3904
|
-
if (!this.active) {
|
|
3905
|
-
this.logger?.warn?.(
|
|
3906
|
-
`[BaichuanVideoStream] Suppressed error after stop: ${err.message}`
|
|
3907
|
-
);
|
|
3908
|
-
return;
|
|
3909
|
-
}
|
|
3910
|
-
if (this.listenerCount("error") === 0) {
|
|
3911
|
-
this.logger?.warn?.(
|
|
3912
|
-
`[BaichuanVideoStream] Unhandled stream error: ${err.message}`
|
|
3913
|
-
);
|
|
3914
|
-
this._pendingStartupError = err;
|
|
3915
|
-
return;
|
|
3916
|
-
}
|
|
3917
|
-
this.emit("error", err);
|
|
3918
|
-
}
|
|
3919
|
-
lastMediaAtMs = 0;
|
|
3920
|
-
watchdogTimer;
|
|
3921
|
-
restarting = false;
|
|
3922
|
-
restartWindowStartMs = 0;
|
|
3923
|
-
restartCountInWindow = 0;
|
|
3924
|
-
idleRestartMs;
|
|
3925
|
-
// Note: reassembly happens at the BcMediaCodec transport level, so we do not
|
|
3926
|
-
// accumulate frames here.
|
|
3927
|
-
static scoreBcMediaLike(b) {
|
|
3928
|
-
if (b.length < 4) return { score: -1, first: -1 };
|
|
3929
|
-
const maxScan = Math.min(64 * 1024, b.length - 4);
|
|
3930
|
-
let count = 0;
|
|
3931
|
-
let first = -1;
|
|
3932
|
-
for (let i = 0; i <= maxScan; i++) {
|
|
3933
|
-
const magic = b.readUInt32LE(i);
|
|
3934
|
-
const isInfoV1 = magic === 825241649;
|
|
3935
|
-
const isInfoV2 = magic === 842018865;
|
|
3936
|
-
const isIFrame = magic >= 1667510320 && magic <= 1667510329;
|
|
3937
|
-
const isPFrame = magic >= 1667510576 && magic <= 1667510585;
|
|
3938
|
-
const isAac = magic === 1651979568;
|
|
3939
|
-
const isAdpcm = magic === 1651978544;
|
|
3940
|
-
if (isInfoV1 || isInfoV2 || isIFrame || isPFrame || isAac || isAdpcm) {
|
|
3941
|
-
count++;
|
|
3942
|
-
if (first < 0) first = i;
|
|
3943
|
-
if (count > 32 && first === 0) break;
|
|
3944
|
-
}
|
|
3945
|
-
}
|
|
3946
|
-
return { score: count * 1e3 - (first < 0 ? 5e4 : first), first };
|
|
3947
|
-
}
|
|
3948
|
-
chooseDecryptedOrRawCandidate(params) {
|
|
3949
|
-
const { raw, enc, channelId, allowResync, encryptLen } = params;
|
|
3950
|
-
if (encryptLen !== void 0 && encryptLen > 0 && encryptLen < raw.length) {
|
|
3951
|
-
const encryptedPart = raw.subarray(0, encryptLen);
|
|
3952
|
-
const clearPart = raw.subarray(encryptLen);
|
|
3953
|
-
const decryptedPart = this.client.tryDecryptBinary(
|
|
3954
|
-
encryptedPart,
|
|
3955
|
-
channelId,
|
|
3956
|
-
enc
|
|
3957
|
-
);
|
|
3958
|
-
const chosen2 = Buffer.concat([decryptedPart, clearPart]);
|
|
3959
|
-
if (!allowResync) return chosen2;
|
|
3960
|
-
const best2 = _BaichuanVideoStream.scoreBcMediaLike(chosen2);
|
|
3961
|
-
return best2.first > 0 ? chosen2.subarray(best2.first) : chosen2;
|
|
3962
|
-
}
|
|
3963
|
-
if (enc.kind === "full_aes") {
|
|
3964
|
-
const key = enc.key;
|
|
3965
|
-
const isReplayMode = this.cmdId === 5;
|
|
3966
|
-
const freshDecrypted = aesDecrypt(raw, key);
|
|
3967
|
-
const freshScore = _BaichuanVideoStream.scoreBcMediaLike(freshDecrypted);
|
|
3968
|
-
const rawScore2 = _BaichuanVideoStream.scoreBcMediaLike(raw);
|
|
3969
|
-
const startsWithMagic = freshScore.first === 0 && freshScore.score > 0;
|
|
3970
|
-
if (!isReplayMode) {
|
|
3971
|
-
const chosen3 = freshScore.score > rawScore2.score ? freshDecrypted : raw;
|
|
3972
|
-
if (!allowResync) return chosen3;
|
|
3973
|
-
const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
|
|
3974
|
-
return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
|
|
3975
|
-
}
|
|
3976
|
-
if (startsWithMagic && freshDecrypted.length >= 24) {
|
|
3977
|
-
const magic = freshDecrypted.readUInt32LE(0);
|
|
3978
|
-
const isIFrame = magic >= 1667510320 && magic <= 1667510329;
|
|
3979
|
-
const isPFrame = magic >= 1667510576 && magic <= 1667510585;
|
|
3980
|
-
if ((isIFrame || isPFrame) && freshDecrypted.length >= 24) {
|
|
3981
|
-
const additionalHeaderSize = freshDecrypted.readUInt32LE(12);
|
|
3982
|
-
const headerLen = 24 + additionalHeaderSize;
|
|
3983
|
-
if (headerLen > 0 && headerLen < raw.length) {
|
|
3984
|
-
const rawPayloadStart = raw.subarray(headerLen, headerLen + 4);
|
|
3985
|
-
const hasRawStartCode = rawPayloadStart.length >= 4 && rawPayloadStart[0] === 0 && rawPayloadStart[1] === 0 && (rawPayloadStart[2] === 1 || rawPayloadStart[2] === 0 && rawPayloadStart[3] === 1);
|
|
3986
|
-
if (hasRawStartCode) {
|
|
3987
|
-
const headerDecrypted = aesDecrypt(
|
|
3988
|
-
raw.subarray(0, headerLen),
|
|
3989
|
-
key
|
|
3990
|
-
);
|
|
3991
|
-
const clearPayload = raw.subarray(headerLen);
|
|
3992
|
-
const chosen3 = Buffer.concat([headerDecrypted, clearPayload]);
|
|
3993
|
-
if (!allowResync) return chosen3;
|
|
3994
|
-
const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
|
|
3995
|
-
return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
|
|
3996
|
-
}
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
const IFRAME_ENCRYPT_BOUNDARY = 1024;
|
|
4000
|
-
if (raw.length > IFRAME_ENCRYPT_BOUNDARY) {
|
|
4001
|
-
const encryptedPart = raw.subarray(0, IFRAME_ENCRYPT_BOUNDARY);
|
|
4002
|
-
const clearPart = raw.subarray(IFRAME_ENCRYPT_BOUNDARY);
|
|
4003
|
-
const decryptedPart = aesDecrypt(encryptedPart, key);
|
|
4004
|
-
const chosen3 = Buffer.concat([decryptedPart, clearPart]);
|
|
4005
|
-
if (!allowResync) return chosen3;
|
|
4006
|
-
const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
|
|
4007
|
-
return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
|
|
4008
|
-
}
|
|
4009
|
-
{
|
|
4010
|
-
const chosen3 = freshDecrypted;
|
|
4011
|
-
if (!allowResync) return chosen3;
|
|
4012
|
-
const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
|
|
4013
|
-
return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
|
|
4014
|
-
}
|
|
4015
|
-
}
|
|
4016
|
-
if (rawScore2.first === 0 && rawScore2.score > freshScore.score) {
|
|
4017
|
-
const chosen3 = raw;
|
|
4018
|
-
if (!allowResync) return chosen3;
|
|
4019
|
-
const best3 = _BaichuanVideoStream.scoreBcMediaLike(chosen3);
|
|
4020
|
-
return best3.first > 0 ? chosen3.subarray(best3.first) : chosen3;
|
|
4021
|
-
}
|
|
4022
|
-
if (this.aesStreamDecryptor && this.aesStreamDecryptor.isInitialized()) {
|
|
4023
|
-
const statefulDecrypted = this.aesStreamDecryptor.update(raw);
|
|
4024
|
-
return statefulDecrypted;
|
|
4025
|
-
}
|
|
4026
|
-
const chosen2 = freshScore.score > rawScore2.score ? freshDecrypted : raw;
|
|
4027
|
-
if (!allowResync) return chosen2;
|
|
4028
|
-
const best2 = _BaichuanVideoStream.scoreBcMediaLike(chosen2);
|
|
4029
|
-
return best2.first > 0 ? chosen2.subarray(best2.first) : chosen2;
|
|
4030
|
-
}
|
|
4031
|
-
const rawScore = _BaichuanVideoStream.scoreBcMediaLike(raw);
|
|
4032
|
-
const dec = this.client.enc.kind === "aes" || this.client.enc.kind === "bc" ? this.client.tryDecryptBinary(raw, channelId, enc) : raw;
|
|
4033
|
-
const decScore = _BaichuanVideoStream.scoreBcMediaLike(dec);
|
|
4034
|
-
const chosen = decScore.score > rawScore.score ? dec : raw;
|
|
4035
|
-
if (!allowResync) return chosen;
|
|
4036
|
-
const best = _BaichuanVideoStream.scoreBcMediaLike(chosen);
|
|
4037
|
-
return best.first > 0 ? chosen.subarray(best.first) : chosen;
|
|
4038
|
-
}
|
|
4039
|
-
constructor(options) {
|
|
4040
|
-
super();
|
|
4041
|
-
this.client = options.client;
|
|
4042
|
-
this.api = options.api;
|
|
4043
|
-
this.channel = options.channel;
|
|
4044
|
-
this.profile = options.profile;
|
|
4045
|
-
this.variant = options.variant ?? "default";
|
|
4046
|
-
this.logger = options.logger;
|
|
4047
|
-
this.cmdId = options.cmdId ?? 3;
|
|
4048
|
-
this.acceptAnyStreamType = options.acceptAnyStreamType ?? false;
|
|
4049
|
-
this.expectedStreamTypes = this.profile === "sub" ? /* @__PURE__ */ new Set([1, 3]) : /* @__PURE__ */ new Set([0, 2]);
|
|
4050
|
-
if (this.variant === "telephoto") this.expectedStreamTypes.add(0);
|
|
4051
|
-
this.bcMediaCodec = new BcMediaCodec(false, this.logger);
|
|
4052
|
-
const dbg = this.client.getDebugConfig();
|
|
4053
|
-
this.debugH264LogsLeft = dbg.traceNativeStream ? 200 : 0;
|
|
4054
|
-
this.debugSavedSamples = false;
|
|
4055
|
-
this.dumpChunkIdx = 0;
|
|
4056
|
-
this.spsById = /* @__PURE__ */ new Map();
|
|
4057
|
-
this.ppsById = /* @__PURE__ */ new Map();
|
|
4058
|
-
this.lastSps = null;
|
|
4059
|
-
this.lastPps = null;
|
|
4060
|
-
this.lastPrependedPpsId = null;
|
|
4061
|
-
this.lastVps = null;
|
|
4062
|
-
this.lastSpsH265 = null;
|
|
4063
|
-
this.lastPpsH265 = null;
|
|
4064
|
-
this.lastPrependedParamSetsH265 = false;
|
|
4065
|
-
if (options.msgNum !== void 0) {
|
|
4066
|
-
this.activeMsgNum = options.msgNum;
|
|
4067
|
-
}
|
|
4068
|
-
const transport = this.client.getTransport?.();
|
|
4069
|
-
this.idleRestartMs = transport === "udp" ? 6e4 : 3e4;
|
|
4070
|
-
}
|
|
4071
|
-
noteMediaActivity() {
|
|
4072
|
-
this.lastMediaAtMs = Date.now();
|
|
4073
|
-
}
|
|
4074
|
-
startWatchdog() {
|
|
4075
|
-
if (!this.api) return;
|
|
4076
|
-
if (this.watchdogTimer) return;
|
|
4077
|
-
this.restartWindowStartMs = Date.now();
|
|
4078
|
-
this.restartCountInWindow = 0;
|
|
4079
|
-
this.watchdogTimer = setInterval(() => {
|
|
4080
|
-
void this.watchdogTick();
|
|
4081
|
-
}, WATCHDOG_TICK_MS);
|
|
4082
|
-
}
|
|
4083
|
-
stopWatchdog() {
|
|
4084
|
-
if (this.watchdogTimer) clearInterval(this.watchdogTimer);
|
|
4085
|
-
this.watchdogTimer = void 0;
|
|
4086
|
-
}
|
|
4087
|
-
async watchdogTick() {
|
|
4088
|
-
if (!this.active) return;
|
|
4089
|
-
if (!this.api) return;
|
|
4090
|
-
if (this.restarting) return;
|
|
4091
|
-
if (this.lastMediaAtMs <= 0) return;
|
|
4092
|
-
const now = Date.now();
|
|
4093
|
-
const idleMs = now - this.lastMediaAtMs;
|
|
4094
|
-
if (idleMs < this.idleRestartMs) return;
|
|
4095
|
-
if (now - this.restartWindowStartMs > 6e4) {
|
|
4096
|
-
this.restartWindowStartMs = now;
|
|
4097
|
-
this.restartCountInWindow = 0;
|
|
4098
|
-
}
|
|
4099
|
-
if (this.restartCountInWindow >= WATCHDOG_MAX_RESTARTS_PER_MINUTE) {
|
|
4100
|
-
this.logger?.warn(
|
|
4101
|
-
`[BaichuanVideoStream] Watchdog: idle for ${idleMs}ms, but restart budget exceeded (${WATCHDOG_MAX_RESTARTS_PER_MINUTE}/min); leaving stream as-is`
|
|
4102
|
-
);
|
|
4103
|
-
this.lastMediaAtMs = now;
|
|
4104
|
-
return;
|
|
4105
|
-
}
|
|
4106
|
-
this.restartCountInWindow++;
|
|
4107
|
-
void this.restartNativeStream({ reason: `idle ${idleMs}ms` });
|
|
4108
|
-
}
|
|
4109
|
-
async restartNativeStream(params) {
|
|
4110
|
-
if (!this.api) return;
|
|
4111
|
-
if (!this.active) return;
|
|
4112
|
-
if (this.restarting) return;
|
|
4113
|
-
this.restarting = true;
|
|
4114
|
-
try {
|
|
4115
|
-
const transport = this.client.getTransport?.() ?? "unknown";
|
|
4116
|
-
const msgNum = this.activeMsgNum ?? "unknown";
|
|
4117
|
-
this.logger?.warn(
|
|
4118
|
-
`[BaichuanVideoStream] Watchdog restarting native stream (channel=${this.channel} profile=${this.profile} expectedStreamTypes=[${[
|
|
4119
|
-
...this.expectedStreamTypes
|
|
4120
|
-
].join(
|
|
4121
|
-
","
|
|
4122
|
-
)}] msgNum=${msgNum} transport=${transport} reason=${params.reason})`
|
|
4123
|
-
);
|
|
4124
|
-
this.depacketizer.reset();
|
|
4125
|
-
this.depacketizerH265.reset();
|
|
4126
|
-
this.bcMediaCodec.clear();
|
|
4127
|
-
this.activeMsgNum = void 0;
|
|
4128
|
-
this.lockedChannelId = void 0;
|
|
4129
|
-
this.lastPrependedPpsId = null;
|
|
4130
|
-
this.lastPrependedParamSetsH265 = false;
|
|
4131
|
-
try {
|
|
4132
|
-
await this.api.stopVideoStream(this.channel, this.profile, {
|
|
4133
|
-
variant: this.variant,
|
|
4134
|
-
client: this.client
|
|
4135
|
-
});
|
|
4136
|
-
} catch {
|
|
4137
|
-
}
|
|
4138
|
-
await this.api.startVideoStream(this.channel, this.profile, {
|
|
4139
|
-
variant: this.variant,
|
|
4140
|
-
client: this.client
|
|
4141
|
-
});
|
|
4142
|
-
try {
|
|
4143
|
-
const getMsgNum = this.api.getActiveVideoMsgNumWithVariant;
|
|
4144
|
-
const v = typeof getMsgNum === "function" ? getMsgNum(this.channel, this.profile, this.variant) : void 0;
|
|
4145
|
-
if (v !== void 0) this.activeMsgNum = v;
|
|
4146
|
-
} catch {
|
|
4147
|
-
}
|
|
4148
|
-
this.lastMediaAtMs = Date.now();
|
|
4149
|
-
} catch (error) {
|
|
4150
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
4151
|
-
this.emitSafeError(err);
|
|
4152
|
-
} finally {
|
|
4153
|
-
this.restarting = false;
|
|
4154
|
-
}
|
|
4155
|
-
}
|
|
4156
|
-
/**
|
|
4157
|
-
* Start video stream.
|
|
4158
|
-
* Listens to `push` events and processes cmd_id=3 frames carrying BcMedia packets.
|
|
4159
|
-
*/
|
|
4160
|
-
async start() {
|
|
4161
|
-
if (this.active) {
|
|
4162
|
-
throw new Error("Video stream already active");
|
|
4163
|
-
}
|
|
4164
|
-
this.depacketizer.reset();
|
|
4165
|
-
this.depacketizerH265.reset();
|
|
4166
|
-
let totalFramesReceived = 0;
|
|
4167
|
-
let totalMediaPackets = 0;
|
|
4168
|
-
this.videoFrameHandler = (frame) => {
|
|
4169
|
-
if (frame.header.cmdId !== this.cmdId) return;
|
|
4170
|
-
if (this.activeMsgNum !== void 0 && frame.header.msgNum !== this.activeMsgNum) {
|
|
4171
|
-
const allowMsgNum0Fallback = this.acceptAnyStreamType && frame.header.msgNum === 0;
|
|
4172
|
-
if (!allowMsgNum0Fallback) {
|
|
4173
|
-
const frameCount = this._msgNumMismatchCount = (this._msgNumMismatchCount || 0) + 1;
|
|
4174
|
-
if (frameCount <= 5 && this.client.getDebugConfig().general) {
|
|
4175
|
-
this.logger?.log(
|
|
4176
|
-
`[BaichuanVideoStream] Frame msgNum mismatch: received=${frame.header.msgNum}, expected=${this.activeMsgNum}, channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
|
|
4177
|
-
);
|
|
4178
|
-
}
|
|
4179
|
-
return;
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
if (!this.acceptAnyStreamType && !this.expectedStreamTypes.has(frame.header.streamType)) {
|
|
4183
|
-
const frameCount = this._streamTypeMismatchCount = (this._streamTypeMismatchCount || 0) + 1;
|
|
4184
|
-
if (frameCount <= 5 && this.client.getDebugConfig().general) {
|
|
4185
|
-
this.logger?.log(
|
|
4186
|
-
`[BaichuanVideoStream] Frame streamType mismatch: received=${frame.header.streamType}, expectedAny=[${[
|
|
4187
|
-
...this.expectedStreamTypes
|
|
4188
|
-
].join(
|
|
4189
|
-
","
|
|
4190
|
-
)}], channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
|
|
4191
|
-
);
|
|
4192
|
-
}
|
|
4193
|
-
return;
|
|
4194
|
-
}
|
|
4195
|
-
if (this.lockedChannelId === void 0) {
|
|
4196
|
-
this.lockedChannelId = frame.header.channelId;
|
|
4197
|
-
} else if (frame.header.channelId !== this.lockedChannelId) {
|
|
4198
|
-
const frameCount = this._channelIdMismatchCount = (this._channelIdMismatchCount || 0) + 1;
|
|
4199
|
-
if (frameCount <= 5) {
|
|
4200
|
-
this.logger?.warn(
|
|
4201
|
-
`[BaichuanVideoStream] Frame channelId mismatch: received=${frame.header.channelId}, locked=${this.lockedChannelId}, streamType=${frame.header.streamType}, msgNum=${frame.header.msgNum}, activeMsgNum=${this.activeMsgNum ?? "unknown"}, channel=${this.channel}, profile=${this.profile}, variant=${this.variant} (frame discarded)`
|
|
4202
|
-
);
|
|
4203
|
-
}
|
|
4204
|
-
return;
|
|
4205
|
-
}
|
|
4206
|
-
totalFramesReceived++;
|
|
4207
|
-
const dbg = this.client.getDebugConfig();
|
|
4208
|
-
const rtspDebug = dbg.debugRtsp;
|
|
4209
|
-
if (totalFramesReceived === 1) {
|
|
4210
|
-
if (rtspDebug) {
|
|
4211
|
-
this.logger?.log(
|
|
4212
|
-
`[BaichuanVideoStream] First cmd_id=${this.cmdId} frame received (bodyLen: ${frame.body.length}, channelId: ${frame.header.channelId})`
|
|
4213
|
-
);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
if (totalFramesReceived % 10 === 0 || totalFramesReceived <= 5) {
|
|
4217
|
-
if (rtspDebug) {
|
|
4218
|
-
this.logger?.log(
|
|
4219
|
-
`[BaichuanVideoStream] Received ${totalFramesReceived} Baichuan frames (cmd_id=${this.cmdId})`
|
|
4220
|
-
);
|
|
4221
|
-
}
|
|
4222
|
-
}
|
|
4223
|
-
const enc = this.client.enc;
|
|
4224
|
-
const rawCandidate = frame.payload.length > 0 ? frame.payload : frame.body;
|
|
4225
|
-
let dataToParse = rawCandidate;
|
|
4226
|
-
if (frame.payload.length === 0) {
|
|
4227
|
-
let searchStart = 0;
|
|
4228
|
-
const extensionEnd = rawCandidate.indexOf(Buffer.from("</Extension>"));
|
|
4229
|
-
const bodyEnd = rawCandidate.indexOf(Buffer.from("</body>"));
|
|
4230
|
-
if (extensionEnd !== -1)
|
|
4231
|
-
searchStart = extensionEnd + Buffer.from("</Extension>").length;
|
|
4232
|
-
else if (bodyEnd !== -1)
|
|
4233
|
-
searchStart = bodyEnd + Buffer.from("</body>").length;
|
|
4234
|
-
dataToParse = rawCandidate.subarray(searchStart);
|
|
4235
|
-
}
|
|
4236
|
-
let encryptLen;
|
|
4237
|
-
if (frame.extension && frame.extension.length > 0) {
|
|
4238
|
-
try {
|
|
4239
|
-
const extDec = this.client.tryDecryptXml(
|
|
4240
|
-
frame.extension,
|
|
4241
|
-
frame.header.channelId,
|
|
4242
|
-
enc
|
|
4243
|
-
);
|
|
4244
|
-
const encryptLenMatch = extDec.match(
|
|
4245
|
-
/<encryptLen>(\d+)<\/encryptLen>/i
|
|
4246
|
-
);
|
|
4247
|
-
if (encryptLenMatch && encryptLenMatch[1]) {
|
|
4248
|
-
encryptLen = parseInt(encryptLenMatch[1], 10);
|
|
4249
|
-
}
|
|
4250
|
-
} catch {
|
|
4251
|
-
}
|
|
4252
|
-
}
|
|
4253
|
-
const dataAfterXml = this.chooseDecryptedOrRawCandidate({
|
|
4254
|
-
raw: dataToParse,
|
|
4255
|
-
enc,
|
|
4256
|
-
channelId: frame.header.channelId,
|
|
4257
|
-
// Some NVR/Hub streams appear to include non-media bytes even when payloadOffset is present.
|
|
4258
|
-
// Allow a one-time resync at startup to avoid delaying the first keyframe.
|
|
4259
|
-
allowResync: frame.payload.length === 0 || totalFramesReceived <= 10 && totalMediaPackets === 0,
|
|
4260
|
-
...encryptLen !== void 0 ? { encryptLen } : {}
|
|
4261
|
-
});
|
|
4262
|
-
if (this.bcMediaCodec.getRemainingBuffer().length === 0 && dataAfterXml.length <= 600) {
|
|
4263
|
-
const s = _BaichuanVideoStream.scoreBcMediaLike(dataAfterXml);
|
|
4264
|
-
if (s.first < 0) {
|
|
4265
|
-
return;
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
if (totalFramesReceived === 1) {
|
|
4269
|
-
if (rtspDebug) {
|
|
4270
|
-
this.logger?.log(
|
|
4271
|
-
`[BaichuanVideoStream] Data after XML: ${dataAfterXml.length} bytes, first 32 bytes: ${dataAfterXml.subarray(0, Math.min(32, dataAfterXml.length)).toString("hex")}`
|
|
4272
|
-
);
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
if (dbg.dumpEnabled) ensureDumpDir(dbg);
|
|
4276
|
-
if (dbg.dumpBcMedia && this.dumpChunkIdx < 200) {
|
|
4277
|
-
const outDir = dbg.dumpDir;
|
|
4278
|
-
const idx = String(this.dumpChunkIdx).padStart(4, "0");
|
|
4279
|
-
const chunkPath = path3.join(outDir, `bcmedia_chunk_${idx}.bin`);
|
|
4280
|
-
const infoPath = path3.join(outDir, "bcmedia_info.json");
|
|
4281
|
-
const chunk = Buffer.from(dataAfterXml);
|
|
4282
|
-
const writeInfo = this.dumpChunkIdx === 0;
|
|
4283
|
-
const infoJson = writeInfo ? JSON.stringify(
|
|
4284
|
-
{
|
|
4285
|
-
note: "Chunks fed into the BcMedia decoder (after XML stripping/alignment).",
|
|
4286
|
-
profile: this.profile,
|
|
4287
|
-
channel: this.channel,
|
|
4288
|
-
encKind: this.client.enc.kind
|
|
4289
|
-
},
|
|
4290
|
-
null,
|
|
4291
|
-
2
|
|
4292
|
-
) : "";
|
|
4293
|
-
this.dumpIo.enqueue(async () => {
|
|
4294
|
-
await fs3.promises.writeFile(chunkPath, chunk);
|
|
4295
|
-
if (writeInfo) {
|
|
4296
|
-
await fs3.promises.writeFile(infoPath, infoJson);
|
|
4297
|
-
}
|
|
4298
|
-
});
|
|
4299
|
-
this.dumpChunkIdx++;
|
|
4300
|
-
}
|
|
4301
|
-
const mediaPackets = this.bcMediaCodec.decode(dataAfterXml);
|
|
4302
|
-
totalMediaPackets += mediaPackets.length;
|
|
4303
|
-
const packetTypes = /* @__PURE__ */ new Map();
|
|
4304
|
-
for (const pkt of mediaPackets) {
|
|
4305
|
-
packetTypes.set(pkt.type, (packetTypes.get(pkt.type) || 0) + 1);
|
|
4306
|
-
}
|
|
4307
|
-
let videoFramesEmitted = 0;
|
|
4308
|
-
let audioFramesEmitted = 0;
|
|
4309
|
-
for (const media of mediaPackets) {
|
|
4310
|
-
const maybeCacheParamSets = (annexB, source, videoType) => {
|
|
4311
|
-
if (videoType === "H264") {
|
|
4312
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
4313
|
-
for (const nal of nals) {
|
|
4314
|
-
const t = (nal[0] ?? 0) & 31;
|
|
4315
|
-
if (t === 7) {
|
|
4316
|
-
const id = parseSpsIdFromNal(nal);
|
|
4317
|
-
if (id != null) {
|
|
4318
|
-
if (!isPlausibleH264Sps(nal)) continue;
|
|
4319
|
-
this.spsById.set(id, nal);
|
|
4320
|
-
if (dbg.traceNativeStream) {
|
|
4321
|
-
this.logger?.warn(
|
|
4322
|
-
`[BaichuanVideoStream] Cached H.264 SPS id=${id} len=${nal.length}`
|
|
4323
|
-
);
|
|
4324
|
-
}
|
|
4325
|
-
}
|
|
4326
|
-
if (isPlausibleH264Sps(nal)) this.lastSps = nal;
|
|
4327
|
-
}
|
|
4328
|
-
if (t === 8) {
|
|
4329
|
-
const ids = parsePpsIdsFromNal(nal);
|
|
4330
|
-
if (ids) {
|
|
4331
|
-
const sps = this.spsById.get(ids.spsId);
|
|
4332
|
-
if (sps && !isPlausibleH264Sps(sps)) continue;
|
|
4333
|
-
this.ppsById.set(ids.ppsId, { nal, spsId: ids.spsId });
|
|
4334
|
-
if (dbg.traceNativeStream) {
|
|
4335
|
-
this.logger?.warn(
|
|
4336
|
-
`[BaichuanVideoStream] Cached H.264 PPS id=${ids.ppsId} (spsId=${ids.spsId}) len=${nal.length}`
|
|
4337
|
-
);
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
this.lastPps = nal;
|
|
4341
|
-
}
|
|
4342
|
-
}
|
|
4343
|
-
} else if (videoType === "H265") {
|
|
4344
|
-
const vps = extractVpsFromAnnexB(annexB);
|
|
4345
|
-
if (vps) {
|
|
4346
|
-
this.lastVps = vps;
|
|
4347
|
-
if (dbg.traceNativeStream) {
|
|
4348
|
-
this.logger?.warn(
|
|
4349
|
-
`[BaichuanVideoStream] Cached H.265 VPS len=${vps.length}`
|
|
4350
|
-
);
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
const sps = extractSpsFromAnnexB(annexB);
|
|
4354
|
-
if (sps) {
|
|
4355
|
-
this.lastSpsH265 = sps;
|
|
4356
|
-
if (dbg.traceNativeStream) {
|
|
4357
|
-
this.logger?.warn(
|
|
4358
|
-
`[BaichuanVideoStream] Cached H.265 SPS len=${sps.length}`
|
|
4359
|
-
);
|
|
4360
|
-
}
|
|
4361
|
-
}
|
|
4362
|
-
const pps = extractPpsFromAnnexB(annexB);
|
|
4363
|
-
if (pps) {
|
|
4364
|
-
this.lastPpsH265 = pps;
|
|
4365
|
-
if (dbg.traceNativeStream) {
|
|
4366
|
-
this.logger?.warn(
|
|
4367
|
-
`[BaichuanVideoStream] Cached H.265 PPS len=${pps.length}`
|
|
4368
|
-
);
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
}
|
|
4372
|
-
};
|
|
4373
|
-
const prependParamSetsIfNeeded = (annexB, videoType, isPframe = false) => {
|
|
4374
|
-
if (videoType === "H264") {
|
|
4375
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
4376
|
-
if (nals.length === 0) return annexB;
|
|
4377
|
-
const types = nals.map((n) => (n[0] ?? 0) & 31);
|
|
4378
|
-
const hasVcl = types.some(
|
|
4379
|
-
(t) => t === 1 || t === 5 || t === 19 || t === 20
|
|
4380
|
-
);
|
|
4381
|
-
if (isPframe && !hasVcl) {
|
|
4382
|
-
if (dbg.traceNativeStream) {
|
|
4383
|
-
this.logger?.warn(
|
|
4384
|
-
`[BaichuanVideoStream] Dropping P-frame without VCL (only param sets): types=${types.join(",")}`
|
|
4385
|
-
);
|
|
4386
|
-
}
|
|
4387
|
-
return Buffer.alloc(0);
|
|
4388
|
-
}
|
|
4389
|
-
if (types.includes(7) && types.includes(8)) {
|
|
4390
|
-
let ppsIdFromSlice = null;
|
|
4391
|
-
for (const nal of nals) {
|
|
4392
|
-
const t = (nal[0] ?? 0) & 31;
|
|
4393
|
-
if (t === 1 || t === 5) {
|
|
4394
|
-
ppsIdFromSlice = parseSlicePpsIdFromNal(nal);
|
|
4395
|
-
break;
|
|
4396
|
-
}
|
|
4397
|
-
}
|
|
4398
|
-
if (ppsIdFromSlice != null && ppsIdFromSlice <= 255) {
|
|
4399
|
-
this.lastPrependedPpsId = ppsIdFromSlice;
|
|
4400
|
-
} else {
|
|
4401
|
-
this.lastPrependedPpsId = -1;
|
|
4402
|
-
}
|
|
4403
|
-
return annexB;
|
|
4404
|
-
}
|
|
4405
|
-
if (!hasVcl) return annexB;
|
|
4406
|
-
let ppsId = null;
|
|
4407
|
-
for (const nal of nals) {
|
|
4408
|
-
const t = (nal[0] ?? 0) & 31;
|
|
4409
|
-
if (t === 1 || t === 5) {
|
|
4410
|
-
ppsId = parseSlicePpsIdFromNal(nal);
|
|
4411
|
-
break;
|
|
4412
|
-
}
|
|
4413
|
-
}
|
|
4414
|
-
if (dbg.traceNativeStream) {
|
|
4415
|
-
this.logger?.warn(
|
|
4416
|
-
`[BaichuanVideoStream] Slice references ppsId=${ppsId ?? "?"} lastPrepended=${this.lastPrependedPpsId ?? "?"}`
|
|
4417
|
-
);
|
|
4418
|
-
}
|
|
4419
|
-
if (ppsId == null || ppsId > 255) {
|
|
4420
|
-
if (this.lastPrependedPpsId != null) return annexB;
|
|
4421
|
-
if (!this.lastSps || !this.lastPps) return annexB;
|
|
4422
|
-
this.lastPrependedPpsId = -1;
|
|
4423
|
-
return Buffer.concat([
|
|
4424
|
-
NAL_START_CODE_4B3,
|
|
4425
|
-
this.lastSps,
|
|
4426
|
-
NAL_START_CODE_4B3,
|
|
4427
|
-
this.lastPps,
|
|
4428
|
-
annexB
|
|
4429
|
-
]);
|
|
4430
|
-
}
|
|
4431
|
-
if (this.lastPrependedPpsId === ppsId) return annexB;
|
|
4432
|
-
const pps = this.ppsById.get(ppsId);
|
|
4433
|
-
if (pps) {
|
|
4434
|
-
const sps = this.spsById.get(pps.spsId);
|
|
4435
|
-
if (sps) {
|
|
4436
|
-
this.lastPrependedPpsId = ppsId;
|
|
4437
|
-
return Buffer.concat([
|
|
4438
|
-
NAL_START_CODE_4B3,
|
|
4439
|
-
sps,
|
|
4440
|
-
NAL_START_CODE_4B3,
|
|
4441
|
-
pps.nal,
|
|
4442
|
-
annexB
|
|
4443
|
-
]);
|
|
4444
|
-
}
|
|
4445
|
-
}
|
|
4446
|
-
return Buffer.alloc(0);
|
|
4447
|
-
} else if (videoType === "H265") {
|
|
4448
|
-
const nals = splitAnnexBToNalPayloads2(annexB);
|
|
4449
|
-
if (nals.length === 0) return annexB;
|
|
4450
|
-
const types = nals.map((n) => getH265NalType(n)).filter((t) => t !== null);
|
|
4451
|
-
const hasVcl = types.some(
|
|
4452
|
-
(t) => t >= 0 && t <= 9 || t >= 16 && t <= 23
|
|
4453
|
-
);
|
|
4454
|
-
if (isPframe && !hasVcl) {
|
|
4455
|
-
if (dbg.traceNativeStream) {
|
|
4456
|
-
this.logger?.warn(
|
|
4457
|
-
`[BaichuanVideoStream] Dropping H.265 P-frame without VCL (only param sets): types=${types.join(",")}`
|
|
4458
|
-
);
|
|
4459
|
-
}
|
|
4460
|
-
return Buffer.alloc(0);
|
|
4461
|
-
}
|
|
4462
|
-
if (types.includes(32) && types.includes(33) && types.includes(34))
|
|
4463
|
-
return annexB;
|
|
4464
|
-
if (!hasVcl) return annexB;
|
|
4465
|
-
if (this.lastPrependedParamSetsH265) return annexB;
|
|
4466
|
-
if (!this.lastVps || !this.lastSpsH265 || !this.lastPpsH265)
|
|
4467
|
-
return annexB;
|
|
4468
|
-
this.lastPrependedParamSetsH265 = true;
|
|
4469
|
-
if (dbg.traceNativeStream) {
|
|
4470
|
-
this.logger?.warn(
|
|
4471
|
-
`[BaichuanVideoStream] Prepending H.265 VPS/SPS/PPS to frame`
|
|
4472
|
-
);
|
|
4473
|
-
}
|
|
4474
|
-
return Buffer.concat([
|
|
4475
|
-
NAL_START_CODE_4B3,
|
|
4476
|
-
this.lastVps,
|
|
4477
|
-
NAL_START_CODE_4B3,
|
|
4478
|
-
this.lastSpsH265,
|
|
4479
|
-
NAL_START_CODE_4B3,
|
|
4480
|
-
this.lastPpsH265,
|
|
4481
|
-
annexB
|
|
4482
|
-
]);
|
|
4483
|
-
}
|
|
4484
|
-
return annexB;
|
|
4485
|
-
};
|
|
4486
|
-
const dumpNalSummary = (annexB, label, microseconds) => {
|
|
4487
|
-
if (!dbg.dumpNals) return;
|
|
4488
|
-
try {
|
|
4489
|
-
if (dbg.dumpEnabled) ensureDumpDir(dbg);
|
|
4490
|
-
const outDir = dbg.dumpDir;
|
|
4491
|
-
const nals = splitAnnexBToNalPayloads(annexB);
|
|
4492
|
-
const types = nals.map((n) => (n[0] ?? 0) & 31);
|
|
4493
|
-
let slicePpsId = null;
|
|
4494
|
-
const spsIds = [];
|
|
4495
|
-
const ppsIds = [];
|
|
4496
|
-
for (const nal of nals) {
|
|
4497
|
-
const t = (nal[0] ?? 0) & 31;
|
|
4498
|
-
if ((t === 1 || t === 5) && slicePpsId == null) {
|
|
4499
|
-
slicePpsId = parseSlicePpsIdFromNal(nal);
|
|
4500
|
-
}
|
|
4501
|
-
if (t === 7) {
|
|
4502
|
-
const id = parseSpsIdFromNal(nal);
|
|
4503
|
-
if (id != null) spsIds.push(id);
|
|
4504
|
-
}
|
|
4505
|
-
if (t === 8) {
|
|
4506
|
-
const ids = parsePpsIdsFromNal(nal);
|
|
4507
|
-
if (ids) ppsIds.push(ids.ppsId);
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4510
|
-
if (this.dumpNalLines >= 2e4) return;
|
|
4511
|
-
const line = JSON.stringify({
|
|
4512
|
-
label,
|
|
4513
|
-
microseconds,
|
|
4514
|
-
len: annexB.length,
|
|
4515
|
-
nalTypes: types,
|
|
4516
|
-
slicePpsId,
|
|
4517
|
-
auSpsIds: spsIds,
|
|
4518
|
-
auPpsIds: ppsIds,
|
|
4519
|
-
cachedSps: this.spsById.size,
|
|
4520
|
-
cachedPps: this.ppsById.size,
|
|
4521
|
-
lastPrependedPpsId: this.lastPrependedPpsId
|
|
4522
|
-
}) + "\n";
|
|
4523
|
-
this.dumpNalLines++;
|
|
4524
|
-
const outPath = path3.join(outDir, "nal_dump.ndjson");
|
|
4525
|
-
this.dumpIo.enqueue(async () => {
|
|
4526
|
-
await fs3.promises.appendFile(outPath, line);
|
|
4527
|
-
});
|
|
4528
|
-
} catch {
|
|
4529
|
-
}
|
|
4530
|
-
};
|
|
4531
|
-
const isPlausibleH264Sps = (nal) => {
|
|
4532
|
-
if (nal.length < 4) return false;
|
|
4533
|
-
if (((nal[0] ?? 0) & 31) !== 7) return false;
|
|
4534
|
-
const profileIdc = nal[1] ?? 0;
|
|
4535
|
-
const levelIdc = nal[3] ?? 0;
|
|
4536
|
-
const knownProfiles = /* @__PURE__ */ new Set([66, 77, 88, 100, 110, 122, 244]);
|
|
4537
|
-
if (!knownProfiles.has(profileIdc)) return false;
|
|
4538
|
-
if (levelIdc === 0 || levelIdc > 255) return false;
|
|
4539
|
-
return true;
|
|
4540
|
-
};
|
|
4541
|
-
if (media.type === "Iframe") {
|
|
4542
|
-
let videoType = media.videoType;
|
|
4543
|
-
const detectedCodec = detectVideoCodecFromNal(media.data);
|
|
4544
|
-
if (detectedCodec && detectedCodec !== videoType) {
|
|
4545
|
-
if (dbg.traceNativeStream) {
|
|
4546
|
-
this.logger?.warn(
|
|
4547
|
-
`[BaichuanVideoStream] Codec mismatch in Iframe: header says ${videoType}, NAL says ${detectedCodec} - using ${detectedCodec}`
|
|
4548
|
-
);
|
|
4549
|
-
}
|
|
4550
|
-
videoType = detectedCodec;
|
|
4551
|
-
}
|
|
4552
|
-
const annexBData = videoType === "H265" ? convertToAnnexB2(media.data) : convertToAnnexB(media.data);
|
|
4553
|
-
const isKeyframe = true;
|
|
4554
|
-
maybeCacheParamSets(annexBData, "Iframe", videoType);
|
|
4555
|
-
const outAnnex = prependParamSetsIfNeeded(annexBData, videoType);
|
|
4556
|
-
if (outAnnex.length === 0) {
|
|
4557
|
-
if (dbg.traceNativeStream) {
|
|
4558
|
-
this.logger?.warn(
|
|
4559
|
-
`[BaichuanVideoStream] Iframe DROPPED: outAnnex is empty`
|
|
4560
|
-
);
|
|
4561
|
-
}
|
|
4562
|
-
continue;
|
|
4563
|
-
}
|
|
4564
|
-
dumpNalSummary(outAnnex, "Iframe", media.microseconds);
|
|
4565
|
-
if (videoType === "H264") {
|
|
4566
|
-
if (!isValidH264AnnexBAccessUnit(outAnnex) || !isH264KeyframeAnnexB(outAnnex)) {
|
|
4567
|
-
if (dbg.traceNativeStream) {
|
|
4568
|
-
this.logger?.warn(
|
|
4569
|
-
`[BaichuanVideoStream] Dropping invalid H.264 Iframe (Annex-B) len=${outAnnex.length}`
|
|
4570
|
-
);
|
|
4571
|
-
}
|
|
4572
|
-
continue;
|
|
4573
|
-
}
|
|
4574
|
-
} else if (videoType === "H265") {
|
|
4575
|
-
if (!isValidH265AnnexBAccessUnit(outAnnex)) {
|
|
4576
|
-
if (dbg.traceNativeStream) {
|
|
4577
|
-
this.logger?.warn(
|
|
4578
|
-
`[BaichuanVideoStream] Dropping invalid H.265 Iframe (Annex-B) len=${outAnnex.length} first16=${outAnnex.subarray(0, 16).toString("hex")}`
|
|
4579
|
-
);
|
|
4580
|
-
}
|
|
4581
|
-
continue;
|
|
4582
|
-
}
|
|
4583
|
-
if (!isH265KeyframeAnnexB(outAnnex)) {
|
|
4584
|
-
if (dbg.traceNativeStream) {
|
|
4585
|
-
this.logger?.warn(
|
|
4586
|
-
`[BaichuanVideoStream] H.265 Iframe missing VPS/SPS/PPS or IRAP, but continuing len=${outAnnex.length}`
|
|
4587
|
-
);
|
|
4588
|
-
}
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
if (dbg.traceNativeStream && !this.debugSavedSamples) {
|
|
4592
|
-
try {
|
|
4593
|
-
const outDir = dbg.dumpDir;
|
|
4594
|
-
fs3.mkdirSync(outDir, { recursive: true });
|
|
4595
|
-
if (media.type === "Iframe" && hasStartCodes(annexBData)) {
|
|
4596
|
-
fs3.writeFileSync(
|
|
4597
|
-
path3.join(outDir, "iframe_annexb.bin"),
|
|
4598
|
-
annexBData
|
|
4599
|
-
);
|
|
4600
|
-
}
|
|
4601
|
-
} catch {
|
|
4602
|
-
}
|
|
4603
|
-
}
|
|
4604
|
-
if (!this.warnedNonAnnexBOnce && !hasStartCodes(annexBData)) {
|
|
4605
|
-
this.warnedNonAnnexBOnce = true;
|
|
4606
|
-
const b = media.data;
|
|
4607
|
-
const head = b.subarray(0, Math.min(24, b.length)).toString("hex");
|
|
4608
|
-
const headAnnex = annexBData.subarray(0, Math.min(24, annexBData.length)).toString("hex");
|
|
4609
|
-
this.logger?.warn(
|
|
4610
|
-
`[BaichuanVideoStream] WARNING: non-AnnexB frame after conversion (${media.type} ${media.videoType}) len=${b.length} head=${head} convertedLen=${annexBData.length} convertedHead=${headAnnex}`
|
|
4611
|
-
);
|
|
4612
|
-
}
|
|
4613
|
-
this.emit("videoFrame", outAnnex);
|
|
4614
|
-
this.emit("videoAccessUnit", {
|
|
4615
|
-
data: outAnnex,
|
|
4616
|
-
isKeyframe,
|
|
4617
|
-
videoType,
|
|
4618
|
-
// Use the detected/corrected videoType, not media.videoType
|
|
4619
|
-
microseconds: media.microseconds,
|
|
4620
|
-
...media.type === "Iframe" && "time" in media ? media.time !== void 0 ? { time: media.time } : {} : {}
|
|
4621
|
-
});
|
|
4622
|
-
videoFramesEmitted++;
|
|
4623
|
-
if (totalFramesReceived <= 5 || videoFramesEmitted <= 5) {
|
|
4624
|
-
const sc = hasStartCodes(annexBData) ? "yes" : "no";
|
|
4625
|
-
if (rtspDebug) {
|
|
4626
|
-
this.logger?.log(
|
|
4627
|
-
`[BaichuanVideoStream] Emitted ${media.type} (${media.videoType}) ${media.data.length} bytes -> ${annexBData.length} bytes (Annex-B, startCode:${sc})`
|
|
4628
|
-
);
|
|
4629
|
-
}
|
|
4630
|
-
}
|
|
4631
|
-
} else if (media.type === "Pframe") {
|
|
4632
|
-
const chunk = media.data;
|
|
4633
|
-
let videoType = media.videoType;
|
|
4634
|
-
const detectedCodec = detectVideoCodecFromNal(chunk);
|
|
4635
|
-
if (detectedCodec && detectedCodec !== videoType) {
|
|
4636
|
-
videoType = detectedCodec;
|
|
4637
|
-
}
|
|
4638
|
-
const annexBOrRaw = hasStartCodes(chunk) ? chunk : videoType === "H265" ? convertToAnnexB2(chunk) : convertToAnnexB(chunk);
|
|
4639
|
-
const parts = hasStartCodes(annexBOrRaw) ? [annexBOrRaw] : videoType === "H265" ? this.depacketizerH265.push(chunk) : this.depacketizer.push(chunk);
|
|
4640
|
-
if (parts.length === 0) {
|
|
4641
|
-
continue;
|
|
4642
|
-
}
|
|
4643
|
-
for (const p of parts) {
|
|
4644
|
-
maybeCacheParamSets(p, "Pframe", videoType);
|
|
4645
|
-
const outP0 = prependParamSetsIfNeeded(p, videoType, true);
|
|
4646
|
-
if (outP0.length === 0) continue;
|
|
4647
|
-
const outP = outP0;
|
|
4648
|
-
dumpNalSummary(outP, "Pframe", media.microseconds);
|
|
4649
|
-
const isValid = videoType === "H265" ? isValidH265AnnexBAccessUnit(outP) : isValidH264AnnexBAccessUnit(outP);
|
|
4650
|
-
if (!isValid) {
|
|
4651
|
-
if (dbg.traceNativeStream && this.debugH264LogsLeft > 0) {
|
|
4652
|
-
this.debugH264LogsLeft--;
|
|
4653
|
-
const head = outP.subarray(0, Math.min(24, outP.length)).toString("hex");
|
|
4654
|
-
this.logger?.warn(
|
|
4655
|
-
`[BaichuanVideoStream] Dropping invalid Pframe (${videoType}): len=${outP.length} head=${head}`
|
|
4656
|
-
);
|
|
4657
|
-
}
|
|
4658
|
-
continue;
|
|
4659
|
-
}
|
|
4660
|
-
this.emit("videoFrame", outP);
|
|
4661
|
-
this.emit("videoAccessUnit", {
|
|
4662
|
-
data: outP,
|
|
4663
|
-
isKeyframe: false,
|
|
4664
|
-
videoType,
|
|
4665
|
-
microseconds: media.microseconds
|
|
4666
|
-
});
|
|
4667
|
-
videoFramesEmitted++;
|
|
4668
|
-
}
|
|
4669
|
-
}
|
|
4670
|
-
if (media.type === "Aac" || media.type === "Adpcm") {
|
|
4671
|
-
audioFramesEmitted++;
|
|
4672
|
-
this.emit("audioFrame", media.data);
|
|
4673
|
-
}
|
|
4674
|
-
if (media.type === "InfoV1" || media.type === "InfoV2") {
|
|
4675
|
-
}
|
|
4676
|
-
}
|
|
4677
|
-
if (totalFramesReceived <= 10 || totalFramesReceived % 20 === 0 && (videoFramesEmitted > 0 || audioFramesEmitted > 0)) {
|
|
4678
|
-
if (rtspDebug) {
|
|
4679
|
-
this.logger?.log(
|
|
4680
|
-
`[BaichuanVideoStream] Frame #${totalFramesReceived}: emitted ${videoFramesEmitted} video frames, ${audioFramesEmitted} audio frames`
|
|
4681
|
-
);
|
|
4682
|
-
}
|
|
4683
|
-
}
|
|
4684
|
-
if (videoFramesEmitted > 0 || audioFramesEmitted > 0) {
|
|
4685
|
-
this.noteMediaActivity();
|
|
4686
|
-
}
|
|
4687
|
-
if (videoFramesEmitted > 0 && (totalFramesReceived <= 10 || totalFramesReceived % 50 === 0)) {
|
|
4688
|
-
let totalVideoFrames = 0;
|
|
4689
|
-
}
|
|
4690
|
-
};
|
|
4691
|
-
this.client.on("push", this.videoFrameHandler);
|
|
4692
|
-
this.active = true;
|
|
4693
|
-
this.startWatchdog();
|
|
4694
|
-
this.lastMediaAtMs = Date.now();
|
|
4695
|
-
if (this.api) {
|
|
4696
|
-
try {
|
|
4697
|
-
if (this.variant === "default") {
|
|
4698
|
-
try {
|
|
4699
|
-
await this.api.stopVideoStream(this.channel, this.profile, {
|
|
4700
|
-
variant: "default",
|
|
4701
|
-
client: this.client
|
|
4702
|
-
});
|
|
4703
|
-
} catch {
|
|
4704
|
-
}
|
|
4705
|
-
} else {
|
|
4706
|
-
try {
|
|
4707
|
-
await this.api.stopVideoStream(this.channel, this.profile, {
|
|
4708
|
-
variant: this.variant,
|
|
4709
|
-
client: this.client
|
|
4710
|
-
});
|
|
4711
|
-
this.logger?.log(
|
|
4712
|
-
`[BaichuanVideoStream] Successfully stopped existing variant stream: ${this.variant}`
|
|
4713
|
-
);
|
|
4714
|
-
} catch (e) {
|
|
4715
|
-
this.logger?.log(
|
|
4716
|
-
`[BaichuanVideoStream] Error stopping variant stream ${this.variant} (may not exist): ${e instanceof Error ? e.message : String(e)}`
|
|
4717
|
-
);
|
|
4718
|
-
}
|
|
4719
|
-
}
|
|
4720
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4721
|
-
const startPromise = this.api.startVideoStream(
|
|
4722
|
-
this.channel,
|
|
4723
|
-
this.profile,
|
|
4724
|
-
{ variant: this.variant, client: this.client }
|
|
4725
|
-
);
|
|
4726
|
-
const updateActiveMsgNum = () => {
|
|
4727
|
-
try {
|
|
4728
|
-
const getMsgNum = this.api.getActiveVideoMsgNumWithVariant;
|
|
4729
|
-
const v = typeof getMsgNum === "function" ? getMsgNum(this.channel, this.profile, this.variant) : void 0;
|
|
4730
|
-
if (v !== void 0) this.activeMsgNum = v;
|
|
4731
|
-
} catch {
|
|
4732
|
-
}
|
|
4733
|
-
};
|
|
4734
|
-
updateActiveMsgNum();
|
|
4735
|
-
if (this.client.getTransport?.() === "udp") {
|
|
4736
|
-
await startPromise;
|
|
4737
|
-
} else {
|
|
4738
|
-
await Promise.race([
|
|
4739
|
-
startPromise,
|
|
4740
|
-
new Promise((resolve) => setTimeout(resolve, 400))
|
|
4741
|
-
]);
|
|
4742
|
-
}
|
|
4743
|
-
updateActiveMsgNum();
|
|
4744
|
-
void startPromise.then(() => updateActiveMsgNum()).catch((e) => {
|
|
4745
|
-
const err = e instanceof Error ? e : new Error(String(e));
|
|
4746
|
-
this.emitSafeError(err);
|
|
4747
|
-
});
|
|
4748
|
-
} catch (error) {
|
|
4749
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
4750
|
-
if (this.client.getTransport?.() === "udp") {
|
|
4751
|
-
this.stopWatchdog();
|
|
4752
|
-
this.active = false;
|
|
4753
|
-
if (this.videoFrameHandler)
|
|
4754
|
-
this.client.off("push", this.videoFrameHandler);
|
|
4755
|
-
this.videoFrameHandler = void 0;
|
|
4756
|
-
throw err;
|
|
4757
|
-
}
|
|
4758
|
-
this.emitSafeError(err);
|
|
4759
|
-
}
|
|
4760
|
-
}
|
|
4761
|
-
}
|
|
4762
|
-
// isVideoFrame, isAudioFrame, and extractVideoData are no longer needed
|
|
4763
|
-
// BcMediaParser handles all frame parsing and extraction
|
|
4764
|
-
/**
|
|
4765
|
-
* Stop video stream.
|
|
4766
|
-
*/
|
|
4767
|
-
async stop() {
|
|
4768
|
-
if (!this.active) return;
|
|
4769
|
-
this.stopWatchdog();
|
|
4770
|
-
this.depacketizer.reset();
|
|
4771
|
-
this.depacketizerH265.reset();
|
|
4772
|
-
if (this.videoFrameHandler) {
|
|
4773
|
-
this.client.removeListener("push", this.videoFrameHandler);
|
|
4774
|
-
}
|
|
4775
|
-
this.videoFrameHandler = void 0;
|
|
4776
|
-
this.bcMediaCodec.clear();
|
|
4777
|
-
this.activeMsgNum = void 0;
|
|
4778
|
-
if (this.api) {
|
|
4779
|
-
try {
|
|
4780
|
-
await this.api.stopVideoStream(this.channel, this.profile, {
|
|
4781
|
-
variant: this.variant,
|
|
4782
|
-
client: this.client
|
|
4783
|
-
});
|
|
4784
|
-
} catch (error) {
|
|
4785
|
-
this.emitSafeError(
|
|
4786
|
-
error instanceof Error ? error : new Error(String(error))
|
|
4787
|
-
);
|
|
4788
|
-
}
|
|
4789
|
-
}
|
|
4790
|
-
this.active = false;
|
|
4791
|
-
this.emit("close");
|
|
4792
|
-
}
|
|
4793
|
-
isActive() {
|
|
4794
|
-
return this.active;
|
|
4795
|
-
}
|
|
4796
|
-
};
|
|
4797
|
-
|
|
4798
2343
|
// src/rtsp/urls.ts
|
|
4799
2344
|
function buildRtspPath(channel, stream) {
|
|
4800
2345
|
const ch = String(channel + 1).padStart(2, "0");
|
|
@@ -4810,10 +2355,10 @@ function buildRtspPath(channel, stream) {
|
|
|
4810
2355
|
}
|
|
4811
2356
|
function buildRtspUrl(params) {
|
|
4812
2357
|
const port = params.port ?? 554;
|
|
4813
|
-
const
|
|
2358
|
+
const path3 = buildRtspPath(params.channel, params.stream);
|
|
4814
2359
|
const user = encodeURIComponent(params.username);
|
|
4815
2360
|
const pass = encodeURIComponent(params.password);
|
|
4816
|
-
return `rtsp://${user}:${pass}@${params.host}:${port}${
|
|
2361
|
+
return `rtsp://${user}:${pass}@${params.host}:${port}${path3}`;
|
|
4817
2362
|
}
|
|
4818
2363
|
|
|
4819
2364
|
// src/protocol/xml.ts
|
|
@@ -5046,6 +2591,15 @@ function applyXmlTagPatch(xml, tag, value) {
|
|
|
5046
2591
|
const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
|
|
5047
2592
|
return xml.replace(re, `<${tag}>${v}</${tag}>`);
|
|
5048
2593
|
}
|
|
2594
|
+
function upsertXmlTag(xml, tag, value) {
|
|
2595
|
+
if (value === void 0) return xml;
|
|
2596
|
+
const v = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
2597
|
+
const re = new RegExp(`<${tag}>[^<]*</${tag}>`);
|
|
2598
|
+
if (re.test(xml)) {
|
|
2599
|
+
return xml.replace(re, `<${tag}>${v}</${tag}>`);
|
|
2600
|
+
}
|
|
2601
|
+
return `${xml}<${tag}>${v}</${tag}>`;
|
|
2602
|
+
}
|
|
5049
2603
|
function patchNestedTag(xml, parent, child, value) {
|
|
5050
2604
|
if (value === void 0) return xml;
|
|
5051
2605
|
const v = typeof value === "boolean" ? value ? 1 : 0 : value;
|
|
@@ -5061,6 +2615,15 @@ function applyStreamPatch(xml, streamTag, patch) {
|
|
|
5061
2615
|
);
|
|
5062
2616
|
return xml.replace(re, (_match, open, body, close) => {
|
|
5063
2617
|
let next = body;
|
|
2618
|
+
if (patch.audio !== void 0) {
|
|
2619
|
+
next = applyXmlTagPatch(next, "audio", patch.audio);
|
|
2620
|
+
}
|
|
2621
|
+
if (patch.width !== void 0) {
|
|
2622
|
+
next = applyXmlTagPatch(next, "width", patch.width);
|
|
2623
|
+
}
|
|
2624
|
+
if (patch.height !== void 0) {
|
|
2625
|
+
next = applyXmlTagPatch(next, "height", patch.height);
|
|
2626
|
+
}
|
|
5064
2627
|
if (patch.bitRate !== void 0) {
|
|
5065
2628
|
next = applyXmlTagPatch(next, "bitRate", patch.bitRate);
|
|
5066
2629
|
}
|
|
@@ -5072,6 +2635,23 @@ function applyStreamPatch(xml, streamTag, patch) {
|
|
|
5072
2635
|
const intVal = patch.videoEncType === "h265" ? 1 : 0;
|
|
5073
2636
|
next = applyXmlTagPatch(next, "videoEncType", intVal);
|
|
5074
2637
|
}
|
|
2638
|
+
if (patch.encoderType !== void 0) {
|
|
2639
|
+
next = upsertXmlTag(next, "encoderType", patch.encoderType);
|
|
2640
|
+
}
|
|
2641
|
+
if (patch.encoderProfile !== void 0) {
|
|
2642
|
+
next = upsertXmlTag(next, "encoderProfile", patch.encoderProfile);
|
|
2643
|
+
}
|
|
2644
|
+
if (patch.gop !== void 0) {
|
|
2645
|
+
const gopBlockRe = /(<gop[^>]*>)([\s\S]*?)(<\/gop>)/;
|
|
2646
|
+
if (gopBlockRe.test(next)) {
|
|
2647
|
+
next = next.replace(
|
|
2648
|
+
gopBlockRe,
|
|
2649
|
+
(_m, gOpen, gBody, gClose) => `${gOpen}${applyXmlTagPatch(gBody, "cur", patch.gop)}${gClose}`
|
|
2650
|
+
);
|
|
2651
|
+
} else {
|
|
2652
|
+
next = `${next}<gop><cur>${patch.gop}</cur></gop>`;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
5075
2655
|
return `${open}${next}${close}`;
|
|
5076
2656
|
});
|
|
5077
2657
|
}
|
|
@@ -5112,19 +2692,19 @@ async function tryCall(fn) {
|
|
|
5112
2692
|
}
|
|
5113
2693
|
}
|
|
5114
2694
|
function mkdirp(dir) {
|
|
5115
|
-
|
|
2695
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
5116
2696
|
}
|
|
5117
2697
|
function writeJson(filePath, obj) {
|
|
5118
|
-
mkdirp(
|
|
5119
|
-
|
|
2698
|
+
mkdirp(path2.dirname(filePath));
|
|
2699
|
+
fs2.writeFileSync(filePath, JSON.stringify(obj, null, 2));
|
|
5120
2700
|
}
|
|
5121
2701
|
function writeText(filePath, text) {
|
|
5122
|
-
mkdirp(
|
|
5123
|
-
|
|
2702
|
+
mkdirp(path2.dirname(filePath));
|
|
2703
|
+
fs2.writeFileSync(filePath, text);
|
|
5124
2704
|
}
|
|
5125
2705
|
function appendNdjson(filePath, obj) {
|
|
5126
|
-
mkdirp(
|
|
5127
|
-
|
|
2706
|
+
mkdirp(path2.dirname(filePath));
|
|
2707
|
+
fs2.appendFileSync(filePath, JSON.stringify(obj) + "\n");
|
|
5128
2708
|
}
|
|
5129
2709
|
function sleepMs(ms) {
|
|
5130
2710
|
return new Promise((r) => setTimeout(r, ms));
|
|
@@ -5234,7 +2814,7 @@ async function createDiagnosticsBundle(params) {
|
|
|
5234
2814
|
cgi,
|
|
5235
2815
|
...params.extra ? { extra: params.extra } : {}
|
|
5236
2816
|
};
|
|
5237
|
-
const diagnosticsPath =
|
|
2817
|
+
const diagnosticsPath = path2.join(outDir, "diagnostics.json");
|
|
5238
2818
|
writeJson(diagnosticsPath, diagnostics);
|
|
5239
2819
|
return { outDir, diagnosticsPath };
|
|
5240
2820
|
}
|
|
@@ -5248,8 +2828,8 @@ function sanitizeFfmpegError(error) {
|
|
|
5248
2828
|
}
|
|
5249
2829
|
function spawnFfmpeg(args, logPath) {
|
|
5250
2830
|
return new Promise((resolve) => {
|
|
5251
|
-
mkdirp(
|
|
5252
|
-
const logStream =
|
|
2831
|
+
mkdirp(path2.dirname(logPath));
|
|
2832
|
+
const logStream = fs2.createWriteStream(logPath, { flags: "a" });
|
|
5253
2833
|
const p = spawn("ffmpeg", args, { stdio: ["ignore", "ignore", "pipe"] });
|
|
5254
2834
|
p.on("error", (e) => {
|
|
5255
2835
|
logStream.write(
|
|
@@ -5279,8 +2859,8 @@ ${stderr.slice(-4e3)}`;
|
|
|
5279
2859
|
}
|
|
5280
2860
|
function spawnFfprobeJson(args, logPath) {
|
|
5281
2861
|
return new Promise((resolve) => {
|
|
5282
|
-
mkdirp(
|
|
5283
|
-
const logStream =
|
|
2862
|
+
mkdirp(path2.dirname(logPath));
|
|
2863
|
+
const logStream = fs2.createWriteStream(logPath, { flags: "a" });
|
|
5284
2864
|
const p = spawn("ffprobe", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
5285
2865
|
p.on("error", (e) => {
|
|
5286
2866
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -5498,7 +3078,7 @@ async function sampleStreams(opts) {
|
|
|
5498
3078
|
snapshotIntervalSeconds,
|
|
5499
3079
|
selection: opts.selection
|
|
5500
3080
|
});
|
|
5501
|
-
const eventsPath =
|
|
3081
|
+
const eventsPath = path2.join(outDir, "events.ndjson");
|
|
5502
3082
|
appendNdjson(eventsPath, {
|
|
5503
3083
|
t: Date.now(),
|
|
5504
3084
|
type: "sampling_start",
|
|
@@ -5510,7 +3090,7 @@ async function sampleStreams(opts) {
|
|
|
5510
3090
|
for (const profile of selectedProfiles) {
|
|
5511
3091
|
for (const kind of opts.selection.kinds) {
|
|
5512
3092
|
const tag = `${kind}_${profile}`;
|
|
5513
|
-
const baseDir =
|
|
3093
|
+
const baseDir = path2.join(outDir, tag);
|
|
5514
3094
|
mkdirp(baseDir);
|
|
5515
3095
|
appendNdjson(eventsPath, {
|
|
5516
3096
|
t: Date.now(),
|
|
@@ -5537,8 +3117,8 @@ async function sampleStreams(opts) {
|
|
|
5537
3117
|
});
|
|
5538
3118
|
continue;
|
|
5539
3119
|
}
|
|
5540
|
-
const rawDir =
|
|
5541
|
-
const snapsDir =
|
|
3120
|
+
const rawDir = path2.join(baseDir, "raw_frames");
|
|
3121
|
+
const snapsDir = path2.join(baseDir, "snapshots");
|
|
5542
3122
|
mkdirp(rawDir);
|
|
5543
3123
|
mkdirp(snapsDir);
|
|
5544
3124
|
const maxRawFrames = opts.limits?.maxNativeRawFrames ?? 400;
|
|
@@ -5557,9 +3137,9 @@ async function sampleStreams(opts) {
|
|
|
5557
3137
|
rawFrames++;
|
|
5558
3138
|
rawBytes += payload.length;
|
|
5559
3139
|
const idx = String(rawFrames).padStart(6, "0");
|
|
5560
|
-
const binPath =
|
|
3140
|
+
const binPath = path2.join(rawDir, `frame_${idx}.bin`);
|
|
5561
3141
|
try {
|
|
5562
|
-
|
|
3142
|
+
fs2.writeFileSync(binPath, payload);
|
|
5563
3143
|
} catch {
|
|
5564
3144
|
}
|
|
5565
3145
|
appendNdjson(eventsPath, {
|
|
@@ -5581,12 +3161,12 @@ async function sampleStreams(opts) {
|
|
|
5581
3161
|
channel,
|
|
5582
3162
|
profile
|
|
5583
3163
|
});
|
|
5584
|
-
const clipBase =
|
|
3164
|
+
const clipBase = path2.join(baseDir, `clip_${nowIsoCompact()}`);
|
|
5585
3165
|
const clipAnnexBPath = clipBase + ".annexb";
|
|
5586
3166
|
const clipAudioPath = clipBase + ".audio.bin";
|
|
5587
3167
|
const clipInfoPath = clipBase + ".json";
|
|
5588
|
-
const videoOut =
|
|
5589
|
-
const audioOut =
|
|
3168
|
+
const videoOut = fs2.createWriteStream(clipAnnexBPath, { flags: "w" });
|
|
3169
|
+
const audioOut = fs2.createWriteStream(clipAudioPath, { flags: "w" });
|
|
5590
3170
|
let firstVideoType;
|
|
5591
3171
|
let firstKeyframeAtMs = null;
|
|
5592
3172
|
let videoAUs = 0;
|
|
@@ -5620,12 +3200,12 @@ async function sampleStreams(opts) {
|
|
|
5620
3200
|
if (u.isKeyframe && now - lastSnapshotAtMs >= snapshotIntervalSeconds * 1e3) {
|
|
5621
3201
|
lastSnapshotAtMs = now;
|
|
5622
3202
|
const snapId = nowIsoCompact();
|
|
5623
|
-
const snapAnnex =
|
|
3203
|
+
const snapAnnex = path2.join(
|
|
5624
3204
|
snapsDir,
|
|
5625
3205
|
`snap_${snapId}.${u.videoType === "H265" ? "h265" : "h264"}`
|
|
5626
3206
|
);
|
|
5627
3207
|
try {
|
|
5628
|
-
|
|
3208
|
+
fs2.writeFileSync(snapAnnex, u.data);
|
|
5629
3209
|
appendNdjson(eventsPath, {
|
|
5630
3210
|
t: Date.now(),
|
|
5631
3211
|
type: "native_snapshot_saved",
|
|
@@ -5633,12 +3213,12 @@ async function sampleStreams(opts) {
|
|
|
5633
3213
|
profile,
|
|
5634
3214
|
path: snapAnnex
|
|
5635
3215
|
});
|
|
5636
|
-
const snapJpeg =
|
|
3216
|
+
const snapJpeg = path2.join(snapsDir, `snap_${snapId}.jpg`);
|
|
5637
3217
|
void tryJpegFromAnnexB({
|
|
5638
3218
|
videoType: u.videoType,
|
|
5639
3219
|
snapshotAnnexBPath: snapAnnex,
|
|
5640
3220
|
outputJpegPath: snapJpeg,
|
|
5641
|
-
logPath:
|
|
3221
|
+
logPath: path2.join(baseDir, "ffmpeg_snapshot.log")
|
|
5642
3222
|
});
|
|
5643
3223
|
} catch {
|
|
5644
3224
|
}
|
|
@@ -5742,11 +3322,11 @@ async function sampleStreams(opts) {
|
|
|
5742
3322
|
stream: profile,
|
|
5743
3323
|
...opts.rtsp.port != null ? { port: opts.rtsp.port } : {}
|
|
5744
3324
|
});
|
|
5745
|
-
const mp4Path =
|
|
5746
|
-
const logPath =
|
|
5747
|
-
const snapsDir =
|
|
3325
|
+
const mp4Path = path2.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
|
|
3326
|
+
const logPath = path2.join(baseDir, "ffmpeg_record.log");
|
|
3327
|
+
const snapsDir = path2.join(baseDir, "snapshots");
|
|
5748
3328
|
mkdirp(snapsDir);
|
|
5749
|
-
const snapsPattern =
|
|
3329
|
+
const snapsPattern = path2.join(snapsDir, "snap_%05d.jpg");
|
|
5750
3330
|
appendNdjson(eventsPath, {
|
|
5751
3331
|
t: Date.now(),
|
|
5752
3332
|
type: "rtsp_url",
|
|
@@ -5768,7 +3348,7 @@ async function sampleStreams(opts) {
|
|
|
5768
3348
|
snapshotsPattern: snapsPattern,
|
|
5769
3349
|
durationSeconds: opts.durationSeconds,
|
|
5770
3350
|
snapshotIntervalSeconds,
|
|
5771
|
-
logPath:
|
|
3351
|
+
logPath: path2.join(baseDir, "ffmpeg_snapshots.log")
|
|
5772
3352
|
})
|
|
5773
3353
|
]);
|
|
5774
3354
|
appendNdjson(eventsPath, {
|
|
@@ -5807,11 +3387,11 @@ async function sampleStreams(opts) {
|
|
|
5807
3387
|
});
|
|
5808
3388
|
continue;
|
|
5809
3389
|
}
|
|
5810
|
-
const mp4Path =
|
|
5811
|
-
const logPath =
|
|
5812
|
-
const snapsDir =
|
|
3390
|
+
const mp4Path = path2.join(baseDir, `clip_${nowIsoCompact()}.mp4`);
|
|
3391
|
+
const logPath = path2.join(baseDir, "ffmpeg_record.log");
|
|
3392
|
+
const snapsDir = path2.join(baseDir, "snapshots");
|
|
5813
3393
|
mkdirp(snapsDir);
|
|
5814
|
-
const snapsPattern =
|
|
3394
|
+
const snapsPattern = path2.join(snapsDir, "snap_%05d.jpg");
|
|
5815
3395
|
appendNdjson(eventsPath, {
|
|
5816
3396
|
t: Date.now(),
|
|
5817
3397
|
type: "rtmp_url",
|
|
@@ -5833,7 +3413,7 @@ async function sampleStreams(opts) {
|
|
|
5833
3413
|
snapshotsPattern: snapsPattern,
|
|
5834
3414
|
durationSeconds: opts.durationSeconds,
|
|
5835
3415
|
snapshotIntervalSeconds,
|
|
5836
|
-
logPath:
|
|
3416
|
+
logPath: path2.join(baseDir, "ffmpeg_snapshots.log")
|
|
5837
3417
|
})
|
|
5838
3418
|
]);
|
|
5839
3419
|
appendNdjson(eventsPath, {
|
|
@@ -6547,8 +4127,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6547
4127
|
else console.log(msg);
|
|
6548
4128
|
};
|
|
6549
4129
|
const runDir = params.outDir;
|
|
6550
|
-
const streamsDir =
|
|
6551
|
-
const logsDir =
|
|
4130
|
+
const streamsDir = join3(runDir, "streams");
|
|
4131
|
+
const logsDir = join3(runDir, "logs");
|
|
6552
4132
|
mkdirp(runDir);
|
|
6553
4133
|
mkdirp(streamsDir);
|
|
6554
4134
|
mkdirp(logsDir);
|
|
@@ -6620,8 +4200,8 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6620
4200
|
for (const app of rtmpApps) {
|
|
6621
4201
|
for (const streamName of streams) {
|
|
6622
4202
|
const streamType = streamName.includes("sub") || streamName === "sub" || streamName === "mobile" ? 1 : 0;
|
|
6623
|
-
const
|
|
6624
|
-
const u = new URL(`rtmp://${params.host}:1935${
|
|
4203
|
+
const path3 = `/${app}/channel${params.channel}_${streamName}.bcs`;
|
|
4204
|
+
const u = new URL(`rtmp://${params.host}:1935${path3}`);
|
|
6625
4205
|
u.searchParams.set("channel", params.channel.toString());
|
|
6626
4206
|
u.searchParams.set("stream", streamType.toString());
|
|
6627
4207
|
u.searchParams.set("user", params.username);
|
|
@@ -6646,9 +4226,9 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6646
4226
|
/[^a-zA-Z0-9._-]+/g,
|
|
6647
4227
|
"_"
|
|
6648
4228
|
);
|
|
6649
|
-
const outPath =
|
|
6650
|
-
const probeLog =
|
|
6651
|
-
const recLog =
|
|
4229
|
+
const outPath = join3(streamsDir, `${baseName}.mkv`);
|
|
4230
|
+
const probeLog = join3(logsDir, `${baseName}.ffprobe.log`);
|
|
4231
|
+
const recLog = join3(logsDir, `${baseName}.ffmpeg.log`);
|
|
6652
4232
|
const probe = await probeVideoInfo({
|
|
6653
4233
|
url: urlWithAuth,
|
|
6654
4234
|
kind: "rtsp",
|
|
@@ -6706,9 +4286,9 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6706
4286
|
/[^a-zA-Z0-9._-]+/g,
|
|
6707
4287
|
"_"
|
|
6708
4288
|
);
|
|
6709
|
-
const outPath =
|
|
6710
|
-
const probeLog =
|
|
6711
|
-
const recLog =
|
|
4289
|
+
const outPath = join3(streamsDir, `${baseName}.mkv`);
|
|
4290
|
+
const probeLog = join3(logsDir, `${baseName}.ffprobe.log`);
|
|
4291
|
+
const recLog = join3(logsDir, `${baseName}.ffmpeg.log`);
|
|
6712
4292
|
const probe = await probeVideoInfo({
|
|
6713
4293
|
url: cand.url,
|
|
6714
4294
|
kind: "rtmp",
|
|
@@ -6804,7 +4384,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6804
4384
|
/[^a-zA-Z0-9._-]+/g,
|
|
6805
4385
|
"_"
|
|
6806
4386
|
);
|
|
6807
|
-
const baseDir =
|
|
4387
|
+
const baseDir = join3(
|
|
6808
4388
|
streamsDir,
|
|
6809
4389
|
"native",
|
|
6810
4390
|
mode,
|
|
@@ -6812,12 +4392,12 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6812
4392
|
profile,
|
|
6813
4393
|
variant
|
|
6814
4394
|
);
|
|
6815
|
-
const rawDir =
|
|
6816
|
-
const snapsDir =
|
|
6817
|
-
const logsBase =
|
|
4395
|
+
const rawDir = join3(baseDir, "raw_frames");
|
|
4396
|
+
const snapsDir = join3(baseDir, "snapshots");
|
|
4397
|
+
const logsBase = join3(logsDir, baseName);
|
|
6818
4398
|
mkdirp(rawDir);
|
|
6819
4399
|
mkdirp(snapsDir);
|
|
6820
|
-
const eventsPath =
|
|
4400
|
+
const eventsPath = join3(baseDir, "events.ndjson");
|
|
6821
4401
|
appendNdjson(eventsPath, {
|
|
6822
4402
|
t: Date.now(),
|
|
6823
4403
|
type: "native_begin",
|
|
@@ -6851,15 +4431,15 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6851
4431
|
rawFrames++;
|
|
6852
4432
|
rawBytes += payload.length;
|
|
6853
4433
|
const idx = String(rawFrames).padStart(6, "0");
|
|
6854
|
-
const binPath =
|
|
4434
|
+
const binPath = join3(rawDir, `frame_${idx}.bin`);
|
|
6855
4435
|
try {
|
|
6856
|
-
|
|
4436
|
+
fs2.writeFileSync(binPath, payload);
|
|
6857
4437
|
} catch {
|
|
6858
4438
|
}
|
|
6859
4439
|
if (rawFrames === 1) {
|
|
6860
4440
|
try {
|
|
6861
4441
|
writeJson(
|
|
6862
|
-
|
|
4442
|
+
join3(baseDir, "first_frame_header.json"),
|
|
6863
4443
|
frame.header
|
|
6864
4444
|
);
|
|
6865
4445
|
} catch {
|
|
@@ -6875,13 +4455,13 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6875
4455
|
payloadLen: frame.payload?.length ?? 0
|
|
6876
4456
|
});
|
|
6877
4457
|
};
|
|
6878
|
-
const clipBase =
|
|
4458
|
+
const clipBase = join3(baseDir, `clip_${nowIsoCompact()}`);
|
|
6879
4459
|
const clipAnnexBPath = clipBase + ".annexb";
|
|
6880
4460
|
const clipAudioPath = clipBase + ".audio.bin";
|
|
6881
4461
|
const clipInfoPath = clipBase + ".json";
|
|
6882
4462
|
const ffmpegMuxLog = logsBase + ".ffmpeg_mux.log";
|
|
6883
|
-
const videoOut =
|
|
6884
|
-
const audioOut =
|
|
4463
|
+
const videoOut = fs2.createWriteStream(clipAnnexBPath, { flags: "w" });
|
|
4464
|
+
const audioOut = fs2.createWriteStream(clipAudioPath, { flags: "w" });
|
|
6885
4465
|
let firstVideoType;
|
|
6886
4466
|
let firstKeyframeAtMs = null;
|
|
6887
4467
|
let startedAtMs = null;
|
|
@@ -6923,24 +4503,24 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
6923
4503
|
if (u.isKeyframe && now - lastSnapshotAtMs >= 2e3) {
|
|
6924
4504
|
lastSnapshotAtMs = now;
|
|
6925
4505
|
const snapId = nowIsoCompact();
|
|
6926
|
-
const snapAnnex =
|
|
4506
|
+
const snapAnnex = join3(
|
|
6927
4507
|
snapsDir,
|
|
6928
4508
|
`snap_${snapId}.${u.videoType === "H265" ? "h265" : "h264"}`
|
|
6929
4509
|
);
|
|
6930
4510
|
try {
|
|
6931
|
-
|
|
4511
|
+
fs2.writeFileSync(snapAnnex, u.data);
|
|
6932
4512
|
appendNdjson(eventsPath, {
|
|
6933
4513
|
t: Date.now(),
|
|
6934
4514
|
type: "native_snapshot_saved",
|
|
6935
4515
|
id,
|
|
6936
4516
|
path: snapAnnex
|
|
6937
4517
|
});
|
|
6938
|
-
const snapJpeg =
|
|
4518
|
+
const snapJpeg = join3(snapsDir, `snap_${snapId}.jpg`);
|
|
6939
4519
|
void tryJpegFromAnnexB({
|
|
6940
4520
|
videoType: u.videoType,
|
|
6941
4521
|
snapshotAnnexBPath: snapAnnex,
|
|
6942
4522
|
outputJpegPath: snapJpeg,
|
|
6943
|
-
logPath:
|
|
4523
|
+
logPath: join3(baseDir, "ffmpeg_snapshot.log")
|
|
6944
4524
|
});
|
|
6945
4525
|
} catch {
|
|
6946
4526
|
}
|
|
@@ -7232,7 +4812,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
7232
4812
|
lockedMsgNum,
|
|
7233
4813
|
annexbPath: clipAnnexBPath,
|
|
7234
4814
|
audioPath: clipAudioPath,
|
|
7235
|
-
mkvPath:
|
|
4815
|
+
mkvPath: fs2.existsSync(mkvPath) ? mkvPath : void 0
|
|
7236
4816
|
};
|
|
7237
4817
|
if (info.mkvPath) {
|
|
7238
4818
|
const p = await spawnFfprobeJson(
|
|
@@ -7304,7 +4884,7 @@ async function runMultifocalDiagnosticsConsecutively(params) {
|
|
|
7304
4884
|
streamsDir,
|
|
7305
4885
|
okStreams: okIds
|
|
7306
4886
|
});
|
|
7307
|
-
const resultsPath =
|
|
4887
|
+
const resultsPath = join3(runDir, "multifocal_diagnostics.json");
|
|
7308
4888
|
writeJson(resultsPath, results);
|
|
7309
4889
|
return { runDir, resultsPath, streamsDir };
|
|
7310
4890
|
}
|
|
@@ -7322,7 +4902,7 @@ async function runAllDiagnosticsConsecutively(params) {
|
|
|
7322
4902
|
};
|
|
7323
4903
|
const baseOutDir = params.outDir;
|
|
7324
4904
|
const runDirName = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7325
|
-
const runDir =
|
|
4905
|
+
const runDir = join3(baseOutDir, runDirName);
|
|
7326
4906
|
log("[Diagnostics] starting run", {
|
|
7327
4907
|
outDir: baseOutDir,
|
|
7328
4908
|
runDir,
|
|
@@ -7353,7 +4933,7 @@ async function runAllDiagnosticsConsecutively(params) {
|
|
|
7353
4933
|
runDir: diagnosticsRes.outDir,
|
|
7354
4934
|
cgiEnabled
|
|
7355
4935
|
});
|
|
7356
|
-
const streamsDir =
|
|
4936
|
+
const streamsDir = join3(runDir, "streams");
|
|
7357
4937
|
const rtspEnabled = params.rtsp === true || typeof params.rtsp === "object" && params.rtsp != null;
|
|
7358
4938
|
const rtspCfg = rtspEnabled ? {
|
|
7359
4939
|
host: (typeof params.rtsp === "object" ? params.rtsp.host : void 0) ?? host,
|
|
@@ -7374,7 +4954,7 @@ async function runAllDiagnosticsConsecutively(params) {
|
|
|
7374
4954
|
...logger ? { logger } : {}
|
|
7375
4955
|
});
|
|
7376
4956
|
log("[Diagnostics] stream sampling completed", { streamsDir });
|
|
7377
|
-
const zipPath =
|
|
4957
|
+
const zipPath = join3(baseOutDir, `${runDirName}.zip`);
|
|
7378
4958
|
log("[Diagnostics] creating zip bundle", { zipPath });
|
|
7379
4959
|
await zipDirectory({ sourceDir: runDir, zipPath });
|
|
7380
4960
|
log("[Diagnostics] zip bundle created", { zipPath });
|
|
@@ -7524,122 +5104,122 @@ async function captureModelFixtures(params) {
|
|
|
7524
5104
|
const info = await capture(
|
|
7525
5105
|
"getInfo",
|
|
7526
5106
|
() => api.getInfo(channel),
|
|
7527
|
-
(v) => writeJsonSafe(
|
|
5107
|
+
(v) => writeJsonSafe(path2.join(outDir, "device-info.json"), v)
|
|
7528
5108
|
);
|
|
7529
5109
|
const support = await capture(
|
|
7530
5110
|
"getSupportInfo",
|
|
7531
5111
|
() => api.getSupportInfo(),
|
|
7532
|
-
(v) => writeJsonSafe(
|
|
5112
|
+
(v) => writeJsonSafe(path2.join(outDir, "support-info.json"), v)
|
|
7533
5113
|
);
|
|
7534
5114
|
const abilities = await capture(
|
|
7535
5115
|
"getAbilityInfo",
|
|
7536
5116
|
() => api.getAbilityInfo(),
|
|
7537
|
-
(v) => writeJsonSafe(
|
|
5117
|
+
(v) => writeJsonSafe(path2.join(outDir, "ability-info.json"), v)
|
|
7538
5118
|
);
|
|
7539
5119
|
await capture(
|
|
7540
5120
|
"getDeviceCapabilities",
|
|
7541
5121
|
() => api.getDeviceCapabilities(channel),
|
|
7542
|
-
(v) => writeJsonSafe(
|
|
5122
|
+
(v) => writeJsonSafe(path2.join(outDir, "capabilities.json"), v)
|
|
7543
5123
|
);
|
|
7544
5124
|
await capture("cmd289-WhiteLed", () => api.sendXml({
|
|
7545
5125
|
cmdId: BC_CMD_ID_GET_WHITE_LED,
|
|
7546
5126
|
channel,
|
|
7547
5127
|
timeoutMs: 3e3
|
|
7548
|
-
}), (v) => writeTextSafe(
|
|
5128
|
+
}), (v) => writeTextSafe(path2.join(outDir, "cmd289-white-led.xml"), v));
|
|
7549
5129
|
await capture(
|
|
7550
5130
|
"getStreamMetadata",
|
|
7551
5131
|
() => api.getStreamMetadata(channel),
|
|
7552
|
-
(v) => writeJsonSafe(
|
|
5132
|
+
(v) => writeJsonSafe(path2.join(outDir, "stream-metadata.json"), v)
|
|
7553
5133
|
);
|
|
7554
5134
|
await capture(
|
|
7555
5135
|
"getEncXml",
|
|
7556
5136
|
() => api.getEncXml(channel),
|
|
7557
|
-
(v) => writeTextSafe(
|
|
5137
|
+
(v) => writeTextSafe(path2.join(outDir, "enc-config.xml"), v)
|
|
7558
5138
|
);
|
|
7559
5139
|
await capture(
|
|
7560
5140
|
"getPorts",
|
|
7561
5141
|
() => api.getPorts(),
|
|
7562
|
-
(v) => writeJsonSafe(
|
|
5142
|
+
(v) => writeJsonSafe(path2.join(outDir, "ports.json"), v)
|
|
7563
5143
|
);
|
|
7564
5144
|
await capture(
|
|
7565
5145
|
"getTalkAbility",
|
|
7566
5146
|
() => api.getTalkAbility(channel),
|
|
7567
|
-
(v) => writeJsonSafe(
|
|
5147
|
+
(v) => writeJsonSafe(path2.join(outDir, "talk-ability.json"), v)
|
|
7568
5148
|
);
|
|
7569
5149
|
await capture(
|
|
7570
5150
|
"getTwoWayAudioConfig",
|
|
7571
5151
|
() => api.getTwoWayAudioConfig(channel),
|
|
7572
|
-
(v) => writeJsonSafe(
|
|
5152
|
+
(v) => writeJsonSafe(path2.join(outDir, "two-way-audio-config.json"), v)
|
|
7573
5153
|
);
|
|
7574
5154
|
await capture(
|
|
7575
5155
|
"getAiState",
|
|
7576
5156
|
() => api.getAiState(channel),
|
|
7577
|
-
(v) => writeJsonSafe(
|
|
5157
|
+
(v) => writeJsonSafe(path2.join(outDir, "ai-state.json"), v)
|
|
7578
5158
|
);
|
|
7579
5159
|
await capture(
|
|
7580
5160
|
"getAiCfg",
|
|
7581
5161
|
() => api.getAiCfg(channel),
|
|
7582
|
-
(v) => writeJsonSafe(
|
|
5162
|
+
(v) => writeJsonSafe(path2.join(outDir, "ai-cfg.json"), v)
|
|
7583
5163
|
);
|
|
7584
5164
|
await capture(
|
|
7585
5165
|
"getOsd",
|
|
7586
5166
|
() => api.getOsd(channel),
|
|
7587
|
-
(v) => writeJsonSafe(
|
|
5167
|
+
(v) => writeJsonSafe(path2.join(outDir, "osd.json"), v)
|
|
7588
5168
|
);
|
|
7589
5169
|
await capture(
|
|
7590
5170
|
"getMotionAlarm",
|
|
7591
5171
|
() => api.getMotionAlarm(channel),
|
|
7592
|
-
(v) => writeJsonSafe(
|
|
5172
|
+
(v) => writeJsonSafe(path2.join(outDir, "motion-alarm.json"), v)
|
|
7593
5173
|
);
|
|
7594
5174
|
await capture(
|
|
7595
5175
|
"getRecordCfg",
|
|
7596
5176
|
() => api.getRecordCfg(channel),
|
|
7597
|
-
(v) => writeJsonSafe(
|
|
5177
|
+
(v) => writeJsonSafe(path2.join(outDir, "record-cfg.json"), v)
|
|
7598
5178
|
);
|
|
7599
5179
|
await capture(
|
|
7600
5180
|
"getVideoInput",
|
|
7601
5181
|
() => api.getVideoInput(channel),
|
|
7602
|
-
(v) => writeJsonSafe(
|
|
5182
|
+
(v) => writeJsonSafe(path2.join(outDir, "video-input.json"), v)
|
|
7603
5183
|
);
|
|
7604
5184
|
await capture(
|
|
7605
5185
|
"getPtzPresets",
|
|
7606
5186
|
() => api.getPtzPresets(channel),
|
|
7607
|
-
(v) => writeJsonSafe(
|
|
5187
|
+
(v) => writeJsonSafe(path2.join(outDir, "ptz-presets.json"), v)
|
|
7608
5188
|
);
|
|
7609
5189
|
await capture(
|
|
7610
5190
|
"getNetworkInfo",
|
|
7611
5191
|
() => api.getNetworkInfo(),
|
|
7612
|
-
(v) => writeJsonSafe(
|
|
5192
|
+
(v) => writeJsonSafe(path2.join(outDir, "network-info.json"), v)
|
|
7613
5193
|
);
|
|
7614
5194
|
await capture(
|
|
7615
5195
|
"getSystemGeneral",
|
|
7616
5196
|
() => api.getSystemGeneral(),
|
|
7617
|
-
(v) => writeJsonSafe(
|
|
5197
|
+
(v) => writeJsonSafe(path2.join(outDir, "system-general.json"), v)
|
|
7618
5198
|
);
|
|
7619
5199
|
await capture(
|
|
7620
5200
|
"getWifiSignal",
|
|
7621
5201
|
() => api.getWifiSignal(channel),
|
|
7622
|
-
(v) => writeJsonSafe(
|
|
5202
|
+
(v) => writeJsonSafe(path2.join(outDir, "wifi-signal.json"), v)
|
|
7623
5203
|
);
|
|
7624
5204
|
await capture(
|
|
7625
5205
|
"getWhiteLedState",
|
|
7626
5206
|
() => api.getWhiteLedState(channel),
|
|
7627
|
-
(v) => writeJsonSafe(
|
|
5207
|
+
(v) => writeJsonSafe(path2.join(outDir, "white-led-state.json"), v)
|
|
7628
5208
|
);
|
|
7629
5209
|
await capture(
|
|
7630
5210
|
"getFloodlightOnMotion",
|
|
7631
5211
|
() => api.getFloodlightOnMotion(channel),
|
|
7632
|
-
(v) => writeJsonSafe(
|
|
5212
|
+
(v) => writeJsonSafe(path2.join(outDir, "floodlight-on-motion.json"), v)
|
|
7633
5213
|
);
|
|
7634
5214
|
await capture(
|
|
7635
5215
|
"buildVideoStreamOptions",
|
|
7636
5216
|
() => api.buildVideoStreamOptions({ channel }),
|
|
7637
|
-
(v) => writeJsonSafe(
|
|
5217
|
+
(v) => writeJsonSafe(path2.join(outDir, "video-stream-options.json"), v)
|
|
7638
5218
|
);
|
|
7639
5219
|
await capture(
|
|
7640
5220
|
"getDualLensChannelInfo",
|
|
7641
5221
|
() => api.getDualLensChannelInfo(channel),
|
|
7642
|
-
(v) => writeJsonSafe(
|
|
5222
|
+
(v) => writeJsonSafe(path2.join(outDir, "dual-lens-info.json"), v)
|
|
7643
5223
|
);
|
|
7644
5224
|
if (!params.skipStreamCombinationTest) await capture("streamCombinationTest", async () => {
|
|
7645
5225
|
let dualLensInfo;
|
|
@@ -7781,12 +5361,12 @@ async function captureModelFixtures(params) {
|
|
|
7781
5361
|
unexpected: unexpected.map((r) => `${r.pair[0]}+${r.pair[1]}: got ${r.ok ? "OK" : "FAIL"}, expected ${r.expectedOk ? "OK" : "FAIL"}`)
|
|
7782
5362
|
}
|
|
7783
5363
|
};
|
|
7784
|
-
}, (v) => writeJsonSafe(
|
|
5364
|
+
}, (v) => writeJsonSafe(path2.join(outDir, "stream-combination-test.json"), v));
|
|
7785
5365
|
const total = Object.keys(calls).length;
|
|
7786
5366
|
const ok = Object.values(calls).filter((c) => c.ok).length;
|
|
7787
5367
|
const failed = total - ok;
|
|
7788
5368
|
const summary = { total, ok, failed, errors };
|
|
7789
|
-
writeJsonSafe(
|
|
5369
|
+
writeJsonSafe(path2.join(outDir, "_summary.json"), {
|
|
7790
5370
|
collectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7791
5371
|
model: info?.type ?? "unknown",
|
|
7792
5372
|
itemNo: info?.itemNo ?? "unknown",
|
|
@@ -7812,140 +5392,6 @@ async function captureModelFixtures(params) {
|
|
|
7812
5392
|
}
|
|
7813
5393
|
|
|
7814
5394
|
export {
|
|
7815
|
-
__require,
|
|
7816
|
-
BC_TCP_DEFAULT_PORT,
|
|
7817
|
-
BC_MAGIC,
|
|
7818
|
-
BC_MAGIC_REV,
|
|
7819
|
-
BC_XML_KEY,
|
|
7820
|
-
BC_AES_IV,
|
|
7821
|
-
BC_CLASS_LEGACY,
|
|
7822
|
-
BC_CLASS_MODERN_20,
|
|
7823
|
-
BC_CLASS_MODERN_24,
|
|
7824
|
-
BC_CLASS_MODERN_24_ALT,
|
|
7825
|
-
BC_CLASS_FILE_DOWNLOAD,
|
|
7826
|
-
bcHeaderHasPayloadOffset,
|
|
7827
|
-
BC_CMD_ID_LOGIN,
|
|
7828
|
-
BC_CMD_ID_LOGOUT,
|
|
7829
|
-
BC_CMD_ID_VIDEO,
|
|
7830
|
-
BC_CMD_ID_VIDEO_STOP,
|
|
7831
|
-
BC_CMD_ID_FILE_INFO_LIST_REPLAY,
|
|
7832
|
-
BC_CMD_ID_FILE_INFO_LIST_STOP,
|
|
7833
|
-
BC_CMD_ID_FILE_INFO_LIST_DL_VIDEO,
|
|
7834
|
-
BC_CMD_ID_FILE_INFO_LIST_DOWNLOAD,
|
|
7835
|
-
BC_CMD_ID_FILE_INFO_LIST_OPEN,
|
|
7836
|
-
BC_CMD_ID_FILE_INFO_LIST_GET,
|
|
7837
|
-
BC_CMD_ID_FILE_INFO_LIST_CLOSE,
|
|
7838
|
-
BC_CMD_ID_FIND_REC_VIDEO_OPEN,
|
|
7839
|
-
BC_CMD_ID_FIND_REC_VIDEO_GET,
|
|
7840
|
-
BC_CMD_ID_FIND_REC_VIDEO_CLOSE,
|
|
7841
|
-
BC_CMD_ID_COVER_PREVIEW,
|
|
7842
|
-
BC_CMD_ID_COVER_STANDALONE_458,
|
|
7843
|
-
BC_CMD_ID_COVER_STANDALONE_459,
|
|
7844
|
-
BC_CMD_ID_COVER_STANDALONE_460,
|
|
7845
|
-
BC_CMD_ID_COVER_STANDALONE_461,
|
|
7846
|
-
BC_CMD_ID_COVER_STANDALONE_462,
|
|
7847
|
-
BC_CMD_ID_COVER_RESPONSE,
|
|
7848
|
-
BC_CMD_ID_TALK_ABILITY,
|
|
7849
|
-
BC_CMD_ID_TALK_RESET,
|
|
7850
|
-
BC_CMD_ID_TALK_CONFIG,
|
|
7851
|
-
BC_CMD_ID_TALK,
|
|
7852
|
-
BC_CMD_ID_PTZ_CONTROL,
|
|
7853
|
-
BC_CMD_ID_PTZ_CONTROL_PRESET,
|
|
7854
|
-
BC_CMD_ID_GET_PTZ_PRESET,
|
|
7855
|
-
BC_CMD_ID_GET_PTZ_POSITION,
|
|
7856
|
-
BC_CMD_ID_GET_ZOOM_FOCUS,
|
|
7857
|
-
BC_CMD_ID_SET_ZOOM_FOCUS,
|
|
7858
|
-
BC_CMD_ID_GET_BATTERY_INFO_LIST,
|
|
7859
|
-
BC_CMD_ID_GET_BATTERY_INFO,
|
|
7860
|
-
BC_CMD_ID_UDP_KEEP_ALIVE,
|
|
7861
|
-
BC_CMD_ID_GET_PIR_INFO,
|
|
7862
|
-
BC_CMD_ID_SET_PIR_INFO,
|
|
7863
|
-
BC_CMD_ID_GET_MOTION_ALARM,
|
|
7864
|
-
BC_CMD_ID_SET_MOTION_ALARM,
|
|
7865
|
-
BC_CMD_ID_ALARM_EVENT_LIST,
|
|
7866
|
-
BC_CMD_ID_GET_AI_ALARM,
|
|
7867
|
-
BC_CMD_ID_SET_AI_ALARM,
|
|
7868
|
-
BC_CMD_ID_GET_AUDIO_ALARM,
|
|
7869
|
-
BC_CMD_ID_AUDIO_ALARM_PLAY,
|
|
7870
|
-
BC_CMD_ID_GET_WHITE_LED,
|
|
7871
|
-
BC_CMD_ID_SET_WHITE_LED_STATE,
|
|
7872
|
-
BC_CMD_ID_SET_WHITE_LED_TASK,
|
|
7873
|
-
BC_CMD_ID_FLOODLIGHT_STATUS_LIST,
|
|
7874
|
-
BC_CMD_ID_ABILITY_INFO,
|
|
7875
|
-
BC_CMD_ID_SUPPORT,
|
|
7876
|
-
BC_CMD_ID_PING,
|
|
7877
|
-
BC_CMD_ID_CHANNEL_INFO_ALL,
|
|
7878
|
-
BC_CMD_ID_GET_OSD_DATETIME,
|
|
7879
|
-
BC_CMD_ID_GET_RECORD_CFG,
|
|
7880
|
-
BC_CMD_ID_GET_ABILITY_SUPPORT,
|
|
7881
|
-
BC_CMD_ID_GET_FTP_TASK,
|
|
7882
|
-
BC_CMD_ID_GET_RECORD,
|
|
7883
|
-
BC_CMD_ID_GET_HDD_INFO_LIST,
|
|
7884
|
-
BC_CMD_ID_GET_WIFI_SIGNAL,
|
|
7885
|
-
BC_CMD_ID_GET_WIFI,
|
|
7886
|
-
BC_CMD_ID_GET_ONLINE_USER_LIST,
|
|
7887
|
-
BC_CMD_ID_GET_DAY_RECORDS,
|
|
7888
|
-
BC_CMD_ID_GET_STREAM_INFO_LIST,
|
|
7889
|
-
BC_CMD_ID_GET_LED_STATE,
|
|
7890
|
-
BC_CMD_ID_GET_EMAIL_TASK,
|
|
7891
|
-
BC_CMD_ID_GET_AUDIO_TASK,
|
|
7892
|
-
BC_CMD_ID_GET_AUDIO_CFG,
|
|
7893
|
-
BC_CMD_ID_GET_DAY_NIGHT_THRESHOLD,
|
|
7894
|
-
BC_CMD_ID_GET_TIMELAPSE_CFG,
|
|
7895
|
-
BC_CMD_ID_GET_AI_DENOISE,
|
|
7896
|
-
BC_CMD_ID_GET_KIT_AP_CFG,
|
|
7897
|
-
BC_CMD_ID_GET_REC_ENC_CFG,
|
|
7898
|
-
BC_CMD_ID_GET_ACCESS_USER_LIST,
|
|
7899
|
-
BC_CMD_ID_GET_SLEEP_STATE,
|
|
7900
|
-
BC_CMD_ID_GET_VIDEO_INPUT,
|
|
7901
|
-
BC_CMD_ID_GET_SYSTEM_GENERAL,
|
|
7902
|
-
BC_CMD_ID_GET_SUPPORT,
|
|
7903
|
-
BC_CMD_ID_GET_AI_CFG,
|
|
7904
|
-
BC_CMD_ID_SET_AI_CFG,
|
|
7905
|
-
BC_CMD_ID_GET_SIREN_STATUS,
|
|
7906
|
-
BC_CMD_ID_SET_AUDIO_TASK,
|
|
7907
|
-
BC_CMD_ID_SET_VIDEO_INPUT,
|
|
7908
|
-
BC_CMD_ID_SET_DAY_NIGHT_THRESHOLD,
|
|
7909
|
-
BC_CMD_ID_GET_ENC,
|
|
7910
|
-
BC_CMD_ID_SET_ENC,
|
|
7911
|
-
BC_CMD_ID_GET_PRIVACY_MASK,
|
|
7912
|
-
BC_CMD_ID_SET_PRIVACY_MASK,
|
|
7913
|
-
BC_CMD_ID_SET_AI_DENOISE,
|
|
7914
|
-
BC_CMD_ID_SET_LED_STATE,
|
|
7915
|
-
BC_CMD_ID_SET_AUDIO_CFG,
|
|
7916
|
-
BC_CMD_ID_SET_RECORD,
|
|
7917
|
-
BC_CMD_ID_SET_RECORD_CFG,
|
|
7918
|
-
BC_CMD_ID_SET_EMAIL_TASK,
|
|
7919
|
-
BC_CMD_ID_GET_PUSH_TASK,
|
|
7920
|
-
BC_CMD_ID_SET_PUSH_TASK,
|
|
7921
|
-
BC_CMD_ID_GET_AUTO_FOCUS,
|
|
7922
|
-
BC_CMD_ID_SET_AUTO_FOCUS,
|
|
7923
|
-
BC_CMD_ID_CMD_123,
|
|
7924
|
-
BC_CMD_ID_CMD_209,
|
|
7925
|
-
BC_CMD_ID_CMD_265,
|
|
7926
|
-
BC_CMD_ID_CMD_440,
|
|
7927
|
-
BC_CMD_ID_PUSH_VIDEO_INPUT,
|
|
7928
|
-
BC_CMD_ID_PUSH_SERIAL,
|
|
7929
|
-
BC_CMD_ID_PUSH_NET_INFO,
|
|
7930
|
-
BC_CMD_ID_PUSH_DINGDONG_LIST,
|
|
7931
|
-
BC_CMD_ID_PUSH_SLEEP_STATUS,
|
|
7932
|
-
BC_CMD_ID_PUSH_COORDINATE_POINT_LIST,
|
|
7933
|
-
BC_CMD_ID_DING_DONG_CTRL,
|
|
7934
|
-
BC_CMD_ID_GET_DING_DONG_LIST,
|
|
7935
|
-
BC_CMD_ID_DING_DONG_OPT,
|
|
7936
|
-
BC_CMD_ID_GET_DING_DONG_CFG,
|
|
7937
|
-
BC_CMD_ID_SET_DING_DONG_CFG,
|
|
7938
|
-
BC_CMD_ID_QUICK_REPLY_PLAY,
|
|
7939
|
-
BC_CMD_ID_GET_DING_DONG_SILENT,
|
|
7940
|
-
BC_CMD_ID_SET_DING_DONG_SILENT,
|
|
7941
|
-
md5HexUpper,
|
|
7942
|
-
md5StrModern,
|
|
7943
|
-
deriveAesKey,
|
|
7944
|
-
bcEncrypt,
|
|
7945
|
-
bcDecrypt,
|
|
7946
|
-
aesEncrypt,
|
|
7947
|
-
aesDecrypt,
|
|
7948
|
-
AesStreamDecryptor,
|
|
7949
5395
|
xmlEscape,
|
|
7950
5396
|
buildLoginXml,
|
|
7951
5397
|
buildLogoutXml,
|
|
@@ -7966,42 +5412,14 @@ export {
|
|
|
7966
5412
|
buildWhiteLedStateXml,
|
|
7967
5413
|
ensureXmlHeader,
|
|
7968
5414
|
applyXmlTagPatch,
|
|
5415
|
+
upsertXmlTag,
|
|
7969
5416
|
patchNestedTag,
|
|
7970
5417
|
applyStreamPatch,
|
|
7971
5418
|
normalizeDayNightMode,
|
|
7972
5419
|
normalizeOpenClose,
|
|
7973
5420
|
buildAbilityInfoExtensionXml,
|
|
7974
|
-
normalizeDebugOptions,
|
|
7975
|
-
recordingsTraceLog,
|
|
7976
|
-
debugLog,
|
|
7977
|
-
traceLog,
|
|
7978
|
-
talkTraceLog,
|
|
7979
|
-
eventTraceLog,
|
|
7980
5421
|
ReolinkHttpClient,
|
|
7981
5422
|
zipDirectory,
|
|
7982
|
-
parseBcMedia,
|
|
7983
|
-
BcMediaCodec,
|
|
7984
|
-
hasStartCodes,
|
|
7985
|
-
convertToAnnexB,
|
|
7986
|
-
splitAnnexBToNalPayloads,
|
|
7987
|
-
isValidH264AnnexBAccessUnit,
|
|
7988
|
-
isH264KeyframeAnnexB,
|
|
7989
|
-
H264RtpDepacketizer,
|
|
7990
|
-
convertToLengthPrefixed,
|
|
7991
|
-
hasStartCodes2,
|
|
7992
|
-
convertToAnnexB2,
|
|
7993
|
-
H265RtpDepacketizer,
|
|
7994
|
-
splitAnnexBToNalPayloads2,
|
|
7995
|
-
getH265NalType,
|
|
7996
|
-
isH265Irap,
|
|
7997
|
-
isValidH265AnnexBAccessUnit,
|
|
7998
|
-
isH265KeyframeAnnexB,
|
|
7999
|
-
extractVpsFromAnnexB,
|
|
8000
|
-
extractSpsFromAnnexB,
|
|
8001
|
-
extractPpsFromAnnexB,
|
|
8002
|
-
detectVideoCodecFromNal,
|
|
8003
|
-
BcMediaAnnexBDecoder,
|
|
8004
|
-
BaichuanVideoStream,
|
|
8005
5423
|
buildRtspPath,
|
|
8006
5424
|
buildRtspUrl,
|
|
8007
5425
|
collectNativeDiagnostics,
|
|
@@ -8020,4 +5438,4 @@ export {
|
|
|
8020
5438
|
parseRecordingFileName,
|
|
8021
5439
|
ReolinkCgiApi
|
|
8022
5440
|
};
|
|
8023
|
-
//# sourceMappingURL=chunk-
|
|
5441
|
+
//# sourceMappingURL=chunk-IJG45AOT.js.map
|