You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
5.2 KiB
192 lines
5.2 KiB
/**
|
|
* mux.js
|
|
*
|
|
* Copyright (c) Brightcove
|
|
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
|
*
|
|
* Utilities to detect basic properties and metadata about Aac data.
|
|
*/
|
|
'use strict';
|
|
|
|
var ADTS_SAMPLING_FREQUENCIES = [
|
|
96000,
|
|
88200,
|
|
64000,
|
|
48000,
|
|
44100,
|
|
32000,
|
|
24000,
|
|
22050,
|
|
16000,
|
|
12000,
|
|
11025,
|
|
8000,
|
|
7350
|
|
];
|
|
|
|
var parseId3TagSize = function(header, byteIndex) {
|
|
var
|
|
returnSize = (header[byteIndex + 6] << 21) |
|
|
(header[byteIndex + 7] << 14) |
|
|
(header[byteIndex + 8] << 7) |
|
|
(header[byteIndex + 9]),
|
|
flags = header[byteIndex + 5],
|
|
footerPresent = (flags & 16) >> 4;
|
|
|
|
// if we get a negative returnSize clamp it to 0
|
|
returnSize = returnSize >= 0 ? returnSize : 0;
|
|
|
|
if (footerPresent) {
|
|
return returnSize + 20;
|
|
}
|
|
return returnSize + 10;
|
|
};
|
|
|
|
var getId3Offset = function(data, offset) {
|
|
if (data.length - offset < 10 ||
|
|
data[offset] !== 'I'.charCodeAt(0) ||
|
|
data[offset + 1] !== 'D'.charCodeAt(0) ||
|
|
data[offset + 2] !== '3'.charCodeAt(0)) {
|
|
return offset;
|
|
}
|
|
|
|
offset += parseId3TagSize(data, offset);
|
|
|
|
return getId3Offset(data, offset);
|
|
};
|
|
|
|
|
|
// TODO: use vhs-utils
|
|
var isLikelyAacData = function(data) {
|
|
var offset = getId3Offset(data, 0);
|
|
|
|
return data.length >= offset + 2 &&
|
|
(data[offset] & 0xFF) === 0xFF &&
|
|
(data[offset + 1] & 0xF0) === 0xF0 &&
|
|
// verify that the 2 layer bits are 0, aka this
|
|
// is not mp3 data but aac data.
|
|
(data[offset + 1] & 0x16) === 0x10;
|
|
};
|
|
|
|
var parseSyncSafeInteger = function(data) {
|
|
return (data[0] << 21) |
|
|
(data[1] << 14) |
|
|
(data[2] << 7) |
|
|
(data[3]);
|
|
};
|
|
|
|
// return a percent-encoded representation of the specified byte range
|
|
// @see http://en.wikipedia.org/wiki/Percent-encoding
|
|
var percentEncode = function(bytes, start, end) {
|
|
var i, result = '';
|
|
for (i = start; i < end; i++) {
|
|
result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// return the string representation of the specified byte range,
|
|
// interpreted as ISO-8859-1.
|
|
var parseIso88591 = function(bytes, start, end) {
|
|
return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
|
|
};
|
|
|
|
var parseAdtsSize = function(header, byteIndex) {
|
|
var
|
|
lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
|
|
middle = header[byteIndex + 4] << 3,
|
|
highTwo = header[byteIndex + 3] & 0x3 << 11;
|
|
|
|
return (highTwo | middle) | lowThree;
|
|
};
|
|
|
|
var parseType = function(header, byteIndex) {
|
|
if ((header[byteIndex] === 'I'.charCodeAt(0)) &&
|
|
(header[byteIndex + 1] === 'D'.charCodeAt(0)) &&
|
|
(header[byteIndex + 2] === '3'.charCodeAt(0))) {
|
|
return 'timed-metadata';
|
|
} else if ((header[byteIndex] & 0xff === 0xff) &&
|
|
((header[byteIndex + 1] & 0xf0) === 0xf0)) {
|
|
return 'audio';
|
|
}
|
|
return null;
|
|
};
|
|
|
|
var parseSampleRate = function(packet) {
|
|
var i = 0;
|
|
|
|
while (i + 5 < packet.length) {
|
|
if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
|
|
// If a valid header was not found, jump one forward and attempt to
|
|
// find a valid ADTS header starting at the next byte
|
|
i++;
|
|
continue;
|
|
}
|
|
return ADTS_SAMPLING_FREQUENCIES[(packet[i + 2] & 0x3c) >>> 2];
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
var parseAacTimestamp = function(packet) {
|
|
var frameStart, frameSize, frame, frameHeader;
|
|
|
|
// find the start of the first frame and the end of the tag
|
|
frameStart = 10;
|
|
if (packet[5] & 0x40) {
|
|
// advance the frame start past the extended header
|
|
frameStart += 4; // header size field
|
|
frameStart += parseSyncSafeInteger(packet.subarray(10, 14));
|
|
}
|
|
|
|
// parse one or more ID3 frames
|
|
// http://id3.org/id3v2.3.0#ID3v2_frame_overview
|
|
do {
|
|
// determine the number of bytes in this frame
|
|
frameSize = parseSyncSafeInteger(packet.subarray(frameStart + 4, frameStart + 8));
|
|
if (frameSize < 1) {
|
|
return null;
|
|
}
|
|
frameHeader = String.fromCharCode(packet[frameStart],
|
|
packet[frameStart + 1],
|
|
packet[frameStart + 2],
|
|
packet[frameStart + 3]);
|
|
|
|
if (frameHeader === 'PRIV') {
|
|
frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
|
|
|
|
for (var i = 0; i < frame.byteLength; i++) {
|
|
if (frame[i] === 0) {
|
|
var owner = parseIso88591(frame, 0, i);
|
|
if (owner === 'com.apple.streaming.transportStreamTimestamp') {
|
|
var d = frame.subarray(i + 1);
|
|
var size = ((d[3] & 0x01) << 30) |
|
|
(d[4] << 22) |
|
|
(d[5] << 14) |
|
|
(d[6] << 6) |
|
|
(d[7] >>> 2);
|
|
size *= 4;
|
|
size += d[7] & 0x03;
|
|
|
|
return size;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
frameStart += 10; // advance past the frame header
|
|
frameStart += frameSize; // advance past the frame body
|
|
} while (frameStart < packet.byteLength);
|
|
return null;
|
|
};
|
|
|
|
module.exports = {
|
|
isLikelyAacData: isLikelyAacData,
|
|
parseId3TagSize: parseId3TagSize,
|
|
parseAdtsSize: parseAdtsSize,
|
|
parseType: parseType,
|
|
parseSampleRate: parseSampleRate,
|
|
parseAacTimestamp: parseAacTimestamp
|
|
};
|