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;
}
相关推荐
莫生灬灬2 小时前
VueMultiBrowser 5.0 开源 - 基于 Vue3 + CEF 的多浏览器管理器
chrome·开源·c#·自动化·多开·cef3
我送炭你添花7 小时前
pytest 入门指南:从零开始掌握 Python 测试框架的核心概念与使用方法
chrome·python·pytest
RichardLau_Cx9 小时前
Google Chrome 浏览器安装「豆包插件」完整教程
前端·chrome·插件·豆包
小码吃趴菜1 天前
Shell脚本编程
前端·chrome
jarreyer1 天前
【AI编程】claudecode插件配置记录和trae软件相关配置
前端·chrome·ai编程
木斯佳1 天前
周末杂谈:Chrome CSS 2025-声明式Web的革命之年
前端·css·chrome
烟锁池塘柳02 天前
【已解决】Google Chrome 浏览器报错 STATUS_ACCESS_VIOLATION 的解决方案
前端·chrome
Trouvaille ~2 天前
【Linux】进程间通信(二):命名管道与进程池架构实战
linux·c++·chrome·架构·进程间通信·命名管道·进程池
John_ToDebug2 天前
Chrome Safe Browsing:浏览器安全背后的全局防护机制解析
chrome·安全