369 lines
12 KiB
JavaScript
369 lines
12 KiB
JavaScript
// META: global=window,dedicatedworker
|
|
// META: script=/webcodecs/video-encoder-utils.js
|
|
// META: variant=?h264_annexb_software
|
|
// META: variant=?h264_annexb_hardware
|
|
// META: variant=?h265_annexb_software
|
|
// META: variant=?h265_annexb_hardware
|
|
|
|
var ENCODER_CONFIG = null;
|
|
var ANNEXB_CODEC = ''
|
|
promise_setup(async () => {
|
|
const config = {
|
|
'?h264_annexb_software': {
|
|
codec: 'avc1.42001E',
|
|
avc: {format: 'annexb'},
|
|
hardwareAcceleration: 'prefer-software',
|
|
},
|
|
'?h264_annexb_hardware': {
|
|
codec: 'avc1.42001E',
|
|
avc: {format: 'annexb'},
|
|
hardwareAcceleration: 'prefer-hardware',
|
|
},
|
|
'?h265_annexb_software': {
|
|
codec: 'hvc1.1.6.L123.00',
|
|
hevc: {format: 'annexb'},
|
|
hardwareAcceleration: 'prefer-software',
|
|
},
|
|
'?h265_annexb_hardware': {
|
|
codec: 'hvc1.1.6.L123.00',
|
|
hevc: {format: 'annexb'},
|
|
hardwareAcceleration: 'prefer-hardware',
|
|
}
|
|
}[location.search];
|
|
if (config.avc) {
|
|
ANNEXB_CODEC = 'h264'
|
|
}
|
|
if (config.hevc) {
|
|
ANNEXB_CODEC = 'h265'
|
|
}
|
|
config.width = 320;
|
|
config.height = 200;
|
|
config.bitrate = 1000000;
|
|
config.framerate = 30;
|
|
ENCODER_CONFIG = config;
|
|
});
|
|
|
|
// The code is inspired from https://source.chromium.org/chromium/chromium/src/+/main:media/formats/mp4/avc.cc;l=190;drc=a6567f4fac823a8a319652bdb5070b5b72a60f30
|
|
// and https://source.chromium.org/chromium/chromium/src/+/main:media/formats/mp4/hevc.cc;l=425;drc=a6567f4fac823a8a319652bdb5070b5b72a60f30?
|
|
|
|
function checkNaluSyntax(test, chunk) {
|
|
test.step(() => {
|
|
|
|
const buffer = new Uint8Array(chunk.byteLength);
|
|
const keyFrame = chunk.type === "key";
|
|
chunk.copyTo(buffer);
|
|
|
|
const kAUDAllowed = 1;
|
|
const kBeforeFirstVCL = 2; // VCL == nal_unit_types 1-5
|
|
const kAfterFirstVCL = 3;
|
|
const kEOStreamAllowed = 4;
|
|
const kNoMoreDataAllowed = 5;
|
|
// Define constants for h264 NALU types
|
|
const kAUD = 9;
|
|
const kSEIMessage = 6;
|
|
const kPrefix = 14;
|
|
const kSubsetSPS = 15;
|
|
const kDPS = 16;
|
|
const kReserved17 = 17;
|
|
const kReserved18 = 18;
|
|
const kPPS = 8;
|
|
const kSPS = 7;
|
|
const kSPSExt = 13;
|
|
const kNonIDRSlice = 1;
|
|
const kSliceDataA = 2;
|
|
const kSliceDataB = 3;
|
|
const kSliceDataC = 4;
|
|
const kIDRSlice = 5;
|
|
const kCodedSliceAux = 19;
|
|
const kEOSeq = 10;
|
|
const kEOStream = 11;
|
|
const kFiller = 12;
|
|
const kUnspecified = 0;
|
|
// Define constants for h265 NALU types
|
|
const AUD_NUT = 35;
|
|
const VPS_NUT = 32;
|
|
const SPS_NUT = 33;
|
|
const PPS_NUT = 34;
|
|
const PREFIX_SEI_NUT = 39;
|
|
const RSV_NVCL41 = 41;
|
|
const RSV_NVCL42 = 42;
|
|
const RSV_NVCL43 = 43;
|
|
const RSV_NVCL44 = 44;
|
|
const UNSPEC48 = 48;
|
|
const UNSPEC49 = 49;
|
|
const UNSPEC50 = 50;
|
|
const UNSPEC51 = 51;
|
|
const UNSPEC52 = 52;
|
|
const UNSPEC53 = 53;
|
|
const UNSPEC54 = 54;
|
|
const UNSPEC55 = 55;
|
|
const FD_NUT = 38;
|
|
const SUFFIX_SEI_NUT = 40;
|
|
const RSV_NVCL45 = 45;
|
|
const RSV_NVCL46 = 46;
|
|
const RSV_NVCL47 = 47;
|
|
const UNSPEC56 = 56;
|
|
const UNSPEC57 = 57;
|
|
const UNSPEC58 = 58;
|
|
const UNSPEC59 = 59;
|
|
const UNSPEC60 = 60;
|
|
const UNSPEC61 = 61;
|
|
const UNSPEC62 = 62;
|
|
const UNSPEC63 = 63;
|
|
const EOS_NUT = 36;
|
|
const EOB_NUT = 37;
|
|
const TRAIL_N = 0;
|
|
const TRAIL_R = 1;
|
|
const TSA_N = 2;
|
|
const TSA_R = 3;
|
|
const STSA_N = 4;
|
|
const STSA_R = 5;
|
|
const RADL_N = 6;
|
|
const RADL_R = 7;
|
|
const RASL_N = 8;
|
|
const RASL_R = 9;
|
|
const RSV_VCL_N10 = 10;
|
|
const RSV_VCL_R11 = 11;
|
|
const RSV_VCL_N12 = 12;
|
|
const RSV_VCL_R13 = 13;
|
|
const RSV_VCL_N14 = 14;
|
|
const RSV_VCL_R15 = 15;
|
|
const RSV_VCL24 = 24;
|
|
const RSV_VCL25 = 25;
|
|
const RSV_VCL26 = 26;
|
|
const RSV_VCL27 = 27;
|
|
const RSV_VCL28 = 28;
|
|
const RSV_VCL29 = 29;
|
|
const RSV_VCL30 = 30;
|
|
const RSV_VCL31 = 31;
|
|
const BLA_W_LP = 16;
|
|
const BLA_W_RADL = 17;
|
|
const BLA_N_LP = 18;
|
|
const IDR_W_RADL = 19;
|
|
const IDR_N_LP = 20;
|
|
const CRA_NUT = 21;
|
|
const RSV_IRAP_VCL22 = 22;
|
|
const RSV_IRAP_VCL23 = 23;
|
|
|
|
let order_state = kAUDAllowed;
|
|
let lastBytes = [0xFF, 0xFF, 0xFF];
|
|
for (let pos = 0; pos < buffer.length; pos++) {
|
|
if (lastBytes[0] == 0x00 && lastBytes[1] == 0x00
|
|
&& lastBytes[2] == 0x01) {
|
|
let naluType = buffer[pos] & 0x1f;
|
|
if (ANNEXB_CODEC === "h264") {
|
|
switch (naluType) {
|
|
case kAUD:
|
|
assert_less_than_equal(order_state, kAUDAllowed, "Unexpected AUD in order_state " + order_state);
|
|
order_state = kBeforeFirstVCL;
|
|
break;
|
|
|
|
case kSEIMessage:
|
|
case kPrefix:
|
|
case kSubsetSPS:
|
|
case kDPS:
|
|
case kReserved17:
|
|
case kReserved18:
|
|
case kPPS:
|
|
case kSPS:
|
|
assert_less_than_equal(order_state, kBeforeFirstVCL, "Unexpected NALU type " + naluType + " in order_state " + order_state);
|
|
order_state = kBeforeFirstVCL;
|
|
break;
|
|
|
|
case kSPSExt:
|
|
assert_equals(last_nalu_type, kSPS, "SPS extension does not follow an SPS.");
|
|
break;
|
|
|
|
case kNonIDRSlice:
|
|
case kSliceDataA:
|
|
case kSliceDataB:
|
|
case kSliceDataC:
|
|
case kIDRSlice:
|
|
assert_less_than_equal(order_state, kAfterFirstVCL, "Unexpected VCL in order_state " + order_state);
|
|
assert_equals(naluType == kIDRSlice, keyFrame, "Keyframe indicator does not match: " + (naluType == kIDRSlice) + " versus " + keyFrame);
|
|
order_state = kAfterFirstVCL;
|
|
break;
|
|
|
|
case kCodedSliceAux:
|
|
assert_equals(order_state, kAfterFirstVCL, "Unexpected extension in order_state " + order_state);
|
|
break;
|
|
|
|
case kEOSeq:
|
|
assert_equals(order_state, kAfterFirstVCL, "Unexpected EOSeq in order_state " + order_state);
|
|
order_state = kEOStreamAllowed;
|
|
break;
|
|
|
|
case kEOStream:
|
|
assert_greater_than(kAfterFirstVCL, order_state, "Unexpected EOStream in order_state " + order_state);
|
|
order_state = kNoMoreDataAllowed;
|
|
break;
|
|
// These syntax elements are to simply be ignored according to H264
|
|
// Annex B 7.4.2.7
|
|
case kFiller:
|
|
case kUnspecified:
|
|
// These syntax elements are to simply be ignored according to H264 Annex B 7.4.2.7
|
|
break;
|
|
|
|
default:
|
|
assert_greater_than(naluType, 19, "NALU TYPE smaller than 20 for unknown type");
|
|
break;
|
|
}
|
|
} else if (ANNEXB_CODEC === 'h265') {
|
|
// When any VPS NAL units, SPS NAL units, PPS NAL units, prefix SEI NAL
|
|
// units, NAL units with nal_unit_type in the range of
|
|
// RSV_NVCL41..RSV_NVCL44, or NAL units with nal_unit_type in the range of
|
|
// UNSPEC48..UNSPEC55 are present, they shall not follow the last VCL NAL
|
|
// unit of the access unit.
|
|
|
|
switch (naluType) {
|
|
case AUD_NUT:
|
|
assert_less_than_equal(order_state, kAUDAllowed, "Unexpected AUD in order_state " + order_state);
|
|
order_state = kBeforeFirstVCL;
|
|
break;
|
|
|
|
case VPS_NUT:
|
|
case SPS_NUT:
|
|
case PPS_NUT:
|
|
case PREFIX_SEI_NUT:
|
|
case RSV_NVCL41:
|
|
case RSV_NVCL42:
|
|
case RSV_NVCL43:
|
|
case RSV_NVCL44:
|
|
case UNSPEC48:
|
|
case UNSPEC49:
|
|
case UNSPEC50:
|
|
case UNSPEC51:
|
|
case UNSPEC52:
|
|
case UNSPEC53:
|
|
case UNSPEC54:
|
|
case UNSPEC55:
|
|
assert_less_than_equal(order_state, kBeforeFirstVCL, "Unexpected NALU type " + nalu.nal_unit_type + " in order_state " + order_state);
|
|
order_state = kBeforeFirstVCL;
|
|
break;
|
|
// NAL units having nal_unit_type equal to FD_NUT or SUFFIX_SEI_NUT or in
|
|
// the range of RSV_NVCL45..RSV_NVCL47 or UNSPEC56..UNSPEC63 shall not
|
|
// precede the first VCL NAL unit of the access unit.
|
|
case FD_NUT:
|
|
case SUFFIX_SEI_NUT:
|
|
case RSV_NVCL45:
|
|
case RSV_NVCL46:
|
|
case RSV_NVCL47:
|
|
case UNSPEC56:
|
|
case UNSPEC57:
|
|
case UNSPEC58:
|
|
case UNSPEC59:
|
|
case UNSPEC60:
|
|
case UNSPEC61:
|
|
case UNSPEC62:
|
|
case UNSPEC63:
|
|
assert_less_than_equal(order_state, kAfterFirstVC, "Unexpected NALU type " + nalu.nal_unit_type + " in order_state " + order_state);
|
|
break;
|
|
|
|
// When an end of sequence NAL unit is present, it shall be the last NAL
|
|
// unit among all NAL units in the access unit other than an end of
|
|
// bitstream NAL unit (when present).
|
|
case EOS_NUT:
|
|
assert_equals(order_state, kAfterFirstVCL, "Unexpected EOS in order_state " + order_state);
|
|
order_state = kEOBitstreamAllowed;
|
|
break;
|
|
// When an end of bitstream NAL unit is present, it shall be the last NAL
|
|
// unit in the access unit.
|
|
case EOB_NUT:
|
|
assert_less_than_equal(order_state, kAfterFirstVCL, "Unexpected EOB in order_state " + order_state);
|
|
order_state = kNoMoreDataAllowed;
|
|
break;
|
|
// VCL, non-IRAP
|
|
case TRAIL_N:
|
|
case TRAIL_R:
|
|
case TSA_N:
|
|
case TSA_R:
|
|
case STSA_N:
|
|
case STSA_R:
|
|
case RADL_N:
|
|
case RADL_R:
|
|
case RASL_N:
|
|
case RASL_R:
|
|
case RSV_VCL_N10:
|
|
case RSV_VCL_R11:
|
|
case RSV_VCL_N12:
|
|
case RSV_VCL_R13:
|
|
case RSV_VCL_N14:
|
|
case RSV_VCL_R15:
|
|
case RSV_VCL24:
|
|
case RSV_VCL25:
|
|
case RSV_VCL26:
|
|
case RSV_VCL27:
|
|
case RSV_VCL28:
|
|
case RSV_VCL29:
|
|
case RSV_VCL30:
|
|
case RSV_VCL31:
|
|
assert_less_than_equal(order_state, kAfterFirstVCL, "Unexpected VCL in order_state " + order_state);
|
|
order_state = kAfterFirstVCL;
|
|
break;
|
|
// VCL, IRAP
|
|
case BLA_W_LP:
|
|
case BLA_W_RADL:
|
|
case BLA_N_LP:
|
|
case IDR_W_RADL:
|
|
case IDR_N_LP:
|
|
case CRA_NUT:
|
|
case RSV_IRAP_VCL22:
|
|
case RSV_IRAP_VCL23:
|
|
assert_less_than_equal(order_state, kAfterFirstVCL, "Unexpected VCL in order_state " + order_state);
|
|
assert_equals(keyFrame, true, "The frame is coded as Keyframe, but indicator does not match");
|
|
order_state = kAfterFirstVCL;
|
|
break;
|
|
|
|
default:
|
|
assert_true(false, "Unsupported NALU type " + naluType);
|
|
break;
|
|
};
|
|
|
|
}
|
|
last_nalu_type = naluType;
|
|
}
|
|
lastBytes.push(buffer[pos]);
|
|
lastBytes.shift(); // advance reading
|
|
}
|
|
})
|
|
}
|
|
|
|
async function runAnnexBTest(t) {
|
|
let encoder_config = { ...ENCODER_CONFIG };
|
|
const w = encoder_config.width;
|
|
const h = encoder_config.height;
|
|
let frames_to_encode = 16;
|
|
|
|
await checkEncoderSupport(t, encoder_config);
|
|
|
|
const encodedResults = [];
|
|
const encoder_init = {
|
|
output(chunk, metadata) {
|
|
encodedResults.push(chunk);
|
|
},
|
|
error(e) {
|
|
assert_unreached(e.message);
|
|
}
|
|
};
|
|
|
|
let encoder = new VideoEncoder(encoder_init);
|
|
encoder.configure(encoder_config);
|
|
|
|
for (let i = 0; i < frames_to_encode; i++) {
|
|
let frame = createDottedFrame(w, h, i);
|
|
let keyframe = (i % 5 == 0);
|
|
encoder.encode(frame, { keyFrame: keyframe });
|
|
frame.close();
|
|
}
|
|
|
|
await encoder.flush();
|
|
encoder.close();
|
|
|
|
encodedResults.forEach((chunk) => checkNaluSyntax(t, chunk));
|
|
|
|
assert_greater_than(encodedResults.length, 0, "frames_encoded");
|
|
}
|
|
|
|
promise_test(async t => {
|
|
return runAnnexBTest(t);
|
|
}, 'Verify stream compliance h26x annexb');
|