jessibuca
Version:
a h5 live stream player
168 lines (138 loc) • 6.24 kB
text/typescript
import { MP4Meta } from './fmp4';
import SPSParser from './h264-sps-parser';
//
export function parseAVCDecoderConfigurationRecord(arrayBuffer: Uint8Array) {
const meta: MP4Meta = {}
const v = new DataView(arrayBuffer.buffer);
let version = v.getUint8(0); // configurationVersion
let avcProfile = v.getUint8(1); // avcProfileIndication
let profileCompatibility = v.getUint8(2); // profile_compatibil
let avcLevel = v.getUint8(3); // AVCLevelIndication
if (version !== 1 || avcProfile === 0) {
// this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord');
return meta;
}
const _naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne
if (_naluLengthSize !== 3 && _naluLengthSize !== 4) { // holy shit!!!
// this._onError(DemuxErrors.FORMAT_ERROR, `Flv: Strange NaluLengthSizeMinusOne: ${_naluLengthSize - 1}`);
return meta;
}
let spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets
if (spsCount === 0) {
// this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS');
return;
} else if (spsCount > 1) {
// Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: SPS Count = ${spsCount}`);
}
let offset = 6;
for (let i = 0; i < spsCount; i++) {
let len = v.getUint16(offset, false); // sequenceParameterSetLength
offset += 2;
if (len === 0) {
continue;
}
// Notice: Nalu without startcode header (00 00 00 01)
let sps = new Uint8Array(arrayBuffer.buffer, offset, len);
offset += len;
// flv.js作者选择了自己来解析这个数据结构,也是迫不得已,因为JS环境下没有ffmpeg,解析这个结构主要是为了提取 sps和pps。虽然理论上sps允许有多个,但其实一般就一个。
// packetTtype 为 1 表示 NALU,NALU= network abstract layer unit,这是H.264的概念,网络抽象层数据单元,其实简单理解就是一帧视频数据。
// pps的信息没什么用,所以作者只实现了sps的分析器,说明作者下了很大功夫去学习264的标准,其中的Golomb解码还是挺复杂的,能解对不容易,我在PC和手机平台都是用ffmpeg去解析的。
// SPS里面包括了视频分辨率,帧率,profile level等视频重要信息。
let config = SPSParser.parseSPS(sps);
if (i !== 0) {
// ignore other sps's config
continue;
}
meta.codecWidth = config.codec_size.width;
meta.codecHeight = config.codec_size.height;
meta.presentWidth = config.present_size.width;
meta.presentHeight = config.present_size.height;
meta.profile = config.profile_string;
meta.level = config.level_string;
meta.bitDepth = config.bit_depth;
meta.chromaFormat = config.chroma_format;
meta.sarRatio = config.sar_ratio;
meta.frameRate = config.frame_rate;
if (config.frame_rate.fixed === false ||
config.frame_rate.fps_num === 0 ||
config.frame_rate.fps_den === 0) {
meta.frameRate = {};
}
let fps_den = meta.frameRate.fps_den;
let fps_num = meta.frameRate.fps_num;
meta.refSampleDuration = meta.timescale * (fps_den / fps_num);
let codecArray = sps.subarray(1, 4);
let codecString = 'avc1.';
for (let j = 0; j < 3; j++) {
let h = codecArray[j].toString(16);
if (h.length < 2) {
h = '0' + h;
}
codecString += h;
}
// codec
meta.codec = codecString;
}
let ppsCount = v.getUint8(offset); // numOfPictureParameterSets
if (ppsCount === 0) {
// this._onError(DemuxErrors.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS');
return meta;
} else if (ppsCount > 1) {
// Log.w(this.TAG, `Flv: Strange AVCDecoderConfigurationRecord: PPS Count = ${ppsCount}`);
}
offset++;
for (let i = 0; i < ppsCount; i++) {
let len = v.getUint16(offset, false); // pictureParameterSetLength
offset += 2;
if (len === 0) {
continue;
}
let pps = new Uint8Array(arrayBuffer.buffer, offset, len);
// pps is useless for extracting video information
offset += len;
}
meta.videoType = 'avc';
// meta.avcc = arrayBuffer;
return meta;
}
export function avcEncoderConfigurationRecord({ sps, pps }: { sps: Uint8Array; pps: Uint8Array }) {
// require Nalu without 4 byte length-header
let length = 6 + 2 + sps.byteLength + 1 + 2 + pps.byteLength;
let need_extra_fields = false;
const sps_details = SPSParser.parseSPS(sps);
if (sps[3] !== 66 && sps[3] !== 77 && sps[3] !== 88) {
need_extra_fields = true;
length += 4;
}
let 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
let 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
let 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;
}
const prevData = [0x17, 0x00, 0x00, 0x00, 0x00];
const newData = new Uint8Array(prevData.length + data.byteLength);
newData.set(prevData, 0);
newData.set(data, prevData.length);
return newData;
}