From c9a7511e2bb6bbc3e0c263a80104e05e4c70bcaa Mon Sep 17 00:00:00 2001 From: Rob Walch Date: Mon, 12 Jun 2023 17:34:23 -0700 Subject: [PATCH] Add AC3 file support and exclude AC3 parsing from hls.light builds Follow up to #5167 --- .eslintrc.js | 1 + build-config.js | 13 ++ src/controller/audio-track-controller.ts | 6 +- src/define-plugin.d.ts | 1 + src/demux/ac3-demuxer.ts | 186 +++++++++++++++++++ src/demux/transmuxer-interface.ts | 14 +- src/demux/transmuxer.ts | 8 +- src/demux/tsdemuxer.ts | 220 ++++++++--------------- src/remux/mp4-generator.ts | 6 +- src/types/demuxer.ts | 3 +- 10 files changed, 295 insertions(+), 163 deletions(-) create mode 100644 src/demux/ac3-demuxer.ts diff --git a/.eslintrc.js b/.eslintrc.js index 386f3ca0586..129b7afec28 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,7 @@ module.exports = { __USE_CMCD__: true, __USE_CONTENT_STEERING__: true, __USE_VARIABLE_SUBSTITUTION__: true, + __USE_M2TS_ADVANCED_CODECS__: true, }, // see https://github.com/standard/eslint-config-standard // 'prettier' (https://github.com/prettier/eslint-config-prettier) must be last diff --git a/build-config.js b/build-config.js index b1b80baea86..758d0511c96 100644 --- a/build-config.js +++ b/build-config.js @@ -41,6 +41,8 @@ const addContentSteeringSupport = !!env.CONTENT_STEERING || !!env.USE_CONTENT_STEERING; const addVariableSubstitutionSupport = !!env.VARIABLE_SUBSTITUTION || !!env.USE_VARIABLE_SUBSTITUTION; +const addM2TSAdvancedCodecSupport = + !!env.M2TS_ADVANCED_CODECS || !!env.USE_M2TS_ADVANCED_CODECS; const shouldBundleWorker = (format) => format !== FORMAT.esm; @@ -62,6 +64,10 @@ const buildConstants = (type, additional = {}) => ({ __USE_VARIABLE_SUBSTITUTION__: JSON.stringify( type === BUILD_TYPE.full || addVariableSubstitutionSupport ), + __USE_M2TS_ADVANCED_CODECS__: JSON.stringify( + type === BUILD_TYPE.full || addM2TSAdvancedCodecSupport + ), + ...additional, }, }); @@ -212,6 +218,13 @@ function getAliasesForLightDist() { }; } + if (!addM2TSAdvancedCodecSupport) { + aliases = { + ...aliases, + './ac3-demuxer': '../empty.js', + }; + } + return aliases; } diff --git a/src/controller/audio-track-controller.ts b/src/controller/audio-track-controller.ts index 96ac5a39fb8..9e62ed2bbf0 100644 --- a/src/controller/audio-track-controller.ts +++ b/src/controller/audio-track-controller.ts @@ -209,8 +209,10 @@ class AudioTrackController extends BasePlaylistController { private selectInitialTrack(): void { const audioTracks = this.tracksInGroup; - const trackId = - this.findTrackId(this.currentTrack) | this.findTrackId(null); + let trackId = this.findTrackId(this.currentTrack); + if (trackId === -1) { + trackId = this.findTrackId(null); + } if (trackId !== -1) { this.setAudioTrack(trackId); diff --git a/src/define-plugin.d.ts b/src/define-plugin.d.ts index 8c50825a51d..2e5cd887c08 100644 --- a/src/define-plugin.d.ts +++ b/src/define-plugin.d.ts @@ -7,6 +7,7 @@ declare const __USE_SUBTITLES__: boolean; declare const __USE_CMCD__: boolean; declare const __USE_CONTENT_STEERING__: boolean; declare const __USE_VARIABLE_SUBSTITUTION__: boolean; +declare const __USE_M2TS_ADVANCED_CODECS__: boolean; // __IN_WORKER__ is provided from a closure call around the final UMD bundle. declare const __IN_WORKER__: boolean; diff --git a/src/demux/ac3-demuxer.ts b/src/demux/ac3-demuxer.ts new file mode 100644 index 00000000000..8c3065fa4be --- /dev/null +++ b/src/demux/ac3-demuxer.ts @@ -0,0 +1,186 @@ +import BaseAudioDemuxer from './base-audio-demuxer'; +import { getID3Data, getTimeStamp } from './id3'; +import type { HlsEventEmitter } from '../events'; +import type { AudioFrame, DemuxedAudioTrack } from '../types/demuxer'; + +export class AC3Demuxer extends BaseAudioDemuxer { + private readonly observer: HlsEventEmitter; + + constructor(observer) { + super(); + this.observer = observer; + } + + resetInitSegment( + initSegment: Uint8Array | undefined, + audioCodec: string | undefined, + videoCodec: string | undefined, + trackDuration: number + ) { + super.resetInitSegment(initSegment, audioCodec, videoCodec, trackDuration); + this._audioTrack = { + container: 'audio/ac-3', + type: 'audio', + id: 2, + pid: -1, + sequenceNumber: 0, + segmentCodec: 'ac3', + samples: [], + manifestCodec: audioCodec, + duration: trackDuration, + inputTimeScale: 90000, + dropped: 0, + }; + } + + canParse(data: Uint8Array, offset: number): boolean { + return offset + 64 < data.length; + } + + appendFrame( + track: DemuxedAudioTrack, + data: Uint8Array, + offset: number + ): AudioFrame | void { + const frameLength = appendFrame( + track, + data, + offset, + this.basePTS as number, + this.frameIndex + ); + if (frameLength !== -1) { + const sample = track.samples[track.samples.length - 1]; + return { sample, length: frameLength, missing: 0 }; + } + } + + static probe(data): boolean { + if (!data) { + return false; + } + + const id3Data = getID3Data(data, 0); + if (!id3Data) { + return false; + } + + // look for the ac-3 sync bytes + let offset = id3Data.length; + if ( + data[offset] === 0x0b && + data[offset + 1] === 0x77 && + getTimeStamp(id3Data) !== undefined + ) { + // check the bsid to confirm ac-3 + let bsid = 0; + let numBits = 5; + offset += numBits; + const temp = new Uint32Array(1); // unsigned 32 bit for temporary storage + const mask = new Uint32Array(1); // unsigned 32 bit mask value + const byte = new Uint8Array(1); // unsigned 8 bit for temporary storage + while (numBits > 0) { + byte[0] = data[offset]; + // read remaining bits, upto 8 bits at a time + const bits = Math.min(numBits, 8); + const shift = 8 - bits; + mask[0] = (0xff000000 >>> (24 + shift)) << shift; + temp[0] = (byte[0] & mask[0]) >> shift; + bsid = !bsid ? temp[0] : (bsid << bits) | temp[0]; + offset += 1; + numBits -= bits; + } + if (bsid < 16) { + return true; + } + } + return false; + } +} + +export function appendFrame( + track: DemuxedAudioTrack, + data: Uint8Array, + start: number, + pts: number, + frameIndex: number +): number { + if (start + 8 > data.length) { + return -1; // not enough bytes left + } + + if (data[start] !== 0x0b || data[start + 1] !== 0x77) { + return -1; // invalid magic + } + + // get sample rate + const samplingRateCode = data[start + 4] >> 6; + if (samplingRateCode >= 3) { + return -1; // invalid sampling rate + } + + const samplingRateMap = [48000, 44100, 32000]; + const sampleRate = samplingRateMap[samplingRateCode]; + + // get frame size + const frameSizeCode = data[start + 4] & 0x3f; + const frameSizeMap = [ + 64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, + 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, + 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, + 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, + 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, + 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, 975, + 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, 1728, + 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920, + ]; + + const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2; + if (start + frameLength > data.length) { + return -1; + } + + // get channel count + const channelMode = data[start + 6] >> 5; + let skipCount = 0; + if (channelMode === 2) { + skipCount += 2; + } else { + if (channelMode & 1 && channelMode !== 1) { + skipCount += 2; + } + if (channelMode & 4) { + skipCount += 2; + } + } + + const lfeon = + (((data[start + 6] << 8) | data[start + 7]) >> (12 - skipCount)) & 1; + + const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5]; + const channelCount = channelsMap[channelMode] + lfeon; + + // build dac3 box + const bsid = data[start + 5] >> 3; + const bsmod = data[start + 5] & 7; + + const config = new Uint8Array([ + (samplingRateCode << 6) | (bsid << 1) | (bsmod >> 2), + ((bsmod & 3) << 6) | + (channelMode << 3) | + (lfeon << 2) | + (frameSizeCode >> 4), + (frameSizeCode << 4) & 0xe0, + ]); + + const frameDuration = (1536 / sampleRate) * 90000; + const stamp = pts + frameIndex * frameDuration; + const unit = data.subarray(start, start + frameLength); + + track.config = config; + track.channelCount = channelCount; + track.samplerate = sampleRate; + track.samples.push({ unit, pts: stamp }); + + return frameLength; +} diff --git a/src/demux/transmuxer-interface.ts b/src/demux/transmuxer-interface.ts index e8e25d16987..f1e642bc5f9 100644 --- a/src/demux/transmuxer-interface.ts +++ b/src/demux/transmuxer-interface.ts @@ -66,12 +66,14 @@ export default class TransmuxerInterface { this.observer.on(Events.FRAG_DECRYPTED, forwardMessage); this.observer.on(Events.ERROR, forwardMessage); - const typeSupported: TypeSupported = { - mp4: MediaSource.isTypeSupported('video/mp4'), + const m2tsTypeSupported: TypeSupported = { mpeg: MediaSource.isTypeSupported('audio/mpeg'), mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'), - ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"'), + ac3: __USE_M2TS_ADVANCED_CODECS__ + ? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"') + : false, }; + // navigator.vendor is not always available in Web Worker // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator const vendor = navigator.vendor; @@ -105,7 +107,7 @@ export default class TransmuxerInterface { }; worker.postMessage({ cmd: 'init', - typeSupported: typeSupported, + typeSupported: m2tsTypeSupported, vendor: vendor, id: id, config: JSON.stringify(config), @@ -119,7 +121,7 @@ export default class TransmuxerInterface { this.error = null; this.transmuxer = new Transmuxer( this.observer, - typeSupported, + m2tsTypeSupported, config, vendor, id @@ -131,7 +133,7 @@ export default class TransmuxerInterface { this.transmuxer = new Transmuxer( this.observer, - typeSupported, + m2tsTypeSupported, config, vendor, id diff --git a/src/demux/transmuxer.ts b/src/demux/transmuxer.ts index 5d18a7484ac..e7a8a323da1 100644 --- a/src/demux/transmuxer.ts +++ b/src/demux/transmuxer.ts @@ -6,6 +6,7 @@ import AACDemuxer from '../demux/aacdemuxer'; import MP4Demuxer from '../demux/mp4demuxer'; import TSDemuxer, { TypeSupported } from '../demux/tsdemuxer'; import MP3Demuxer from '../demux/mp3demuxer'; +import { AC3Demuxer } from './ac3-demuxer'; import MP4Remuxer from '../remux/mp4-remuxer'; import PassThroughRemuxer from '../remux/passthrough-remuxer'; import { logger } from '../utils/logger'; @@ -29,6 +30,7 @@ try { type MuxConfig = | { demux: typeof MP4Demuxer; remux: typeof PassThroughRemuxer } | { demux: typeof TSDemuxer; remux: typeof MP4Remuxer } + | { demux: typeof AC3Demuxer; remux: typeof MP4Remuxer } | { demux: typeof AACDemuxer; remux: typeof MP4Remuxer } | { demux: typeof MP3Demuxer; remux: typeof MP4Remuxer }; @@ -39,6 +41,10 @@ const muxConfig: MuxConfig[] = [ { demux: MP3Demuxer, remux: MP4Remuxer }, ]; +if (__USE_M2TS_ADVANCED_CODECS__) { + muxConfig.splice(2, 0, { demux: AC3Demuxer, remux: MP4Remuxer }); +} + export default class Transmuxer { public async: boolean = false; private observer: HlsEventEmitter; @@ -418,7 +424,7 @@ export default class Transmuxer { // probe for content type let mux; for (let i = 0, len = muxConfig.length; i < len; i++) { - if (muxConfig[i].demux.probe(data)) { + if (muxConfig[i].demux?.probe(data)) { mux = muxConfig[i]; break; } diff --git a/src/demux/tsdemuxer.ts b/src/demux/tsdemuxer.ts index 2f5115a37ff..fe2347bf928 100644 --- a/src/demux/tsdemuxer.ts +++ b/src/demux/tsdemuxer.ts @@ -11,6 +11,7 @@ import * as ADTS from './adts'; import * as MpegAudio from './mpegaudio'; +import * as AC3 from './ac3-demuxer'; import ExpGolomb from './exp-golomb'; import SampleAesDecrypter from './sample-aes'; import { Events } from '../events'; @@ -35,6 +36,7 @@ import { ElementaryStreamData, KeyData, MetadataSchema, + AvcSampleUnit, } from '../types/demuxer'; import { AudioFrame } from '../types/demuxer'; @@ -53,7 +55,6 @@ type ParsedAvcSample = ParsedTimestamp & Omit; export interface TypeSupported { mpeg: boolean; mp3: boolean; - mp4: boolean; ac3: boolean; } @@ -315,7 +316,9 @@ class TSDemuxer implements Demuxer { this.parseMPEGPES(audioTrack, pes); break; case 'ac3': - this.parseAC3PES(pes); + if (__USE_M2TS_ADVANCED_CODECS__) { + this.parseAC3PES(audioTrack, pes); + } break; } } @@ -484,7 +487,9 @@ class TSDemuxer implements Demuxer { this.parseMPEGPES(audioTrack, pes); break; case 'ac3': - this.parseAC3PES(pes); + if (__USE_M2TS_ADVANCED_CODECS__) { + this.parseAC3PES(audioTrack, pes); + } break; } audioTrack.pesData = null; @@ -731,9 +736,9 @@ class TSDemuxer implements Demuxer { } } - private getLastNalUnit(samples: AvcSample[]) { + private getLastNalUnit(samples: AvcSample[]): AvcSampleUnit | undefined { let avcSample = this.avcSample; - let lastUnit; + let lastUnit: AvcSampleUnit | undefined; // try to fallback to previous sample if current one is empty if (!avcSample || avcSample.units.length === 0) { avcSample = samples[samples.length - 1]; @@ -756,15 +761,11 @@ class TSDemuxer implements Demuxer { const len = array.byteLength; let state = track.naluState || 0; const lastState = state; - const units = [] as Array<{ - data: Uint8Array; - type: number; - state?: number; - }>; + const units: AvcSampleUnit[] = []; let i = 0; - let value; - let overflow; - let unitType; + let value: number; + let overflow: number; + let unitType: number; let lastUnitStart = -1; let lastUnitType: number = 0; // logger.log('PES:' + Hex.hexDump(array)); @@ -793,9 +794,10 @@ class TSDemuxer implements Demuxer { if (!value) { state = 3; } else if (value === 1) { + overflow = i - state - 1; if (lastUnitStart >= 0) { - const unit = { - data: array.subarray(lastUnitStart, i - state - 1), + const unit: AvcSampleUnit = { + data: array.subarray(lastUnitStart, overflow), type: lastUnitType, }; // logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength); @@ -820,13 +822,13 @@ class TSDemuxer implements Demuxer { } } // If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit. - overflow = i - state - 1; + if (overflow > 0) { // logger.log('first NALU found with overflow:' + overflow); - const tmp = new Uint8Array(lastUnit.data.byteLength + overflow); - tmp.set(lastUnit.data, 0); - tmp.set(array.subarray(0, overflow), lastUnit.data.byteLength); - lastUnit.data = tmp; + lastUnit.data = appendUint8Array( + lastUnit.data, + array.subarray(0, overflow) + ); lastUnit.state = 0; } } @@ -847,7 +849,7 @@ class TSDemuxer implements Demuxer { } } if (lastUnitStart >= 0 && state >= 0) { - const unit = { + const unit: AvcSampleUnit = { data: array.subarray(lastUnitStart, len), type: lastUnitType, state: state, @@ -860,10 +862,7 @@ class TSDemuxer implements Demuxer { // append pes.data to previous NAL unit const lastUnit = this.getLastNalUnit(track.samples); if (lastUnit) { - const tmp = new Uint8Array(lastUnit.data.byteLength + array.byteLength); - tmp.set(lastUnit.data, 0); - tmp.set(array, lastUnit.data.byteLength); - lastUnit.data = tmp; + lastUnit.data = appendUint8Array(lastUnit.data, array); } } track.naluState = state; @@ -880,10 +879,7 @@ class TSDemuxer implements Demuxer { const sampleLength = aacOverFlow.sample.unit.byteLength; // logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`); if (frameMissingBytes === -1) { - const tmp = new Uint8Array(sampleLength + data.byteLength); - tmp.set(aacOverFlow.sample.unit, 0); - tmp.set(data, sampleLength); - data = tmp; + data = appendUint8Array(aacOverFlow.sample.unit, data); } else { const frameOverflowBytes = sampleLength - frameMissingBytes; aacOverFlow.sample.unit.set( @@ -1001,113 +997,25 @@ class TSDemuxer implements Demuxer { } } - private parseAC3PES(pes) { - const data = pes.data; - const pts = pes.pts; - const length = data.length; - let frameIndex = 0; - let offset = 0; - let parsed; - - while ( - offset < length && - (parsed = this.parseAC3(data, offset, length, frameIndex++, pts)) > 0 - ) { - offset += parsed; - } - } - - private onAC3Frame(data, sampleRate, channelCount, config, frameIndex, pts) { - const frameDuration = (1536 / sampleRate) * 1000; - const stamp = pts + frameIndex * frameDuration; - const audioTrack = this._audioTrack as DemuxedAudioTrack; - - audioTrack.config = config; - audioTrack.channelCount = channelCount; - audioTrack.samplerate = sampleRate; - audioTrack.duration = this._duration; - audioTrack.samples.push({ unit: data, pts: stamp }); - } - - private parseAC3(data, start, end, frameIndex, pts) { - if (start + 8 > end) { - return -1; // not enough bytes left - } - - if (data[start] !== 0x0b || data[start + 1] !== 0x77) { - return -1; // invalid magic - } - - // get sample rate - const samplingRateCode = data[start + 4] >> 6; - if (samplingRateCode >= 3) { - return -1; // invalid sampling rate - } - - const samplingRateMap = [48000, 44100, 32000]; - const sampleRate = samplingRateMap[samplingRateCode]; - - // get frame size - const frameSizeCode = data[start + 4] & 0x3f; - const frameSizeMap = [ - 64, 69, 96, 64, 70, 96, 80, 87, 120, 80, 88, 120, 96, 104, 144, 96, 105, - 144, 112, 121, 168, 112, 122, 168, 128, 139, 192, 128, 140, 192, 160, 174, - 240, 160, 175, 240, 192, 208, 288, 192, 209, 288, 224, 243, 336, 224, 244, - 336, 256, 278, 384, 256, 279, 384, 320, 348, 480, 320, 349, 480, 384, 417, - 576, 384, 418, 576, 448, 487, 672, 448, 488, 672, 512, 557, 768, 512, 558, - 768, 640, 696, 960, 640, 697, 960, 768, 835, 1152, 768, 836, 1152, 896, - 975, 1344, 896, 976, 1344, 1024, 1114, 1536, 1024, 1115, 1536, 1152, 1253, - 1728, 1152, 1254, 1728, 1280, 1393, 1920, 1280, 1394, 1920, - ]; - - const frameLength = frameSizeMap[frameSizeCode * 3 + samplingRateCode] * 2; - if (start + frameLength > end) { - return -1; - } - - // get channel count - const channelMode = data[start + 6] >> 5; - let skipCount = 0; - if (channelMode === 2) { - skipCount += 2; - } else { - if (channelMode & 1 && channelMode !== 1) { - skipCount += 2; + private parseAC3PES(track: DemuxedAudioTrack, pes: PES) { + if (__USE_M2TS_ADVANCED_CODECS__) { + const data = pes.data; + const pts = pes.pts; + if (pts === undefined) { + logger.warn('[tsdemuxer]: AC3 PES unknown PTS'); + return; } - if (channelMode & 4) { - skipCount += 2; + let frameIndex = 0; + let offset = 0; + let parsed; + + while ( + offset < length && + (parsed = AC3.appendFrame(track, data, offset, pts, frameIndex++)) > 0 + ) { + offset += parsed; } } - - const lfeon = - (((data[start + 6] << 8) | data[start + 7]) >> (12 - skipCount)) & 1; - - const channelsMap = [2, 1, 2, 3, 3, 4, 4, 5]; - const channelCount = channelsMap[channelMode] + lfeon; - - // build dac3 box - const bsid = data[start + 5] >> 3; - const bsmod = data[start + 5] & 7; - - const config = new Uint8Array([ - (samplingRateCode << 6) | (bsid << 1) | (bsmod >> 2), - ((bsmod & 3) << 6) | - (channelMode << 3) | - (lfeon << 2) | - (frameSizeCode >> 4), - (frameSizeCode << 4) & 0xe0, - ]); - - this.onAC3Frame( - data.subarray(start, start + frameLength), - sampleRate, - channelCount, - config, - frameIndex, - pts - ); - - return frameLength; } private parseID3PES(id3Track: DemuxedMetadataTrack, pes: PES) { @@ -1170,9 +1078,7 @@ function parsePMT( switch (data[offset]) { case 0xcf: // SAMPLE-AES AAC if (!isSampleAes) { - logger.log( - 'ADTS AAC with AES-128-CBC frame encryption found in unencrypted stream' - ); + logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC'); break; } /* falls through */ @@ -1195,9 +1101,7 @@ function parsePMT( case 0xdb: // SAMPLE-AES AVC if (!isSampleAes) { - logger.log( - 'H.264 with AES-128-CBC slice encryption found in unencrypted stream' - ); + logEncryptedSamplesFoundInUnencryptedStream('H.264'); break; } /* falls through */ @@ -1214,7 +1118,7 @@ function parsePMT( case 0x03: case 0x04: // logger.log('MPEG PID:' + pid); - if (typeSupported.mpeg !== true && typeSupported.mp3 !== true) { + if (!typeSupported.mpeg && !typeSupported.mp3) { logger.log('MPEG audio found, not supported in this browser'); } else if (result.audio === -1) { result.audio = pid; @@ -1222,17 +1126,32 @@ function parsePMT( } break; + case 0xc1: // SAMPLE-AES AC3 + if (!isSampleAes) { + logEncryptedSamplesFoundInUnencryptedStream('AC-3'); + break; + } + /* falls through */ case 0x81: - if (typeSupported.ac3 !== true) { - logger.log('AC-3 audio found, not supported in this browser for now'); - } else if (result.audio === -1) { - result.audio = pid; - result.segmentCodec = 'ac3'; + if (__USE_M2TS_ADVANCED_CODECS__) { + if (!typeSupported.ac3) { + logger.log('AC-3 audio found, not supported in this browser'); + } else if (result.audio === -1) { + result.audio = pid; + result.segmentCodec = 'ac3'; + } + } else { + logger.warn('AC-3 in M2TS support not included in build'); } break; + case 0xc2: // SAMPLE-AES EC3 + /* falls through */ + case 0x87: + logger.warn('Unsupported EC-3 in M2TS found'); + break; case 0x24: - logger.warn('Unsupported HEVC stream type found'); + logger.warn('Unsupported HEVC in M2TS found'); break; default: @@ -1246,6 +1165,10 @@ function parsePMT( return result; } +function logEncryptedSamplesFoundInUnencryptedStream(type: string) { + logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`); +} + function parsePES(stream: ElementaryStreamData): PES | null { let i = 0; let frag: Uint8Array; @@ -1263,10 +1186,7 @@ function parsePES(stream: ElementaryStreamData): PES | null { // if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes // usually only one merge is needed (and this is rare ...) while (data[0].length < 19 && data.length > 1) { - const newData = new Uint8Array(data[0].length + data[1].length); - newData.set(data[0]); - newData.set(data[1], data[0].length); - data[0] = newData; + data[0] = appendUint8Array(data[0], data[1]); data.splice(1, 1); } // retrieve PTS/DTS from first fragment diff --git a/src/remux/mp4-generator.ts b/src/remux/mp4-generator.ts index 4c3ee1425b3..54c06a6efa1 100644 --- a/src/remux/mp4-generator.ts +++ b/src/remux/mp4-generator.ts @@ -2,6 +2,8 @@ * Generate MP4 Box */ +import { appendUint8Array } from '../utils/mp4-tools'; + type HdlrTypes = { video: Uint8Array; audio: Uint8Array; @@ -1118,9 +1120,7 @@ class MP4 { } const movie = MP4.moov(tracks); - const result = new Uint8Array(MP4.FTYP.byteLength + movie.byteLength); - result.set(MP4.FTYP); - result.set(movie, MP4.FTYP.byteLength); + const result = appendUint8Array(MP4.FTYP, movie); return result; } } diff --git a/src/types/demuxer.ts b/src/types/demuxer.ts index 554bc79ba9a..e84779b386f 100644 --- a/src/types/demuxer.ts +++ b/src/types/demuxer.ts @@ -59,7 +59,7 @@ export interface PassthroughTrack extends DemuxedTrack { codec: string; } export interface DemuxedAudioTrack extends DemuxedTrack { - config?: number[]; + config?: number[] | Uint8Array; samplerate?: number; segmentCodec?: string; channelCount?: number; @@ -127,6 +127,7 @@ export interface AvcSample { export interface AvcSampleUnit { data: Uint8Array; type: number; + state?: number; } export type AudioSample = {