webcodecs配置codec和description

1、h264/h265提取出00 00 00 01裸数据,根据SP构造description

复制代码
class HEVCParser {
    constructor() {
        this.byteOffset = 0;
        this.bitOffset = 0;
        this.currentByte = 0;
        this.hevcspsInfo = {};
    }

    /**
     * 读取指定数量的比特
     * @param {Uint8Array} data - 数据数组
     * @param {number} bitnum - 要读取的比特数
     * @returns {number} - 读取的值
     */
    read_bits(data, numBits) {
        let value = 0;
        for (let i = 0; i < numBits; i++) {
            if (this.bitOffset === 0) {
                this.currentByte = data[this.byteOffset++];
            }
            const bit = (this.currentByte >> (7 - this.bitOffset)) & 1;
            value = (value << 1) | bit;
            this.bitOffset = (this.bitOffset + 1) % 8;
        }
        // console.log("this.byteOffset:" + this.byteOffset + ",this.bitOffset:" + this.bitOffset);
        return value;
    }
    read_bits_ue(data) {
        let zeroBits = -1;
        let bitval = 0;

        while (!bitval) {
            bitval = this.read_bits(data, 1);
            zeroBits++;
        }

        const code = (1 << zeroBits) - 1 + this.read_bits(data, zeroBits);
        return code;
    }
    /**
     * HEVC防竞争字节过滤
     * @param {Uint8Array} data - 输入数据
     * @returns {Uint8Array} - 过滤后的数据
     */
    hevc_filter_emulation_byte(data) {
        const result = [];
        let delimitercnt = 0;

        for (let i = 0; i < data.length; i++) {
            if (delimitercnt === 2 && data[i] < 0x03) {
                throw new Error("NALU内不能出现分隔符");
            }

            if (delimitercnt === 2 && data[i] === 0x03) {
                if (i === data.length - 1) {
                    break;
                }
                if (data[i + 1] > 0x03) {
                    throw new Error("语法非法");
                }
                delimitercnt = 0;
                continue;
            }

            result.push(data[i]);

            if (data[i] === 0x00) {
                delimitercnt++;
            } else {
                delimitercnt = 0;
            }
        }

        return new Uint8Array(result);
    }

