@@ -561,12 +561,24 @@ async function mp4FileToSamples(otFile: OPFSToolFile, opts: MP4ClipOpts = {}) {
561
561
sampleType : 'video' | 'audio' ,
562
562
) {
563
563
// todo: perf 丢弃多余字段,小尺寸对象性能更好
564
+ const is_idr =
565
+ sampleType === 'video' &&
566
+ s . is_sync &&
567
+ isIDRFrame ( s . data , s . description . type ) ;
568
+ let offset = s . offset ;
569
+ let size = s . size ;
570
+ if ( is_idr ) {
571
+ // 当 IDR 帧前面携带 SEI 数据可能导致解码失败
572
+ // 所以此处通过控制 offset、size 字段 跳过 SEI 数据
573
+ const seiLen = seiLenOfStart ( s . data , s . description . type ) ;
574
+ offset += seiLen ;
575
+ size -= seiLen ;
576
+ }
564
577
return {
565
578
...s ,
566
- is_idr :
567
- sampleType === 'video' &&
568
- s . is_sync &&
569
- isIDRFrame ( s . data , s . description . type ) ,
579
+ is_idr,
580
+ offset,
581
+ size,
570
582
cts : ( ( s . cts - delta ) / s . timescale ) * 1e6 ,
571
583
dts : ( ( s . dts - delta ) / s . timescale ) * 1e6 ,
572
584
duration : ( s . duration / s . timescale ) * 1e6 ,
@@ -1087,28 +1099,24 @@ async function videosamples2Chunks(
1087
1099
) ;
1088
1100
return samples . map ( ( s ) => {
1089
1101
const offset = s . offset - first . offset ;
1090
- let sData = data . subarray ( offset , offset + s . size ) ;
1091
- if ( s . is_idr ) sData = removeSEIForIDR ( sData ) ;
1092
1102
return new EncodedVideoChunk ( {
1093
1103
type : s . is_sync ? 'key' : 'delta' ,
1094
1104
timestamp : s . cts ,
1095
1105
duration : s . duration ,
1096
- data : sData ,
1106
+ data : data . subarray ( offset , offset + s . size ) ,
1097
1107
} ) ;
1098
1108
} ) ;
1099
1109
}
1100
1110
1101
1111
return await Promise . all (
1102
1112
samples . map ( async ( s ) => {
1103
- let sData = await reader . read ( s . size , {
1104
- at : s . offset ,
1105
- } ) ;
1106
- if ( s . is_idr ) sData = removeSEIForIDR ( new Uint8Array ( sData ) ) ;
1107
1113
return new EncodedVideoChunk ( {
1108
1114
type : s . is_sync ? 'key' : 'delta' ,
1109
1115
timestamp : s . cts ,
1110
1116
duration : s . duration ,
1111
- data : sData ,
1117
+ data : await reader . read ( s . size , {
1118
+ at : s . offset ,
1119
+ } ) ,
1112
1120
} ) ;
1113
1121
} ) ,
1114
1122
) ;
@@ -1224,13 +1232,24 @@ function decodeGoP(
1224
1232
} ) ;
1225
1233
}
1226
1234
1227
- // 当 IDR 帧前面携带其它数据(如 SEI)可能导致解码失败
1228
- function removeSEIForIDR ( u8buf : Uint8Array ) {
1229
- const dv = new DataView ( u8buf . buffer , u8buf . byteOffset , u8buf . byteLength ) ;
1230
- if ( ( dv . getUint8 ( 4 ) & 0x1f ) === 6 ) {
1231
- return u8buf . subarray ( dv . getUint32 ( 0 ) + 4 ) ;
1235
+ // 获取起始位置的 SEI 长度
1236
+ function seiLenOfStart (
1237
+ u8Arr : Uint8Array ,
1238
+ type : MP4Sample [ 'description' ] [ 'type' ] ,
1239
+ ) {
1240
+ if ( type !== 'avc1' && type !== 'hvc1' ) return 0 ;
1241
+
1242
+ const dv = new DataView ( u8Arr . buffer ) ;
1243
+ if ( type === 'avc1' && ( dv . getUint8 ( 4 ) & 0x1f ) === 6 ) {
1244
+ return dv . getUint32 ( 0 ) + 4 ;
1245
+ }
1246
+ if ( type === 'hvc1' ) {
1247
+ const nalUnitType = ( dv . getUint8 ( 4 ) >> 1 ) & 0x3f ;
1248
+ if ( nalUnitType === 39 || nalUnitType === 40 ) {
1249
+ return dv . getUint32 ( 0 ) + 4 ;
1250
+ }
1232
1251
}
1233
- return u8buf ;
1252
+ return 0 ;
1234
1253
}
1235
1254
1236
1255
function isIDRFrame ( u8Arr : Uint8Array , type : MP4Sample [ 'description' ] [ 'type' ] ) {
@@ -1239,8 +1258,8 @@ function isIDRFrame(u8Arr: Uint8Array, type: MP4Sample['description']['type']) {
1239
1258
const dv = new DataView ( u8Arr . buffer ) ;
1240
1259
let i = 0 ;
1241
1260
for ( ; i < u8Arr . byteLength - 4 ; ) {
1242
- if ( type === 'avc1' ) {
1243
- if ( ( dv . getUint8 ( i + 4 ) & 0x1f ) === 5 ) return true ;
1261
+ if ( type === 'avc1' && ( dv . getUint8 ( i + 4 ) & 0x1f ) === 5 ) {
1262
+ return true ;
1244
1263
} else if ( type === 'hvc1' ) {
1245
1264
const nalUnitType = ( dv . getUint8 ( i + 4 ) >> 1 ) & 0x3f ;
1246
1265
if ( nalUnitType === 19 || nalUnitType === 20 ) return true ;
0 commit comments