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