@@ -72,6 +72,7 @@ class TSDemuxer implements Demuxer {
72
72
private aacOverFlow : AudioFrame | null = null ;
73
73
private remainderData : Uint8Array | null = null ;
74
74
private videoParser : BaseVideoParser | null ;
75
+ private videoIntegrityChecker : PacketsIntegrityChecker | null = null ;
75
76
76
77
constructor (
77
78
observer : HlsEventEmitter ,
@@ -182,6 +183,10 @@ class TSDemuxer implements Demuxer {
182
183
183
184
this . _videoTrack = TSDemuxer . createTrack ( 'video' ) as DemuxedVideoTrack ;
184
185
this . _videoTrack . duration = trackDuration ;
186
+ this . videoIntegrityChecker =
187
+ this . config . handleMpegTsVideoIntegrityErrors === 'skip'
188
+ ? new PacketsIntegrityChecker ( this . logger )
189
+ : null ;
185
190
this . _audioTrack = TSDemuxer . createTrack (
186
191
'audio' ,
187
192
trackDuration ,
@@ -227,6 +232,7 @@ class TSDemuxer implements Demuxer {
227
232
let pes : PES | null ;
228
233
229
234
const videoTrack = this . _videoTrack as DemuxedVideoTrack ;
235
+ const videoIntegrityChecker = this . videoIntegrityChecker ;
230
236
const audioTrack = this . _audioTrack as DemuxedAudioTrack ;
231
237
const id3Track = this . _id3Track as DemuxedMetadataTrack ;
232
238
const textTrack = this . _txtTrack as DemuxedUserdataTrack ;
@@ -290,7 +296,11 @@ class TSDemuxer implements Demuxer {
290
296
switch ( pid ) {
291
297
case videoPid :
292
298
if ( stt ) {
293
- if ( videoData && ( pes = parsePES ( videoData , this . logger ) ) ) {
299
+ if (
300
+ videoData &&
301
+ ! videoIntegrityChecker ?. isCorrupted &&
302
+ ( pes = parsePES ( videoData , this . logger ) )
303
+ ) {
294
304
if ( this . videoParser === null ) {
295
305
switch ( videoTrack . segmentCodec ) {
296
306
case 'avc' :
@@ -309,7 +319,9 @@ class TSDemuxer implements Demuxer {
309
319
}
310
320
311
321
videoData = { data : [ ] , size : 0 } ;
322
+ videoIntegrityChecker ?. reset ( videoPid ) ;
312
323
}
324
+ videoIntegrityChecker ?. handle_packet ( data . subarray ( start ) ) ;
313
325
if ( videoData ) {
314
326
videoData . data . push ( data . subarray ( offset , start + PACKET_LENGTH ) ) ;
315
327
videoData . size += start + PACKET_LENGTH - offset ;
@@ -597,6 +609,8 @@ class TSDemuxer implements Demuxer {
597
609
this . _id3Track =
598
610
this . _txtTrack =
599
611
undefined ;
612
+
613
+ this . videoIntegrityChecker = null ;
600
614
}
601
615
602
616
private parseAACPES ( track : DemuxedAudioTrack , pes : PES ) {
@@ -1057,4 +1071,77 @@ function parsePES(stream: ElementaryStreamData, logger: ILogger): PES | null {
1057
1071
return null ;
1058
1072
}
1059
1073
1074
+ // See FFMpeg for reference: https://github.com/FFmpeg/FFmpeg/blob/e4c8e80a2efee275f2a10fcf0424c9fc1d86e309/libavformat/mpegts.c#L2811-L2834
1075
+ class PacketsIntegrityChecker {
1076
+ private readonly logger : ILogger ;
1077
+
1078
+ private pid : number = 0 ;
1079
+ private lastContinuityCounter = - 1 ;
1080
+ private integrityState : 'ok' | 'tei-bit' | 'cc-failed' = 'ok' ;
1081
+
1082
+ constructor ( logger : ILogger ) {
1083
+ this . logger = logger ;
1084
+ }
1085
+
1086
+ public get isCorrupted ( ) : boolean {
1087
+ return this . integrityState !== 'ok' ;
1088
+ }
1089
+
1090
+ public reset ( pid : number ) {
1091
+ this . pid = pid ;
1092
+ this . lastContinuityCounter = - 1 ;
1093
+ this . integrityState = 'ok' ;
1094
+ }
1095
+
1096
+ public handle_packet ( data : Uint8Array ) {
1097
+ if ( data . byteLength < 4 ) {
1098
+ return ;
1099
+ }
1100
+
1101
+ const pid = parsePID ( data , 0 ) ;
1102
+ if ( pid !== this . pid ) {
1103
+ this . logger . debug ( `Packet PID mismatch, expected ${ this . pid } got ${ pid } ` ) ;
1104
+ return ;
1105
+ }
1106
+
1107
+ const adaptationFieldControl = ( data [ 3 ] & 0x30 ) >> 4 ;
1108
+ if ( adaptationFieldControl === 0 ) {
1109
+ return ;
1110
+ }
1111
+ const continuityCounter = data [ 3 ] & 0xf ;
1112
+
1113
+ const lastContinuityCounter = this . lastContinuityCounter ;
1114
+ this . lastContinuityCounter = continuityCounter ;
1115
+
1116
+ const hasPayload = ( adaptationFieldControl & 0b01 ) != 0 ;
1117
+ const hasAdaptation = ( adaptationFieldControl & 0b10 ) != 0 ;
1118
+ const isDiscontinuity =
1119
+ hasAdaptation && data [ 4 ] != 0 && ( data [ 5 ] & 0x80 ) != 0 ;
1120
+
1121
+ if ( isDiscontinuity ) {
1122
+ return ;
1123
+ }
1124
+ if ( lastContinuityCounter < 0 ) {
1125
+ return ;
1126
+ }
1127
+
1128
+ const expectedContinuityCounter = hasPayload
1129
+ ? ( lastContinuityCounter + 1 ) & 0x0f
1130
+ : lastContinuityCounter ;
1131
+ if ( continuityCounter !== expectedContinuityCounter ) {
1132
+ this . logger . warn (
1133
+ `MPEG-TS Continuity Counter check failed for PID='${ pid } ', CC=${ continuityCounter } , Expected-CC=${ expectedContinuityCounter } Last-CC=${ lastContinuityCounter } ` ,
1134
+ ) ;
1135
+ this . integrityState = 'cc-failed' ;
1136
+ return ;
1137
+ }
1138
+
1139
+ if ( ( data [ 1 ] & 0x80 ) !== 0 ) {
1140
+ this . logger . warn ( `MPEG-TS Packet had TEI flag set for PID='${ pid } '` ) ;
1141
+ this . integrityState = 'tei-bit' ;
1142
+ return ;
1143
+ }
1144
+ }
1145
+ }
1146
+
1060
1147
export default TSDemuxer ;
0 commit comments