    /**
     * 解析H.265 SPS(序列参数集)
     * @param {Uint8Array} spsData - SPS数据
     * @returns {Object} - 解析结果
     */
    hevc_analysis_sps(sps) {
        let spsData = this.hevc_filter_emulation_byte(sps);
        // 重置读取状态
        this.byteOffset = 0;
        const profilePresentFlag = 1;
        this.read_bits(spsData, 16);
        // 读取基本参数
        this.read_bits(spsData, 4);  // sps_video_parameter_set_id
        this.hevcspsInfo.sps_max_sub_layers = this.read_bits(spsData, 3);  // sps_max_sub_layers_minus1
        this.read_bits(spsData, 1);  // sps_temporal_id_nesting_flag

        // // profile_tier_level start
        if (profilePresentFlag) {
            this.hevcspsInfo.general_profile_space = this.read_bits(spsData, 2);  // s
            this.hevcspsInfo.general_tier_flag = this.read_bits(spsData, 1);  // general_tier_flag
            this.hevcspsInfo.general_profile_idc = this.read_bits(spsData, 5);  // general_profile_idc

            this.hevcspsInfo.general_profile_compatibility_flags = 0;
            for (let i = 0; i < 32; i++) {
                if (this.read_bits(spsData, 1)) {
                    this.hevcspsInfo.general_profile_compatibility_flags |= (1 << (31 - i));
                }
            }

            // this.read_bits(spsData, 1);  // general_progressive_source_flag
            // this.read_bits(spsData, 1);  // general_interlaced_source_falg
            // this.read_bits(spsData, 1);  // general_non_packed_constraint_flag
            // this.read_bits(spsData, 1);  // general_frame_only_constraint_falg

            // // 复杂的条件判断逻辑
            // if (this.hevcspsInfo.general_profile_idc === 4 || this.hevcspsInfo.general_profile_compatibility_flag[4] ||
            //     this.hevcspsInfo.general_profile_idc === 5 || this.hevcspsInfo.general_profile_compatibility_flag[5] ||
            //     this.hevcspsInfo.general_profile_idc === 6 || this.hevcspsInfo.general_profile_compatibility_flag[6] ||
            //     this.hevcspsInfo.general_profile_idc === 7 || this.hevcspsInfo.general_profile_compatibility_flag[7] ||
            //     this.hevcspsInfo.general_profile_idc === 8 || this.hevcspsInfo.general_profile_compatibility_flag[8] ||
            //     this.hevcspsInfo.general_profile_idc === 9 || this.hevcspsInfo.general_profile_compatibility_flag[9] ||
            //     this.hevcspsInfo.general_profile_idc === 10 || this.hevcspsInfo.general_profile_compatibility_flag[10] ||
            //     this.hevcspsInfo.general_profile_idc === 11 || this.hevcspsInfo.general_profile_compatibility_flag[11]) {

            //     this.read_bits(spsData, 1);  // general_max_12bit_constraint_flag
            //     this.read_bits(spsData, 1);  // general_max_10bit_constraint_falg
            //     this.read_bits(spsData, 1);  // general_max_8bit_constraint_falg
            //     this.read_bits(spsData, 1);  // general_max_422chroma_constraint_flag
            //     this.read_bits(spsData, 1);  // general_max_420chroma_constraint_flag
            //     this.read_bits(spsData, 1);  // general_max_monochrome_constraint_flag
            //     this.read_bits(spsData, 1);  // general_intra_constraint_flag
            //     this.read_bits(spsData, 1);  // general_one_picture_only_constraint_flag
            //     this.read_bits(spsData, 1);  // general_lower_bit_rate_constraint_flag

            //     if (this.hevcspsInfo.general_profile_idc === 5 || this.hevcspsInfo.general_profile_compatibility_flag[5] ||
            //         this.hevcspsInfo.general_profile_idc === 9 || this.hevcspsInfo.general_profile_compatibility_flag[9] ||
            //         this.hevcspsInfo.general_profile_idc === 10 || this.hevcspsInfo.general_profile_compatibility_flag[10] ||
            //         this.hevcspsInfo.general_profile_idc === 11 || this.hevcspsInfo.general_profile_compatibility_flag[11]) {

            //         this.read_bits(spsData, 1);   // general_max_14bit_constraint_flag
            //         this.read_bits(spsData, 33);  // general_reserved_zero_33bits
            //     } else {
            //         this.read_bits(spsData, 34);  // general_reserved_zero_34bits
            //     }
            // } else if (this.hevcspsInfo.general_profile_idc === 2 || this.hevcspsInfo.general_profile_compatibility_flag[2]) {
            //     this.read_bits(spsData, 7);  // general_reserved_zero_7bits
            //     this.read_bits(spsData, 1);  // general_one_picture_only_constraint_flag
            //     this.read_bits(spsData, 35); // general_reserved_zero_34bits
            // } else {
            //     this.read_bits(spsData, 43);  // general_reserved_zero_43bits
            // }

            // if (this.hevcspsInfo.general_profile_idc === 1 || this.hevcspsInfo.general_profile_compatibility_flag[1] ||
            //     this.hevcspsInfo.general_profile_idc === 2 || this.hevcspsInfo.general_profile_compatibility_flag[2] ||
            //     this.hevcspsInfo.general_profile_idc === 3 || this.hevcspsInfo.general_profile_compatibility_flag[3] ||
            //     this.hevcspsInfo.general_profile_idc === 4 || this.hevcspsInfo.general_profile_compatibility_flag[4] ||
            //     this.hevcspsInfo.general_profile_idc === 5 || this.hevcspsInfo.general_profile_compatibility_flag[5] ||
            //     this.hevcspsInfo.general_profile_idc === 9 || this.hevcspsInfo.general_profile_compatibility_flag[9] ||
            //     this.hevcspsInfo.general_profile_idc === 11 || this.hevcspsInfo.general_profile_compatibility_flag[11]) {

            //     this.read_bits(spsData, 1);  // general_inbld_flag
            // } else {
            //     this.read_bits(spsData, 1);  // general_reserved_zero_bit
            // }
        }
        // general_constraint_indicator_flags (48 bits)
        this.hevcspsInfo.general_constraint_indicator_flags = new Array(6);
        for (let i = 0; i < 6; i++) {
            this.hevcspsInfo.general_constraint_indicator_flags[i] = this.read_bits(spsData, 8);
        }
        this.hevcspsInfo.general_level_idc = this.read_bits(spsData, 8);

        // 读取子层信息
        // sub_layer_profile_present_flag and sub_layer_level_present_flag
        this.hevcspsInfo.sub_layer_profile_present_flag = [];
        this.hevcspsInfo.sub_layer_level_present_flag = [];
        for (let i = 0; i < this.hevcspsInfo.sps_max_sub_layers; i++) {
            this.hevcspsInfo.sub_layer_profile_present_flag[i] = this.read_bits(spsData, 1);  // sub_layer_profile_present_flag[i]
            this.hevcspsInfo.sub_layer_level_present_flag[i] = this.read_bits(spsData, 1);  // sub_layer_level_present_flag[i]
        }

        if (this.hevcspsInfo.sps_max_sub_layers > 0) {
            for (let i = this.hevcspsInfo.sps_max_sub_layers; i < 8; i++) {
                this.read_bits(spsData, 2);   // reserved_zero_2bits[i]
            }
        }

        // 读取关键视频参数
        this.read_bits_ue(spsData);   // sps_seq_parameter_set_id
        this.hevcspsInfo.chroma_format_idc = this.read_bits_ue(spsData);   // chroma_format_idc

        if (this.hevcspsInfo.chroma_format_idc === 3) {
            this.read_bits(spsData, 1); // separate_colour_plane_flag;
        }

        this.hevcspsInfo.pic_width_in_luma_samples = this.read_bits_ue(spsData);
        this.hevcspsInfo.pic_height_in_luma_samples = this.read_bits_ue(spsData);

        const conformance_window_flag = this.read_bits(spsData, 1);
        if (conformance_window_flag) {
            this.read_bits_ue(spsData);  // conf_win_left_offset
            this.read_bits_ue(spsData);  // conf_win_right_offset
            this.read_bits_ue(spsData);  // conf_win_top_offset
            this.read_bits_ue(spsData);  // conf_win_bottom_offset
        }

        this.hevcspsInfo.bit_depth_luma_minus8 = this.read_bits_ue(spsData);
        this.hevcspsInfo.bit_depth_chroma_minus8 = this.read_bits_ue(spsData);
        this.read_bits_ue(spsData);  // log2_max_pic_order_cnt_lsb_minus4

        const sps_sub_layer_ordering_info_present_flag = this.read_bits(spsData, 1);
        this.hevcspsInfo.sps_max_dec_pic_buffering_minus1 = [];

        const startIndex = sps_sub_layer_ordering_info_present_flag ? 0 : this.hevcspsInfo.sps_max_sub_layers;
        for (let i = startIndex; i <= this.hevcspsInfo.sps_max_sub_layers; i++) {
            this.hevcspsInfo.sps_max_dec_pic_buffering_minus1[i] = this.read_bits_ue(spsData);
            this.read_bits_ue(spsData);  // sps_max_num_reorder_pics[i]
            this.read_bits_ue(spsData);  // sps_max_latency_increase_plus1[i]
        }

        return this.hevcspsInfo;
    }
    createHEVCDecoderConfigurationRecord(vps, sps, pps) {
        // 构造createHEVCDecoderConfigurationRecord:vps/sps/pps不需要去除防竞争字节。
        // 基础结构长度: 固定头部23字节 + 各NALU数组描述
        let totalSize = 23;

        // 计算NALU数组部分长度: 每个数组有1字节数量 + 每个NALU有2字节长度 + 实际数据长度
        const naluArrays = [sps, pps, vps];
        naluArrays.forEach(nalu => {
            if (nalu && nalu.length > 0) {
                totalSize += 1 + 2 + (2 + nalu.length); //类型(1)+ 数量(2) + [长度(2) + 数据]
            }
        });

        const buffer = new ArrayBuffer(totalSize);
        const view = new DataView(buffer);
        let offset = 0;

        // 1. configurationVersion (1字节)
        view.setUint8(offset++, 0x01);

        // 2. general_profile_space (2) + general_tier_flag (1) + general_profile_idc (5)
        const profileTierLevelByte =
            ((this.hevcspsInfo.general_profile_space & 0x03) << 6) |
            ((this.hevcspsInfo.general_tier_flag & 0x01) << 5) |
            (this.hevcspsInfo.general_profile_idc & 0x1F);
        view.setUint8(offset++, profileTierLevelByte);

        // 3. general_profile_compatibility_flags (4字节)
        view.setUint32(offset, this.hevcspsInfo.general_profile_compatibility_flags, false);
        offset += 4;

        // 4. general_constraint_indicator_flags (6字节)
        for (let i = 0; i < 6; i++) {
            view.setUint8(offset++, this.hevcspsInfo.general_constraint_indicator_flags[i]);
        }

        // 5. general_level_idc (1字节)
        // 从SPS中提取level值,这里使用Level 4.1示例
        view.setUint8(offset++, this.hevcspsInfo.general_level_idc); // 93 = Level 4.1

        // 6. min_spatial_segmentation_idc (2字节)
        // 按ISO/IEC 14496-10规范: 0表示未指定
        view.setUint16(offset, 0xF000, false); // 0xF000 = 未指定
        offset += 2;

        // 7. parallelismType (2位) + 保留位(6位)
        view.setUint8(offset++, 0xFC); // 0xFC = 混合并行模式

        // 8. chromaFormat (2位) + 保留位(6位)
        let chromaFormat = 0xF8; // 默认4:2:0
        if (this.hevcspsInfo.chroma_format_idc === 1) chromaFormat = 0xF8; // 4:2:0
        else if (this.hevcspsInfo.chroma_format_idc === 2) chromaFormat = 0xFA; // 4:2:2
        else if (this.hevcspsInfo.chroma_format_idc === 3) chromaFormat = 0xFC; // 4:4:4
        view.setUint8(offset++, chromaFormat);

        // 9. 亮度位深度 (1字节)
        const lumaBitDepthByte =
            (0x1F << 3) | // 保留位: 11111 (5位)
            (this.hevcspsInfo.bit_depth_luma_minus8 & 0x07); // bit_depth_luma_minus8 (3位)
        view.setUint8(offset++, lumaBitDepthByte);

        // 10. 色度位深度 (1字节)
        const chromaBitDepthByte =
            (0x1F << 3) | // 保留位: 11111 (5位)
            (this.hevcspsInfo.bit_depth_chroma_minus8 & 0x07); // bit_depth_chroma_minus8 (3位)
        view.setUint8(offset++, chromaBitDepthByte);

        // 11. avgFrameRate (2字节)
        view.setUint16(offset, 0, false); // 0 = 可变帧率
        offset += 2;

        // 12. 帧率相关标志 (1字节)
        const frameRateFlags =
            (0x00 << 6) | // constantFrameRate: 0 (可变帧率)
            (0x00 << 3) | // numTemporalLayers: 0 
            (0x01 << 2) | // temporalIdNested: 1
            (0x03 << 0);  // lengthSizeMinusOne: 3 (NAL长度字段为4字节)
        view.setUint8(offset++, frameRateFlags);

        // 13. numOfArrays (1字节) - NALU数组数量,固定为3(VPS/SPS/PPS)
        view.setUint8(offset++, 0x03);

        // 14. NALU数组: 依次写入SPS、PPS、
        const writeNALUArray = (naluArray, arrayType) => {
            if (!naluArray || naluArray.length === 0) {
                view.setUint8(offset++, 0); // 数量为0
                return;
            }
            view.setUint8(offset++, arrayType); // 数组类型和标志
            view.setUint16(offset, 1); // 数量为1(假设每个数组只有一个NALU)
            offset += 2;
            view.setUint16(offset, naluArray.length, false); // NALU长度
            offset += 2;

            // 写入NALU数据
            new Uint8Array(buffer, offset).set(naluArray);
            offset += naluArray.length;
        };

        writeNALUArray(vps, 0x20); // VPS数组
        writeNALUArray(sps, 0x21); // SPS数组
        writeNALUArray(pps, 0x22); // PPS数组

        return new Uint8Array(buffer);
    }
}

