| // 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'); |