Skip to content

Commit c86af05

Browse files
committed
Add test for Annex B to length-prefixed conversion
1 parent 831e838 commit c86af05

4 files changed

Lines changed: 62 additions & 15 deletions

File tree

src/codec-data.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,17 +212,6 @@ export const concatNalUnitsInLengthPrefixed = (nalUnits: Uint8Array[], lengthSiz
212212
return result;
213213
};
214214

215-
/** Converts an AVC packet in Annex B format to length-prefixed format. */
216-
export const transformAnnexBToLengthPrefixed = (packetData: Uint8Array) => {
217-
const nalUnits = findNalUnitsInAnnexB(packetData);
218-
if (nalUnits.length === 0) {
219-
// It's not valid Annex B data
220-
return null;
221-
}
222-
223-
return concatNalUnitsInLengthPrefixed(nalUnits, 4);
224-
};
225-
226215
// Data specified in ISO 14496-15
227216
export type AvcDecoderConfigurationRecord = {
228217
configurationVersion: number;

src/isobmff/isobmff-muxer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@ import {
2525
import { BufferTarget } from '../target';
2626
import { EncodedPacket, PacketType } from '../packet';
2727
import {
28+
concatNalUnitsInLengthPrefixed,
2829
extractAvcDecoderConfigurationRecord,
2930
extractHevcDecoderConfigurationRecord,
31+
findNalUnitsInAnnexB,
3032
serializeAvcDecoderConfigurationRecord,
3133
serializeHevcDecoderConfigurationRecord,
32-
transformAnnexBToLengthPrefixed,
3334
} from '../codec-data';
3435
import { buildIsobmffMimeType } from './isobmff-misc';
3536
import { MAX_BOX_HEADER_SIZE, MIN_BOX_HEADER_SIZE } from './isobmff-reader';
@@ -464,15 +465,18 @@ export class IsobmffMuxer extends Muxer {
464465

465466
let packetData = packet.data;
466467
if (trackData.info.requiresAnnexBTransformation) {
467-
const transformedData = transformAnnexBToLengthPrefixed(packetData);
468-
if (!transformedData) {
468+
const nalUnits = findNalUnitsInAnnexB(packetData);
469+
if (nalUnits.length === 0) {
470+
// It's not valid Annex B data
469471
throw new Error(
470472
'Failed to transform packet data. Make sure all packets are provided in Annex B format, as'
471473
+ ' specified in ITU-T-REC-H.264 and ITU-T-REC-H.265.',
472474
);
473475
}
474476

475-
packetData = transformedData;
477+
// We don't strip things like SPS or PPS NALUs here, mainly because they can also appear in the middle
478+
// of a stream and potentially modify the parameters of it. So, let's just leave them in to be sure.
479+
packetData = concatNalUnitsInLengthPrefixed(nalUnits, 4);
476480
}
477481

478482
const timestamp = this.validateAndNormalizeTimestamp(
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expect, test } from 'vitest';
2+
import { Input } from '../../src/input.js';
3+
import { BufferSource, FilePathSource } from '../../src/source.js';
4+
import path from 'node:path';
5+
import { ALL_FORMATS } from '../../src/input-format.js';
6+
import { Output } from '../../src/output.js';
7+
import { Mp4OutputFormat } from '../../src/output-format.js';
8+
import { BufferTarget } from '../../src/target.js';
9+
import { Conversion } from '../../src/conversion.js';
10+
import { EncodedPacketSink } from '../../src/media-sink.js';
11+
import { extractAvcNalUnits } from '../../src/codec-data.js';
12+
13+
const __dirname = new URL('.', import.meta.url).pathname;
14+
15+
test('Annex B to length-prefixed conversion, MP4', async () => {
16+
using originalInput = new Input({
17+
source: new FilePathSource(path.join(__dirname, '..', 'public/annex-b-avc.mkv')),
18+
formats: ALL_FORMATS,
19+
});
20+
const originalVideoTrack = (await originalInput.getPrimaryVideoTrack())!;
21+
const originalDecoderConfig = (await originalVideoTrack.getDecoderConfig())!;
22+
expect(originalDecoderConfig.description).toBeUndefined();
23+
expect(originalVideoTrack.codec).toBe('avc');
24+
25+
const originalSink = new EncodedPacketSink(originalVideoTrack);
26+
const originalFirstPacket = await originalSink.getFirstPacket();
27+
expect([...originalFirstPacket!.data.slice(0, 4)]).toEqual([0, 0, 0, 1]);
28+
29+
const originalNalUnits = extractAvcNalUnits(originalFirstPacket!.data, originalDecoderConfig);
30+
31+
const output = new Output({
32+
format: new Mp4OutputFormat(),
33+
target: new BufferTarget(),
34+
});
35+
36+
const conversion = await Conversion.init({ input: originalInput, output });
37+
await conversion.execute();
38+
39+
using newInput = new Input({
40+
source: new BufferSource(output.target.buffer!),
41+
formats: ALL_FORMATS,
42+
});
43+
const newVideoTrack = (await newInput.getPrimaryVideoTrack())!;
44+
const newDecoderConfig = (await newVideoTrack.getDecoderConfig())!;
45+
expect(newDecoderConfig.description).toBeDefined();
46+
expect(newVideoTrack.codec).toBe('avc');
47+
48+
const newSink = new EncodedPacketSink(newVideoTrack);
49+
const newFirstPacket = await newSink.getFirstPacket();
50+
expect([...newFirstPacket!.data.slice(0, 4)]).not.toEqual([0, 0, 0, 1]); // Successfully converted
51+
52+
const newNalUnits = extractAvcNalUnits(newFirstPacket!.data, newDecoderConfig);
53+
expect(newNalUnits).toEqual(originalNalUnits); // Content is the same though
54+
});

test/public/annex-b-avc.mkv

1.26 MB
Binary file not shown.

0 commit comments

Comments
 (0)