From 66455c8f01499781a421fe8c8d26cfa2e1befa11 Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Sat, 5 Apr 2025 09:57:21 -0700 Subject: [PATCH] rebroadcast: fix bug where stream may be started on fragmented key frame --- common/src/rtsp-server.ts | 12 ++++++------ plugins/prebuffer-mixin/package-lock.json | 4 ++-- plugins/prebuffer-mixin/package.json | 2 +- plugins/prebuffer-mixin/src/main.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/rtsp-server.ts b/common/src/rtsp-server.ts index e9f54893d..982fc2d1a 100644 --- a/common/src/rtsp-server.ts +++ b/common/src/rtsp-server.ts @@ -164,10 +164,10 @@ export function findH265NaluTypeInNalu(nalu: Buffer, naluType: number) { return; } -export function getNaluTypes(streamChunk: StreamChunk) { +export function getStartedH264NaluTypes(streamChunk: StreamChunk) { if (streamChunk.type !== 'h264') return new Set(); - return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12)) + return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true) } export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) { @@ -208,10 +208,10 @@ export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaReq return ret; } -export function getH265NaluTypes(streamChunk: StreamChunk) { +export function getStartedH265NaluTypes(streamChunk: StreamChunk) { if (streamChunk.type !== 'h265') return new Set(); - return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12)) + return getNaluTypesInH265Nalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12), true) } export function getNaluTypesInH265Nalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) { @@ -275,13 +275,13 @@ export function createRtspParser(options?: StreamParserOptions): RtspStreamParse for (let prebufferIndex = 0; prebufferIndex < streamChunks.length; prebufferIndex++) { const streamChunk = streamChunks[prebufferIndex]; if (streamChunk.type === 'h264') { - const naluTypes = getNaluTypes(streamChunk); + const naluTypes = getStartedH264NaluTypes(streamChunk); if (naluTypes.has(H264_NAL_TYPE_SPS) || naluTypes.has(H264_NAL_TYPE_IDR)) { return streamChunks.slice(prebufferIndex); } } else if (streamChunk.type === 'h265') { - const naluTypes = getH265NaluTypes(streamChunk); + const naluTypes = getStartedH265NaluTypes(streamChunk); if (naluTypes.has(H265_NAL_TYPE_VPS) || naluTypes.has(H265_NAL_TYPE_SPS) diff --git a/plugins/prebuffer-mixin/package-lock.json b/plugins/prebuffer-mixin/package-lock.json index 14c4ea61a..86b5a4baa 100644 --- a/plugins/prebuffer-mixin/package-lock.json +++ b/plugins/prebuffer-mixin/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.10.56", + "version": "0.10.57", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/prebuffer-mixin", - "version": "0.10.56", + "version": "0.10.57", "license": "Apache-2.0", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/prebuffer-mixin/package.json b/plugins/prebuffer-mixin/package.json index dfe717cf2..e34a4d9ec 100644 --- a/plugins/prebuffer-mixin/package.json +++ b/plugins/prebuffer-mixin/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.10.56", + "version": "0.10.57", "description": "Video Stream Rebroadcast, Prebuffer, and Management Plugin for Scrypted.", "author": "Scrypted", "license": "Apache-2.0", diff --git a/plugins/prebuffer-mixin/src/main.ts b/plugins/prebuffer-mixin/src/main.ts index 640eadbbc..62cc9e599 100644 --- a/plugins/prebuffer-mixin/src/main.ts +++ b/plugins/prebuffer-mixin/src/main.ts @@ -1,7 +1,7 @@ import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider'; import { ListenZeroSingleClientTimeoutError, closeQuiet, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster'; import { readLength } from '@scrypted/common/src/read-stream'; -import { H264_NAL_TYPE_FU_B, H264_NAL_TYPE_IDR, H264_NAL_TYPE_MTAP16, H264_NAL_TYPE_MTAP32, H264_NAL_TYPE_RESERVED0, H264_NAL_TYPE_RESERVED30, H264_NAL_TYPE_RESERVED31, H264_NAL_TYPE_SEI, H264_NAL_TYPE_SPS, H264_NAL_TYPE_STAP_B, RtspServer, RtspTrack, createRtspParser, findH264NaluType, getNaluTypes, listenSingleRtspClient } from '@scrypted/common/src/rtsp-server'; +import { H264_NAL_TYPE_FU_B, H264_NAL_TYPE_IDR, H264_NAL_TYPE_MTAP16, H264_NAL_TYPE_MTAP32, H264_NAL_TYPE_RESERVED0, H264_NAL_TYPE_RESERVED30, H264_NAL_TYPE_RESERVED31, H264_NAL_TYPE_SEI, H264_NAL_TYPE_SPS, H264_NAL_TYPE_STAP_B, RtspServer, RtspTrack, createRtspParser, findH264NaluType, getStartedH264NaluTypes, listenSingleRtspClient } from '@scrypted/common/src/rtsp-server'; import { addTrackControls, getSpsPps, parseSdp } from '@scrypted/common/src/sdp-utils'; import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin"; import { sleep } from '@scrypted/common/src/sleep'; @@ -656,7 +656,7 @@ class PrebufferSession { if (chunk.type !== 'h264') return; - const types = getNaluTypes(chunk); + const types = getStartedH264NaluTypes(chunk); h264Probe.fuab ||= types.has(H264_NAL_TYPE_FU_B); h264Probe.stapb ||= types.has(H264_NAL_TYPE_STAP_B); h264Probe.mtap16 ||= types.has(H264_NAL_TYPE_MTAP16); @@ -1000,7 +1000,7 @@ class PrebufferSession { // prebuffer search for remote streaming should be even more conservative than local network. const defaultPrebuffer = options?.destination === 'remote' ? 2000 : 4000; // try to gaurantee a sync frame, but don't search too much prebuffer to make it happen. - requestedPrebuffer = Math.min(defaultPrebuffer, this.getDetectedIdrInterval() || defaultPrebuffer);; + requestedPrebuffer = Math.min(defaultPrebuffer, this.getDetectedIdrInterval() || defaultPrebuffer); } const codecInfo = await this.parseCodecs(true);