class AVCParser {
    constructor() {
        this.avcspsInfo = {};
    }
    avc_analysis_sps(sps) {
        // SPS 结构:profile_idc | constraints | level_idc
        this.avcspsInfo.profile_idc = sps[1];  // 第1个字节:profile
        this.avcspsInfo.constraints = sps[2];  // 第2个字节:constraints
        this.avcspsInfo.level_idc = sps[3];    // 第3个字节:level

        // 构建 codec 字符串:avc1.PP.CC.LL
        return this.avcspsInfo;
    }
    createAVCDecoderConfigurationRecord(sps, pps) {
        // AVCC 盒子结构
        const config = [
            0x01,        // configurationVersion
            sps[1],      // AVCProfileIndication
            sps[2],      // profile_compatibility
            sps[3],      // AVCLevelIndication
            0xFF,        // lengthSizeMinusOne (0xFF = 4 bytes)
            0xE1         // numOfSequenceParameterSets (0xE0 | 0x01)
        ];

        // 添加 SPS 长度和内容 (大端序)
        const spsLength = new Uint8Array(new Uint16Array([sps.length]).buffer);
        config.push(...spsLength.reverse());  // SPS 长度 (2 bytes)
        config.push(...sps);                  // SPS 内容

        // 添加 PPS 数量
        config.push(0x01);  // numOfPictureParameterSets

        // 添加 PPS 长度和内容 (大端序)
        const ppsLength = new Uint8Array(new Uint16Array([pps.length]).buffer);
        config.push(...ppsLength.reverse());  // PPS 长度 (2 bytes)
        config.push(...pps);                  // PPS 内容

        return new Uint8Array(config);
    }
}
export { HEVCParser, AVCParser };

