Skip to content

Commit

Permalink
Merge pull request #4781 from video-dev/bugfix/ts-adts-acc-overflow
Browse files Browse the repository at this point in the history
Fix AAC overflow when it occurs at ADTS header
  • Loading branch information
robwalch authored Jul 14, 2022
2 parents fc2243e + df1e5a7 commit 9d66046
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 49 deletions.
39 changes: 22 additions & 17 deletions src/demux/adts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type AudioConfig = {
type FrameHeader = {
headerLength: number;
frameLength: number;
stamp: number;
};

export function getAudioConfig(
Expand Down Expand Up @@ -255,21 +254,17 @@ export function getFrameDuration(samplerate: number): number {

export function parseFrameHeader(
data: Uint8Array,
offset: number,
pts: number,
frameIndex: number,
frameDuration: number
offset: number
): FrameHeader | void {
// The protection skip bit tells us if we have 2 bytes of CRC data at the end of the ADTS header
const headerLength = getHeaderLength(data, offset);
// retrieve frame size
let frameLength = getFullFrameLength(data, offset);
frameLength -= headerLength;

if (frameLength > 0) {
const stamp = pts + frameIndex * frameDuration;
// logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}/${(stamp/90).toFixed(0)}`);
return { headerLength, frameLength, stamp };
if (offset + headerLength <= data.length) {
// retrieve frame size
const frameLength = getFullFrameLength(data, offset) - headerLength;
if (frameLength > 0) {
// logger.log(`AAC frame, offset/length/total/pts:${offset+headerLength}/${frameLength}/${data.byteLength}`);
return { headerLength, frameLength };
}
}
}

Expand All @@ -279,15 +274,16 @@ export function appendFrame(
offset: number,
pts: number,
frameIndex: number
): AudioFrame | void {
): AudioFrame {
const frameDuration = getFrameDuration(track.samplerate as number);
const header = parseFrameHeader(data, offset, pts, frameIndex, frameDuration);
const stamp = pts + frameIndex * frameDuration;
const header = parseFrameHeader(data, offset);
let unit: Uint8Array;
if (header) {
const { frameLength, headerLength, stamp } = header;
const { frameLength, headerLength } = header;
const length = headerLength + frameLength;
const missing = Math.max(0, offset + length - data.length);
// logger.log(`AAC frame ${frameIndex}, pts:${stamp} length@offset/total: ${frameLength}@${offset+headerLength}/${data.byteLength} missing: ${missing}`);
let unit: Uint8Array;
if (missing) {
unit = new Uint8Array(length - headerLength);
unit.set(data.subarray(offset + headerLength, data.length), 0);
Expand All @@ -305,4 +301,13 @@ export function appendFrame(

return { sample, length, missing };
}
// overflow incomplete header
const length = data.length - offset;
unit = new Uint8Array(length);
unit.set(data.subarray(offset, data.length), 0);
const sample: AudioSample = {
unit,
pts: stamp,
};
return { sample, length, missing: -1 };
}
54 changes: 27 additions & 27 deletions src/demux/tsdemuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,20 +838,26 @@ class TSDemuxer implements Demuxer {
private parseAACPES(track: DemuxedAudioTrack, pes: PES) {
let startOffset = 0;
const aacOverFlow = this.aacOverFlow;
const data = pes.data;
let data = pes.data;
if (aacOverFlow) {
this.aacOverFlow = null;
const frameMissingBytes = aacOverFlow.missing;
const sampleLength = aacOverFlow.sample.unit.byteLength;
const frameMissingBytes = Math.min(aacOverFlow.missing, sampleLength);
const frameOverflowBytes = sampleLength - frameMissingBytes;
aacOverFlow.sample.unit.set(
data.subarray(0, frameMissingBytes),
frameOverflowBytes
);
track.samples.push(aacOverFlow.sample);

// logger.log(`AAC: append overflowing ${frameOverflowBytes} bytes to beginning of new PES`);
startOffset = aacOverFlow.missing;
// 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;
} else {
const frameOverflowBytes = sampleLength - frameMissingBytes;
aacOverFlow.sample.unit.set(
data.subarray(0, frameMissingBytes),
frameOverflowBytes
);
track.samples.push(aacOverFlow.sample);
startOffset = aacOverFlow.missing;
}
}
// look for ADTS header (0xFFFx)
let offset: number;
Expand Down Expand Up @@ -907,26 +913,20 @@ class TSDemuxer implements Demuxer {

// scan for aac samples
let frameIndex = 0;
let frame;
while (offset < len) {
if (ADTS.isHeader(data, offset)) {
if (offset + 5 < len) {
const frame = ADTS.appendFrame(track, data, offset, pts, frameIndex);
if (frame) {
if (frame.missing) {
this.aacOverFlow = frame;
} else {
offset += frame.length;
frameIndex++;
continue;
}
frame = ADTS.appendFrame(track, data, offset, pts, frameIndex);
offset += frame.length;
if (!frame.missing) {
frameIndex++;
for (; offset < len - 1; offset++) {
if (ADTS.isHeader(data, offset)) {
break;
}
}
// We are at an ADTS header, but do not have enough data for a frame
// Remaining data will be added to aacOverFlow
break;
} else {
// nothing found, keep looking
offset++;
this.aacOverFlow = frame;
break;
}
}
}
Expand Down
34 changes: 29 additions & 5 deletions tests/unit/demuxer/adts.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,9 @@ describe('parseFrameHeader', function () {
data[0] = 0xff;
data[1] = 0xf0; // protection_absent = 0
data[4] = 0x02; // frame_length is 16
expect(parseFrameHeader(data, 0, 0, 0, 0)).to.deep.equal({
expect(parseFrameHeader(data, 0)).to.deep.equal({
headerLength: 9,
frameLength: 7,
stamp: 0,
});
});

Expand All @@ -386,12 +385,12 @@ describe('parseFrameHeader', function () {
data[0] = 0xff;
data[1] = 0xf0; // protection_absent = 0
data[4] = 0x00; // frame_length is 0
expect(parseFrameHeader(data, 0, 0, 0, 0)).to.be.undefined;
expect(parseFrameHeader(data, 0)).to.be.undefined;
});
});

describe('appendFrame', function () {
it('should append the found sample to track and return some useful information', function () {
it('should append the found sample to track and return frame information', function () {
const track = {
samplerate: 64000,
samples: [],
Expand All @@ -414,7 +413,7 @@ describe('appendFrame', function () {
expect(track.samples.length).to.equal(1);
});

it('should not append sample when incomplete (aac overflow or progressive streaming)', function () {
it('should return an incomplete frame without appending when data is incomplete (aac overflow or progressive streaming)', function () {
const track = {
samplerate: 64000,
samples: [],
Expand All @@ -439,4 +438,29 @@ describe('appendFrame', function () {
});
expect(track.samples.length).to.equal(0);
});

it('should return an incomplete frame without appending when header is incomplete (aac overflow or progressive streaming)', function () {
const track = {
samplerate: 64000,
samples: [],
len: 0,
};
const data = new Uint8Array(new ArrayBuffer(2));
data[0] = 0xff;
data[1] = 0xf0; // protection_absent = 0

const frame = appendFrame(track, data, 0, 0, 0);
const unit = new Uint8Array(2);
unit.set(data.subarray(0, 2), 0);

expect(frame, JSON.stringify(frame)).to.deep.equal({
sample: {
unit,
pts: 0,
},
length: 2,
missing: -1,
});
expect(track.samples.length).to.equal(0);
});
});

0 comments on commit 9d66046

Please sign in to comment.