@@ -527,7 +527,6 @@ async function mp4FileToSamples(otFile: OPFSToolFile, opts: MP4ClipOpts = {}) {
527527
528528 let videoDeltaTS = - 1 ;
529529 let audioDeltaTS = - 1 ;
530- let findedFirstSync = false ;
531530 const reader = await otFile . createReader ( ) ;
532531 await quickParseMP4File (
533532 reader ,
@@ -561,18 +560,8 @@ async function mp4FileToSamples(otFile: OPFSToolFile, opts: MP4ClipOpts = {}) {
561560 ( _ , type , samples ) => {
562561 if ( type === 'video' ) {
563562 if ( videoDeltaTS === - 1 ) videoDeltaTS = samples [ 0 ] . dts ;
564- for ( let i = 0 ; i < samples . length ; i ++ ) {
565- const s = samples [ i ] ;
566- if ( ! findedFirstSync && s . is_sync ) {
567- findedFirstSync = true ;
568- videoSamples . push (
569- normalizeTimescale ( s , videoDeltaTS , 'video' , true ) ,
570- ) ;
571- } else {
572- videoSamples . push (
573- normalizeTimescale ( s , videoDeltaTS , 'video' , false ) ,
574- ) ;
575- }
563+ for ( const s of samples ) {
564+ videoSamples . push ( normalizeTimescale ( s , videoDeltaTS , 'video' ) ) ;
576565 }
577566 } else if ( type === 'audio' && opts . audio ) {
578567 if ( audioDeltaTS === - 1 ) audioDeltaTS = samples [ 0 ] . dts ;
@@ -605,30 +594,25 @@ function normalizeTimescale(
605594 s : MP4Sample ,
606595 delta = 0 ,
607596 sampleType : 'video' | 'audio' ,
608- isFirstSync ?: boolean ,
609597) {
610598 // todo: perf 丢弃多余字段,小尺寸对象性能更好
611599 let offset = s . offset ;
612- const isVideoSync = sampleType === 'video' && s . is_sync ;
613- const idrOffset = isVideoSync
614- ? idrNALUOffset ( s . data , s . description . type , offset )
615- : - 1 ;
600+ // 当 IDR 帧前面包含非图像帧数据(如 SEI),可能导致解码失败
601+ const idrOffset =
602+ sampleType === 'video' && s . is_sync
603+ ? idrNALUOffset ( s . data , s . description . type )
604+ : - 1 ;
616605
617- // 默认信任第一个关键帧 是 IDR 帧,兼容某些异常标注的视频文件
618- let is_idr = isFirstSync === true && isVideoSync ;
619606 let size = s . size ;
620- if ( idrOffset >= 0 ) {
621- // 当 IDR 帧前面包含非图像帧数据(如 SEI),可能导致解码失败
622- // 所以此处通过控制 offset、size 字段 跳过非图像帧数据
623- offset = idrOffset ;
624- size -= idrOffset - offset ;
625- // 非第一个关键帧,如果根据 naluType 判定是 IDR 帧,则设置 is_idr
626- is_idr = true ;
607+ if ( idrOffset > 0 ) {
608+ // 此处通过控制 offset、size 字段 跳过非图像帧数据
609+ offset += idrOffset ;
610+ size -= idrOffset ;
627611 }
628612
629613 return {
630614 ...s ,
631- is_idr,
615+ is_idr : idrOffset >= 0 ,
632616 offset,
633617 size,
634618 cts : ( ( s . cts - delta ) / s . timescale ) * 1e6 ,
@@ -1352,18 +1336,26 @@ function decodeGoP(
13521336function idrNALUOffset (
13531337 u8Arr : Uint8Array ,
13541338 type : MP4Sample [ 'description' ] [ 'type' ] ,
1355- startOffset : number ,
13561339) {
13571340 if ( type !== 'avc1' && type !== 'hvc1' ) return 0 ;
13581341
13591342 const dv = new DataView ( u8Arr . buffer ) ;
1360- let i = startOffset ;
1361- for ( ; i < u8Arr . byteLength - 4 ; ) {
1362- if ( type === 'avc1' && ( dv . getUint8 ( i + 4 ) & 0x1f ) === 5 ) {
1363- return i ;
1343+ for ( let i = 0 ; i < u8Arr . byteLength - 4 ; ) {
1344+ if ( type === 'avc1' ) {
1345+ const nalUnitType = dv . getUint8 ( i + 4 ) & 0x1f ;
1346+ // 5: IDR 帧, 7: SPS, 8: PPS
1347+ if ( nalUnitType === 5 || nalUnitType === 7 || nalUnitType === 8 ) return i ;
13641348 } else if ( type === 'hvc1' ) {
13651349 const nalUnitType = ( dv . getUint8 ( i + 4 ) >> 1 ) & 0x3f ;
1366- if ( nalUnitType === 19 || nalUnitType === 20 ) return i ;
1350+ // 19-20: IDR 帧, 32-34: VPS SPS PPS
1351+ if (
1352+ nalUnitType === 19 ||
1353+ nalUnitType === 20 ||
1354+ nalUnitType === 32 ||
1355+ nalUnitType === 33 ||
1356+ nalUnitType === 34
1357+ )
1358+ return i ;
13671359 }
13681360 // 跳至下一个 NALU 继续检查
13691361 i += dv . getUint32 ( i ) + 4 ;
@@ -1513,7 +1505,7 @@ if (import.meta.vitest) {
15131505 description : { type : 'avc1' } ,
15141506 is_rap : false ,
15151507 } ;
1516- const normalized = normalizeTimescale ( s , 0 , 'video' , true ) ;
1508+ const normalized = normalizeTimescale ( s , 0 , 'video' ) ;
15171509 expect ( normalized . offset ) . toBe ( 48 ) ;
15181510 expect ( normalized . size ) . toBe ( 1000 ) ;
15191511 expect ( normalized . is_sync ) . toBe ( normalized . is_idr ) ;
0 commit comments