2、从buffer中提取出SPS/PPS/VPS/I帧等数据

复制代码
parseInitializationData(codecType, data) {
    if (typeof data === 'string') {
        // 假设是 base64 编码的 hvcC 盒子数据
        data = this.base64ToUint8Array(data);
    }
    this.offset = 0;
    // 解析 NAL 单元
    while (this.offset < data.length - 4) {
        const nalUnit = this.parseNALUnit(codecType, data);
        if (!nalUnit) break;
        if (VIDEO_AVC265 == codecType) {
            switch (nalUnit.type) {
                case 1://P帧
                case 19://I帧
                case 20:
                case 21:
                    this.rawData = nalUnit.data;
                    return;
                case 32: //VPS
                    this.vpsdata = nalUnit.data;
                    break;
                case 33: //SPS
                    this.spsdata = nalUnit.data;
                    break;
                case 34: //PPS
                    this.ppsdata = nalUnit.data;
                    break;
                case 39: //SEI
                default:
                    // console.log("265 unuse nalUnit.type:" + nalUnit.type);
                    break;
            }
        } else if (VIDEO_AVC264 == codecType) {
            switch (nalUnit.type) {
                case 1://P帧
                case 5://I帧
                    this.rawData = nalUnit.data;
                    return;
                case 7: //SPS
                    this.spsdata = nalUnit.data;
                    break;
                case 8: //PPS
                    this.ppsdata = nalUnit.data;
                    break;
                case 6: //SEI
                case 9://AUD
                default:
                    console.log("264 unuse nalUnit.type:" + nalUnit.type);
                    break;
            }
        }
    }
    return 1;
}
parseNALUnit(codecType, data) {
    // 查找起始码 (0x000001 或 0x00000001)
    let offsetstart = 0;
    let offsetend = data.length;
    let nalUnitType = 0;
    while (this.offset < data.length - 4) {
        if ((data[this.offset] === 0x00 && data[this.offset + 1] === 0x00 && data[this.offset + 2] === 0x01) || (data[this.offset] === 0x00 && data[this.offset + 1] === 0x00 && data[this.offset + 2] === 0x00 && data[this.offset + 3] === 0x01)) {
            if (offsetstart == 0) {
                offsetstart = (data[this.offset + 2] === 0x01) ? 3 : 4 + this.offset;
                if (VIDEO_AVC265 == codecType) {
                    nalUnitType = (data[offsetstart] >> 1) & 0x3F;
                    if (nalUnitType == 19 || nalUnitType == 20 || nalUnitType == 21 || nalUnitType == 1) {
                        break;
                    }
                } else if (VIDEO_AVC264 == codecType) {
                    nalUnitType = data[offsetstart] & 0x1F;
                    if (nalUnitType == 5 || nalUnitType == 1) {
                        break;
                    }
                }
                this.offset = offsetstart;
            } else {
                offsetend = this.offset;
                break;
            }
        }
        this.offset++;
    }
    if (offsetstart >= data.length) return null;
    const nalData = data.slice(offsetstart, offsetend);
    offsetstart = 0;
    offsetend = 0;
    return { type: nalUnitType, data: nalData };
}
createConfigFromSPSPPS(codecType, sps, pps, vps, width, height) {
    let config = null;
    if (VIDEO_AVC265 == codecType) {
        // 1. 创建解析对象
        this.h265Parser = new HEVCParser();
        // 2. 生成 codec 字符串
        let hevcspsInfo = this.h265Parser.hevc_analysis_sps(sps);
        const tierChar = hevcspsInfo.general_tier_flag ? 'H' : 'L';
        const codecString = `hvc1.${hevcspsInfo.general_profile_idc}.1.${tierChar}${hevcspsInfo.general_level_idc}.B0`;
        //3. 创建 description(HEVCDecoderConfigurationRecord)
        const hevcdescription = this.h265Parser.createHEVCDecoderConfigurationRecord(vps, sps, pps);
        this.h265Parser = null;
        config = {
            codec: codecString,
            codedWidth: width,
            codedHeight: height,
            description: hevcdescription,
            hardwareAcceleration: "no-preference",
        };
        console.log("265 hard decoder, codecString:" + codecString);
    } else if (VIDEO_AVC264 == codecType) {
        // 1. 创建解析对象
        this.h264Parser = new AVCParser();
        // 2. 生成 codec 字符串
        let avcspsInfo = this.h264Parser.avc_analysis_sps(sps);
        const codecString = `avc1.${avcspsInfo.profile_idc.toString(16).padStart(2, '0')}${avcspsInfo.constraints.toString(16).padStart(2, '0')}${avcspsInfo.level_idc.toString(16).padStart(2, '0')}`;
        //3. 创建 description(AVCDecoderConfigurationRecord)
        const avcdescription = this.h264Parser.createAVCDecoderConfigurationRecord(sps, pps);
        this.h264Parser = null;
        config = {
            codec: codecString,//codecString,
            codedWidth: width,
            codedHeight: height,
            description: avcdescription,
            hardwareAcceleration: "no-preference",
        };
        console.log("264 hard decoder, codecString:" + codecString);
    }
    if ((VIDEO_AVC265 != codecType) && (this.needSetPreSoft)) {//this.frameRate < 15 || 
        config.hardwareAcceleration = "prefer-software";
    }
    return config;
}
相关推荐
Nuanyt20 小时前
Linux系统的 Shell 常见指令和常见知识点(以bash为主)
linux·运维·chrome·bash
lpfasd1232 天前
MediaCrawler 项目深度分析
chrome·python·chrome devtools
水瓶夜之3 天前
.NET 中的新增功能系列文章——.NET SDK中的新增功能
前端·chrome·.net
代码对我眨眼睛3 天前
macOS Google Chrome 常用快捷键
前端·chrome·macos
小猪咪piggy4 天前
【AI 赋能测试】Langchain 框架的使用
人工智能·chrome·langchain
IT小白杨4 天前
从Chromium内核到云手机:2026数据采集环境隔离方案全解析
大数据·chrome·业界资讯·指纹浏览器
m0_726365835 天前
# Chrome纸牌游戏预测工具开发实战:Canvas图表识别、OCR与实时状态监控
chrome·python·游戏·贪心算法·ocr·线性回归·哈希算法
wardenlzr6 天前
VSCode 运行 Live Server 提示「Windows 找不到文件 Chrome」问题全解析
chrome·windows·vscode
fthux6 天前
GitZip Pro:给GitHub仓库“瘦身”的魔法剪刀手
人工智能·chrome·ai·语言模型·开源·github·open source
小溪彼岸8 天前
浏览器提效插件Ajax Interceptor Tools
chrome