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.
289 lines
7.0 KiB
289 lines
7.0 KiB
'use strict'; |
|
|
|
var |
|
aacStream, |
|
AacStream = require('../lib/aac'), |
|
QUnit = require('qunit'), |
|
utils = require('./utils'), |
|
createId3Header, |
|
createId3FrameHeader, |
|
createAdtsHeader; |
|
|
|
createId3Header = function(tagSize) { |
|
var header = []; |
|
|
|
header[0] = 'I'.charCodeAt(0); |
|
header[1] = 'D'.charCodeAt(0); |
|
header[2] = '3'.charCodeAt(0); |
|
// 2 version bytes, ID3v2.4.0 (major 4, revision 0) |
|
header[3] = 4; |
|
header[4] = 0; |
|
// unsynchronization, extended header, experimental indicator, footer present flags |
|
header[5] = 0; |
|
// "The ID3v2 tag size is the sum of the byte length of the extended |
|
// header, the padding and the frames after unsynchronisation. If a |
|
// footer is present this equals to ('total size' - 20) bytes, otherwise |
|
// ('total size' - 10) bytes." |
|
// http://id3.org/id3v2.4.0-structure |
|
header[6] = 0; |
|
header[7] = 0; |
|
header[8] = 0; |
|
header[9] = tagSize; |
|
|
|
return header; |
|
}; |
|
|
|
createId3FrameHeader = function() { |
|
var header = []; |
|
|
|
// four byte frame ID, XYZ are experimental |
|
header[0] = 'X'.charCodeAt(0); |
|
header[1] = 'Y'.charCodeAt(0); |
|
header[2] = 'Z'.charCodeAt(0); |
|
header[3] = '0'.charCodeAt(0); |
|
// four byte sync safe integer size (excluding frame header) |
|
header[4] = 0; |
|
header[5] = 0; |
|
header[6] = 0; |
|
header[7] = 10; |
|
// two bytes for flags |
|
header[8] = 0; |
|
header[9] = 0; |
|
|
|
return header; |
|
}; |
|
|
|
createAdtsHeader = function(frameLength) { |
|
// Header consists of 7 or 9 bytes (without or with CRC). |
|
// see: https://wiki.multimedia.cx/index.php/ADTS |
|
return utils.binaryStringToArrayOfBytes(''.concat( |
|
// 12 bits for syncword (0xFFF) |
|
'111111111111', |
|
// 1 bit MPEG version |
|
'0', |
|
// 2 bit layer (always 0) |
|
'00', |
|
// 1 bit protection absent (1 for no CRC) |
|
'1', |
|
// 2 bit profile |
|
'10', |
|
// 4 bit sampling frequency index |
|
'0110', |
|
// 1 bit private bit |
|
'0', |
|
// 3 bit channel config |
|
'100', |
|
// 2 bit (ignore) |
|
'00', |
|
// 2 bit (copright bits) |
|
'00', |
|
// 13 bit frame length (includes header length) |
|
utils.leftPad(frameLength.toString(2), 13), |
|
// 11 bit buffer fullness |
|
'11111111111', |
|
// 2 bit number of AAC frames minus 1 |
|
'00' |
|
// 16 bit CRC (if present) |
|
)); |
|
}; |
|
|
|
QUnit.module('AAC Stream', { |
|
beforeEach: function() { |
|
aacStream = new AacStream(); |
|
} |
|
}); |
|
|
|
QUnit.test('parses ID3 tag', function(assert) { |
|
var |
|
id3Count = 0, |
|
adtsCount = 0, |
|
frameHeader = createId3FrameHeader(), |
|
id3Tag = createId3Header(frameHeader.length).concat(frameHeader); |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
|
|
aacStream.push(new Uint8Array(id3Tag)); |
|
|
|
assert.equal(adtsCount, 0, 'no adts frames'); |
|
assert.equal(id3Count, 1, 'one id3 chunk'); |
|
}); |
|
|
|
QUnit.test('parses two ID3 tags in sequence', function(assert) { |
|
var |
|
id3Count = 0, |
|
adtsCount = 0, |
|
frameHeader = createId3FrameHeader(), |
|
id3Tag = createId3Header(frameHeader.length).concat(frameHeader); |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
|
|
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag))); |
|
|
|
assert.equal(adtsCount, 0, 'no adts frames'); |
|
assert.equal(id3Count, 2, 'two id3 chunks'); |
|
}); |
|
|
|
QUnit.test('does not parse second ID3 tag if it\'s incomplete', function(assert) { |
|
var |
|
id3Count = 0, |
|
adtsCount = 0, |
|
frameHeader = createId3FrameHeader(), |
|
id3Tag = createId3Header(frameHeader.length).concat(frameHeader); |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
|
|
aacStream.push(new Uint8Array(id3Tag.concat(id3Tag.slice(0, id3Tag.length - 1)))); |
|
|
|
assert.equal(adtsCount, 0, 'no adts frames'); |
|
assert.equal(id3Count, 1, 'one id3 chunk'); |
|
}); |
|
|
|
QUnit.test('handles misaligned adts header', function(assert) { |
|
var |
|
id3Count = 0, |
|
adtsCount = 0, |
|
// fake adts frame |
|
adtsFrame = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
|
packetStream = createAdtsHeader(adtsFrame.length).concat(adtsFrame); |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
|
|
// misalign by two bytes specific to a bug related to detecting sync bytes |
|
// (where we were only properly checking the second byte) |
|
aacStream.push(new Uint8Array([0x01, 0xf0].concat(packetStream))); |
|
|
|
assert.equal(adtsCount, 1, 'one adts frames'); |
|
assert.equal(id3Count, 0, 'no id3 chunk'); |
|
}); |
|
|
|
QUnit.test('handles incomplete adts frame after id3 frame', function(assert) { |
|
var |
|
id3Count = 0, |
|
adtsCount = 0, |
|
id3FrameHeader = createId3FrameHeader(), |
|
id3Tag = createId3Header(id3FrameHeader.length).concat(id3FrameHeader), |
|
// in this case: |
|
// id3 tag = 20 bytes |
|
// adts header = 7 bytes |
|
// total = 27 bytes |
|
// report the ADTS frame size as 20 bytes |
|
adtsHeader = createAdtsHeader(20), |
|
// no adts frame, stream was cut off |
|
packetStream = id3Tag.concat(adtsHeader); |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
|
|
aacStream.push(new Uint8Array(packetStream)); |
|
|
|
assert.equal(adtsCount, 0, 'no adts frame'); |
|
assert.equal(id3Count, 1, 'one id3 chunk'); |
|
}); |
|
|
|
QUnit.test('emits data after receiving push', function(assert) { |
|
var |
|
array = new Uint8Array(109), |
|
count = 0; |
|
|
|
array[0] = 255; |
|
array[1] = 241; |
|
array[2] = 92; |
|
array[3] = 128; |
|
array[4] = 13; |
|
array[5] = 191; |
|
array[6] = 252; |
|
array[7] = 33; |
|
array[8] = 32; |
|
array[9] = 3; |
|
array[10] = 64; |
|
array[11] = 104; |
|
array[12] = 27; |
|
array[13] = 212; |
|
aacStream.setTimestamp(90); |
|
aacStream.on('data', function(frame) { |
|
if (frame.pts === 90 && frame.dts === 90) { |
|
count += 1; |
|
} |
|
}); |
|
aacStream.push(array); |
|
assert.equal(count, 1); |
|
}); |
|
|
|
QUnit.test('continues parsing after corrupted stream', function(assert) { |
|
var |
|
array = new Uint8Array(10000), |
|
adtsCount = 0, |
|
id3Count = 0; |
|
|
|
// an ID3 frame |
|
array[0] = 73; |
|
array[1] = 68; |
|
array[2] = 51; |
|
array[3] = 4; |
|
array[4] = 0; |
|
array[5] = 0; |
|
array[6] = 0; |
|
array[7] = 0; |
|
array[8] = 0; |
|
array[9] = 63; |
|
array[10] = 80; |
|
array[11] = 82; |
|
array[12] = 73; |
|
array[13] = 86; |
|
|
|
// an atds frame |
|
array[1020] = 255; |
|
array[1021] = 241; |
|
array[1022] = 92; |
|
array[1023] = 128; |
|
array[1024] = 13; |
|
array[1025] = 191; |
|
array[1026] = 252; |
|
array[1027] = 33; |
|
array[1028] = 32; |
|
array[1029] = 3; |
|
array[1030] = 64; |
|
array[1031] = 104; |
|
array[1032] = 27; |
|
array[1033] = 212; |
|
|
|
aacStream.on('data', function(frame) { |
|
if (frame.type === 'timed-metadata') { |
|
id3Count += 1; |
|
} else if (frame.type === 'audio') { |
|
adtsCount += 1; |
|
} |
|
}); |
|
aacStream.push(array); |
|
assert.equal(adtsCount, 1); |
|
assert.equal(id3Count, 1); |
|
});
|
|
|