Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AC3 file support and exclude AC3 parsing from hls.light builds #5562

Merged
merged 1 commit into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions build-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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,
},
});
Expand Down Expand Up @@ -212,6 +218,13 @@ function getAliasesForLightDist() {
};
}

if (!addM2TSAdvancedCodecSupport) {
aliases = {
...aliases,
'./ac3-demuxer': '../empty.js',
};
}

return aliases;
}

Expand Down
6 changes: 4 additions & 2 deletions src/controller/audio-track-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/define-plugin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
186 changes: 186 additions & 0 deletions src/demux/ac3-demuxer.ts
Original file line number Diff line number Diff line change
@@ -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;
}
14 changes: 8 additions & 6 deletions src/demux/transmuxer-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -105,7 +107,7 @@ export default class TransmuxerInterface {
};
worker.postMessage({
cmd: 'init',
typeSupported: typeSupported,
typeSupported: m2tsTypeSupported,
vendor: vendor,
id: id,
config: JSON.stringify(config),
Expand All @@ -119,7 +121,7 @@ export default class TransmuxerInterface {
this.error = null;
this.transmuxer = new Transmuxer(
this.observer,
typeSupported,
m2tsTypeSupported,
config,
vendor,
id
Expand All @@ -131,7 +133,7 @@ export default class TransmuxerInterface {

this.transmuxer = new Transmuxer(
this.observer,
typeSupported,
m2tsTypeSupported,
config,
vendor,
id
Expand Down
8 changes: 7 additions & 1 deletion src/demux/transmuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 };

Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Loading
Loading