Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add codec string parsing for h264, h265, and mp4a #4996

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/remux/passthrough-remuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,10 @@ function getParsedTrackCodec(
if (parsedCodec && parsedCodec.length > 4) {
return parsedCodec;
}
// Since mp4-tools cannot parse full codec string (see 'TODO: Parse codec details'... in mp4-tools)
// Provide defaults based on codec type
// This allows for some playback of some fmp4 playlists without CODECS defined in manifest
if (parsedCodec === 'hvc1' || parsedCodec === 'hev1') {
return 'hvc1.1.c.L120.90';
return 'hvc1.1.6.L120.90';
}
if (parsedCodec === 'av01') {
return 'av01.0.04M.08';
Expand Down
103 changes: 97 additions & 6 deletions src/utils/mp4-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,103 @@ export function parseInitSegment(initSegment: Uint8Array): InitData {
let codec;
if (stsd) {
codec = bin2str(stsd.subarray(12, 16));
// TODO: Parse codec details to be able to build MIME type.
// stsd.start += 8;
// const codecBox = findBox(stsd, [codec])[0];
// if (codecBox) {
// TODO: Codec parsing support for avc1, mp4a, hevc, av01...
// }
// Parse codec details to be able to build MIME type
// TODO: Codec parsing support for AV1
const toHex = (x: number): string => {
return ('0' + x.toString(16).toUpperCase()).slice(-2);
};

// Handle H264
if (
codec.slice(0, 3) === 'avc' &&
codec[3] >= '1' &&
codec[3] <= '4' &&
stsd.length > 102 &&
bin2str(stsd.subarray(98, 102)) === 'avcC'
) {
// profile + compatibility + level
codec +=
'.' + toHex(stsd[111]) + toHex(stsd[112]) + toHex(stsd[113]);
}

// Handle H265
else if (
(codec === 'hev1' || codec === 'hvc1') &&
stsd.length > 102 &&
bin2str(stsd.subarray(98, 102)) === 'hvcC'
) {
// Profile Space
const profileByte = stsd[103];
const profileSpace = { 0: '', 1: 'A', 2: 'B', 3: 'C' }[
profileByte >> 6
];
const generalProfileIdc = profileByte & 31;
codec += '.' + profileSpace + generalProfileIdc;

// Compatibility
let reversed = 0;
for (let i = 0; i < 4; ++i) {
// byte number
for (let j = 0; j < 8; ++j) {
// bit number
reversed |=
((stsd[i + 104] >> (7 - j)) & 1) << (31 - 8 * i - j);
}
}
codec += '.' + toHex(reversed >>> 0);

// Tier Flag
codec += (profileByte & 32 ? '.H' : '.L') + stsd[114];

// Constraint String
let hasByte = false;
let constraintString = '';
for (let i = 113; i > 107; --i) {
if (stsd[i] || hasByte) {
constraintString = '.' + toHex(stsd[i]) + constraintString;
hasByte = true;
}
}
codec += constraintString;
}

// Handle Audio
else if (codec === 'mp4a') {
// Parse ES Descriptors
let i: number;
// oti
for (i = 0; i < stsd.length - 5; ++i) {
if (
stsd[i] == 4 &&
stsd[i + 1] == 128 &&
stsd[i + 2] == 128 &&
stsd[i + 3] == 128
) {
codec += '.' + toHex(stsd[i + 5]);
break;
}
}

// dsi
for (i = 0; i < stsd.length - 6; ++i) {
if (
stsd[i] == 5 &&
stsd[i + 1] == 128 &&
stsd[i + 2] == 128 &&
stsd[i + 3] == 128
) {
let dsi = (stsd[i + 5] & 248) >> 3;
if (dsi == 31 && stsd[i + 4] >= 2) {
dsi =
32 +
((stsd[i + 5] & 7) << 3) +
((stsd[i + 6] & 224) >> 5);
}
codec += '.' + dsi;
break;
}
}
}
}
result[trackId] = { timescale, type };
result[type] = { timescale, id: trackId, codec };
Expand Down