UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

195 lines (159 loc) 7.17 kB
import Log from "../utils/logger"; import { MPEG4AudioObjectTypes, MPEG4SamplingFrequencies, MPEG4SamplingFrequencyIndex } from "./mpeg4-audio"; export class AACFrame { audio_object_type: MPEG4AudioObjectTypes; sampling_freq_index: MPEG4SamplingFrequencyIndex; sampling_frequency: number; channel_config: number; data: Uint8Array; } export class AACADTSParser { private readonly TAG: string = "AACADTSParser"; private data_: Uint8Array; private current_syncword_offset_: number; private eof_flag_: boolean; private has_last_incomplete_data: boolean; public constructor(data: Uint8Array) { this.data_ = data; this.current_syncword_offset_ = this.findNextSyncwordOffset(0); if (this.eof_flag_) { Log.e(this.TAG, `Could not found ADTS syncword until payload end`); } } private findNextSyncwordOffset(syncword_offset: number): number { let i = syncword_offset; let data = this.data_; while (true) { if (i + 7 >= data.byteLength) { this.eof_flag_ = true; return data.byteLength; } // search 12-bit 0xFFF syncword let syncword = ((data[i + 0] << 8) | data[i + 1]) >>> 4; if (syncword === 0xFFF) { return i; } else { i++; } } } public readNextAACFrame(): AACFrame | null { let data = this.data_; let aac_frame: AACFrame = null; while (aac_frame == null) { if (this.eof_flag_) { break; } let syncword_offset = this.current_syncword_offset_; let offset = syncword_offset; // adts_fixed_header() // syncword 0xFFF: 12-bit let ID = (data[offset + 1] & 0x08) >>> 3; let layer = (data[offset + 1] & 0x06) >>> 1; let protection_absent = data[offset + 1] & 0x01; let profile = (data[offset + 2] & 0xC0) >>> 6; let sampling_frequency_index = (data[offset + 2] & 0x3C) >>> 2; let channel_configuration = ((data[offset + 2] & 0x01) << 2) | ((data[offset + 3] & 0xC0) >>> 6); // adts_variable_header() let aac_frame_length = ((data[offset + 3] & 0x03) << 11) | (data[offset + 4] << 3) | ((data[offset + 5] & 0xE0) >>> 5); let number_of_raw_data_blocks_in_frame = data[offset + 6] & 0x03; if (offset + aac_frame_length > this.data_.byteLength) { // data not enough for extracting last sample this.eof_flag_ = true; this.has_last_incomplete_data = true; break; } let adts_header_length = (protection_absent === 1) ? 7 : 9; let adts_frame_payload_length = aac_frame_length - adts_header_length; offset += adts_header_length; let next_syncword_offset = this.findNextSyncwordOffset(offset + adts_frame_payload_length); this.current_syncword_offset_ = next_syncword_offset; if ((ID !== 0 && ID !== 1) || layer !== 0) { // invalid adts frame ? continue; } let frame_data = data.subarray(offset, offset + adts_frame_payload_length); aac_frame = new AACFrame(); aac_frame.audio_object_type = (profile + 1) as MPEG4AudioObjectTypes; aac_frame.sampling_freq_index = sampling_frequency_index as MPEG4SamplingFrequencyIndex; aac_frame.sampling_frequency = MPEG4SamplingFrequencies[sampling_frequency_index]; aac_frame.channel_config = channel_configuration; aac_frame.data = frame_data; } return aac_frame; } public hasIncompleteData(): boolean { return this.has_last_incomplete_data; } public getIncompleteData(): Uint8Array { if (!this.has_last_incomplete_data) { return null; } return this.data_.subarray(this.current_syncword_offset_); } } export class AudioSpecificConfig { public config: Array<number>; public sampling_rate: number; public channel_count: number; public codec_mimetype: string; public original_codec_mimetype: string; public constructor(frame: AACFrame) { let config: Array<number> = null; let original_audio_object_type = frame.audio_object_type; let audio_object_type = frame.audio_object_type; let sampling_index = frame.sampling_freq_index; let channel_config = frame.channel_config; let extension_sampling_index = 0; let userAgent = navigator.userAgent.toLowerCase(); if (userAgent.indexOf('firefox') !== -1) { // firefox: use SBR (HE-AAC) if freq less than 24kHz if (sampling_index >= 6) { audio_object_type = 5; config = new Array(4); extension_sampling_index = sampling_index - 3; } else { // use LC-AAC audio_object_type = 2; config = new Array(2); extension_sampling_index = sampling_index; } } else if (userAgent.indexOf('android') !== -1) { // android: always use LC-AAC audio_object_type = 2; config = new Array(2); extension_sampling_index = sampling_index; } else { // for other browsers, e.g. chrome... // Always use HE-AAC to make it easier to switch aac codec profile audio_object_type = 5; extension_sampling_index = sampling_index; config = new Array(4); if (sampling_index >= 6) { extension_sampling_index = sampling_index - 3; } else if (channel_config === 1) { // Mono channel audio_object_type = 2; config = new Array(2); extension_sampling_index = sampling_index; } } config[0] = audio_object_type << 3; config[0] |= (sampling_index & 0x0F) >>> 1; config[1] = (sampling_index & 0x0F) << 7; config[1] |= (channel_config & 0x0F) << 3; if (audio_object_type === 5) { config[1] |= ((extension_sampling_index & 0x0F) >>> 1); config[2] = (extension_sampling_index & 0x01) << 7; // extended audio object type: force to 2 (LC-AAC) config[2] |= (2 << 2); config[3] = 0; } this.config = config; this.sampling_rate = MPEG4SamplingFrequencies[sampling_index]; this.channel_count = channel_config; this.codec_mimetype = 'mp4a.40.' + audio_object_type; this.original_codec_mimetype = 'mp4a.40.' + original_audio_object_type; } }