"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AVCDecoderConfigurationRecord = exports.H264AnnexBParser = exports.H264NaluAVC1 = exports.H264NaluPayload = exports.H264NaluType = void 0;
exports.annexb2avcc = annexb2avcc;
const sps_parser_1 = __importDefault(require("./sps-parser"));
var H264NaluType;
(function (H264NaluType) {
    H264NaluType[H264NaluType["kUnspecified"] = 0] = "kUnspecified";
    H264NaluType[H264NaluType["kSliceNonIDR"] = 1] = "kSliceNonIDR";
    H264NaluType[H264NaluType["kSliceDPA"] = 2] = "kSliceDPA";
    H264NaluType[H264NaluType["kSliceDPB"] = 3] = "kSliceDPB";
    H264NaluType[H264NaluType["kSliceDPC"] = 4] = "kSliceDPC";
    H264NaluType[H264NaluType["kSliceIDR"] = 5] = "kSliceIDR";
    H264NaluType[H264NaluType["kSliceSEI"] = 6] = "kSliceSEI";
    H264NaluType[H264NaluType["kSliceSPS"] = 7] = "kSliceSPS";
    H264NaluType[H264NaluType["kSlicePPS"] = 8] = "kSlicePPS";
    H264NaluType[H264NaluType["kSliceAUD"] = 9] = "kSliceAUD";
    H264NaluType[H264NaluType["kEndOfSequence"] = 10] = "kEndOfSequence";
    H264NaluType[H264NaluType["kEndOfStream"] = 11] = "kEndOfStream";
    H264NaluType[H264NaluType["kFiller"] = 12] = "kFiller";
    H264NaluType[H264NaluType["kSPSExt"] = 13] = "kSPSExt";
    H264NaluType[H264NaluType["kReserved0"] = 14] = "kReserved0";
})(H264NaluType || (exports.H264NaluType = H264NaluType = {}));
class H264NaluPayload {
    constructor() {
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "data", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
    }
}
exports.H264NaluPayload = H264NaluPayload;
class H264NaluAVC1 {
    constructor(nalu) {
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "data", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        const nalu_size = nalu.data.byteLength;
        this.type = nalu.type;
        this.data = new Uint8Array(4 + nalu_size); // 4 byte length-header + nalu payload
        const v = new DataView(this.data.buffer);
        // Fill 4 byte length-header
        v.setUint32(0, nalu_size);
        // Copy payload
        this.data.set(nalu.data, 4);
    }
}
exports.H264NaluAVC1 = H264NaluAVC1;
class H264AnnexBParser {
    constructor(data) {
        Object.defineProperty(this, "TAG", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: "H264AnnexBParser"
        });
        Object.defineProperty(this, "data_", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "current_startcode_offset_", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: 0
        });
        Object.defineProperty(this, "eof_flag_", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        this.data_ = data;
        this.current_startcode_offset_ = this.findNextStartCodeOffset(0);
    }
    findNextStartCodeOffset(start_offset) {
        let i = start_offset;
        const data = this.data_;
        for (;;) {
            if (i + 3 >= data.byteLength) {
                this.eof_flag_ = true;
                return data.byteLength;
            }
            // search 00 00 00 01 or 00 00 01
            const uint32 = (data[i + 0] << 24) |
                (data[i + 1] << 16) |
                (data[i + 2] << 8) |
                data[i + 3];
            const uint24 = (data[i + 0] << 16) | (data[i + 1] << 8) | data[i + 2];
            if (uint32 === 0x00000001 || uint24 === 0x000001) {
                return i;
            }
            else {
                i++;
            }
        }
    }
    readNextNaluPayload() {
        const data = this.data_;
        let nalu_payload = null;
        while (nalu_payload == null) {
            if (this.eof_flag_) {
                break;
            }
            // offset pointed to start code
            const startcode_offset = this.current_startcode_offset_;
            // nalu payload start offset
            let offset = startcode_offset;
            const u32 = (data[offset] << 24) |
                (data[offset + 1] << 16) |
                (data[offset + 2] << 8) |
                data[offset + 3];
            if (u32 === 0x00000001) {
                offset += 4;
            }
            else {
                offset += 3;
            }
            const nalu_type = data[offset] & 0x1f;
            const forbidden_bit = (data[offset] & 0x80) >>> 7;
            const next_startcode_offset = this.findNextStartCodeOffset(offset);
            this.current_startcode_offset_ = next_startcode_offset;
            if (nalu_type >= H264NaluType.kReserved0) {
                continue;
            }
            if (forbidden_bit !== 0) {
                // Log.e(this.TAG, `forbidden_bit near offset ${offset} should be 0 but has value ${forbidden_bit}`);
                continue;
            }
            const payload_data = data.subarray(offset, next_startcode_offset);
            nalu_payload = new H264NaluPayload();
            nalu_payload.type = nalu_type;
            nalu_payload.data = payload_data;
        }
        return nalu_payload;
    }
}
exports.H264AnnexBParser = H264AnnexBParser;
class AVCDecoderConfigurationRecord {
    // sps, pps: require Nalu without 4 byte length-header
    constructor(sps, pps, sps_details) {
        Object.defineProperty(this, "data", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        let length = 6 + 2 + sps.byteLength + 1 + 2 + pps.byteLength;
        let need_extra_fields = false;
        if (sps[3] !== 66 && sps[3] !== 77 && sps[3] !== 88) {
            need_extra_fields = true;
            length += 4;
        }
        const data = (this.data = new Uint8Array(length));
        data[0] = 0x01; // configurationVersion
        data[1] = sps[1]; // AVCProfileIndication
        data[2] = sps[2]; // profile_compatibility
        data[3] = sps[3]; // AVCLevelIndication
        data[4] = 0xff; // 111111 + lengthSizeMinusOne(3)
        data[5] = 0xe0 | 0x01; // 111 + numOfSequenceParameterSets
        const sps_length = sps.byteLength;
        data[6] = sps_length >>> 8; // sequenceParameterSetLength
        data[7] = sps_length & 0xff;
        let offset = 8;
        data.set(sps, 8);
        offset += sps_length;
        data[offset] = 1; // numOfPictureParameterSets
        const pps_length = pps.byteLength;
        data[offset + 1] = pps_length >>> 8; // pictureParameterSetLength
        data[offset + 2] = pps_length & 0xff;
        data.set(pps, offset + 3);
        offset += 3 + pps_length;
        if (need_extra_fields) {
            data[offset] = 0xfc | sps_details.chroma_format_idc;
            data[offset + 1] = 0xf8 | (sps_details.bit_depth_luma - 8);
            data[offset + 2] = 0xf8 | (sps_details.bit_depth_chroma - 8);
            data[offset + 3] = 0x00; // number of sps ext
            offset += 4;
        }
    }
    getData() {
        return this.data;
    }
}
exports.AVCDecoderConfigurationRecord = AVCDecoderConfigurationRecord;
function annexb2avcc(data) {
    const annexb_parser = new H264AnnexBParser(data);
    let nalu_payload = null;
    const video_init_segment_dispatched_ = false;
    const video_metadata_changed_ = false;
    const video_metadata_ = {
        sps: undefined,
        pps: undefined,
        details: undefined,
    };
    while ((nalu_payload = annexb_parser.readNextNaluPayload()) != null) {
        const nalu_avc1 = new H264NaluAVC1(nalu_payload);
        if (nalu_avc1.type === H264NaluType.kSliceSPS) {
            // Notice: parseSPS requires Nalu without startcode or length-header
            const details = sps_parser_1.default.parseSPS(nalu_payload.data);
            if (!video_init_segment_dispatched_) {
                video_metadata_.sps = nalu_avc1;
                video_metadata_.details = details;
            }
        }
        else if (nalu_avc1.type === H264NaluType.kSlicePPS) {
            if (!video_init_segment_dispatched_ || video_metadata_changed_) {
                video_metadata_.pps = nalu_avc1;
            }
        }
    }
    const sps_without_header = video_metadata_.sps.data.subarray(4);
    const pps_without_header = video_metadata_.pps.data.subarray(4);
    const details = video_metadata_.details;
    const avcc = new AVCDecoderConfigurationRecord(sps_without_header, pps_without_header, details);
    return avcc.getData();
}
//# sourceMappingURL=h264.js.map