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;
}