Skip to content

Commit

Permalink
feat(HLS): Get the correct video info for TS segments with H.265 (#5616)
Browse files Browse the repository at this point in the history
  • Loading branch information
avelad authored Sep 8, 2023
1 parent 6b17355 commit e191c75
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 6 deletions.
6 changes: 5 additions & 1 deletion lib/hls/hls_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,11 @@ shaka.hls.HlsParser = class {
hasVideo = true;
break;
case 'hvc':
codecs.push('hvc1.1.6.L93.90');
if (videoInfo.codec) {
codecs.push(videoInfo.codec);
} else {
codecs.push('hvc1.1.6.L93.90');
}
hasVideo = true;
break;
}
Expand Down
32 changes: 30 additions & 2 deletions lib/util/exp_golomb.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

goog.provide('shaka.util.ExpGolomb');

goog.require('shaka.util.BufferUtils');
goog.require('shaka.util.DataViewReader');


Expand All @@ -31,13 +32,17 @@ goog.require('shaka.util.DataViewReader');
shaka.util.ExpGolomb = class {
/**
* @param {!Uint8Array} data
* @param {boolean=} convertEbsp2rbsp
*/
constructor(data) {
constructor(data, convertEbsp2rbsp = false) {
/** @private {!Uint8Array} */
this.data_ = data;
if (convertEbsp2rbsp) {
this.data_ = this.ebsp2rbsp_(data);
}

/** @private {number} */
this.workingBytesAvailable_ = data.byteLength;
this.workingBytesAvailable_ = this.data_.byteLength;

// the current word being examined
/** @private {number} */
Expand All @@ -48,6 +53,29 @@ shaka.util.ExpGolomb = class {
this.workingBitsAvailable_ = 0;
}

/**
* @param {!Uint8Array} data
* @return {!Uint8Array}
* @private
*/
ebsp2rbsp_(data) {
const ret = new Uint8Array(data.byteLength);
let retIndex = 0;

for (let i = 0; i < data.byteLength; i++) {
if (i >= 2) {
// Unescape: Skip 0x03 after 00 00
if (data[i] == 0x03 && data[i - 1] == 0x00 && data[i - 2] == 0x00) {
continue;
}
}
ret[retIndex] = data[i];
retIndex++;
}

return shaka.util.BufferUtils.toUint8(ret, 0, retIndex);
}

/**
* Load the next word
*
Expand Down
164 changes: 161 additions & 3 deletions lib/util/ts_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,15 +727,25 @@ shaka.util.TsParser = class {
* @export
*/
getVideoInfo() {
if (this.videoCodec_ == 'hvc') {
return this.getHvcInfo_();
}
return this.getAvcInfo_();
}

/**
* Return the video information for AVC
*
* @return {{height: ?string, width: ?string, codec: ?string}}
* @private
*/
getAvcInfo_() {
const TsParser = shaka.util.TsParser;
const videoInfo = {
height: null,
width: null,
codec: null,
};
if (this.videoCodec_ != 'avc') {
return videoInfo;
}
const videoNalus = this.getVideoNalus();
if (!videoNalus.length) {
return videoInfo;
Expand Down Expand Up @@ -850,6 +860,146 @@ shaka.util.TsParser = class {
return videoInfo;
}

/**
* Return the video information for HVC
*
* @return {{height: ?string, width: ?string, codec: ?string}}
* @private
*/
getHvcInfo_() {
const TsParser = shaka.util.TsParser;
const videoInfo = {
height: null,
width: null,
codec: null,
};
const videoNalus = this.getVideoNalus();
if (!videoNalus.length) {
return videoInfo;
}
const spsNalu = videoNalus.find((nalu) => {
return nalu.type == TsParser.H265_NALU_TYPE_SPS_;
});
if (!spsNalu) {
return videoInfo;
}

const gb = new shaka.util.ExpGolomb(
spsNalu.fullData, /* convertEbsp2rbsp= */ true);

// remove NALu Header
gb.readUnsignedByte();
gb.readUnsignedByte();

// SPS
gb.readBits(4); // video_paramter_set_id
const maxSubLayersMinus1 = gb.readBits(3);
gb.readBoolean(); // temporal_id_nesting_flag

// profile_tier_level begin
const generalProfileSpace = gb.readBits(2);
const generalTierFlag = gb.readBits(1);
const generalProfileIdc = gb.readBits(5);
const generalProfileCompatibilityFlags = gb.readBits(32);
const generalConstraintIndicatorFlags1 = gb.readUnsignedByte();
const generalConstraintIndicatorFlags2 = gb.readUnsignedByte();
const generalConstraintIndicatorFlags3 = gb.readUnsignedByte();
const generalConstraintIndicatorFlags4 = gb.readUnsignedByte();
const generalConstraintIndicatorFlags5 = gb.readUnsignedByte();
const generalConstraintIndicatorFlags6 = gb.readUnsignedByte();
const generalLevelIdc = gb.readUnsignedByte();
const subLayerProfilePresentFlag = [];
const subLayerLevelPresentFlag = [];
for (let i = 0; i < maxSubLayersMinus1; i++) {
subLayerProfilePresentFlag.push(gb.readBoolean());
subLayerLevelPresentFlag.push(gb.readBoolean());
}
if (maxSubLayersMinus1 > 0) {
for (let i = maxSubLayersMinus1; i < 8; i++) {
gb.readBits(2);
}
}
for (let i = 0; i < maxSubLayersMinus1; i++) {
if (subLayerProfilePresentFlag[i]) {
gb.readBits(88);
}
if (subLayerLevelPresentFlag[i]) {
gb.readUnsignedByte();
}
}
// profile_tier_level end

gb.readUnsignedExpGolomb(); // seq_parameter_set_id
const chromaFormatIdc = gb.readUnsignedExpGolomb();
if (chromaFormatIdc == 3) {
gb.readBits(1); // separate_colour_plane_flag
}
const picWidthInLumaSamples = gb.readUnsignedExpGolomb();
const picHeightInLumaSamples = gb.readUnsignedExpGolomb();
let leftOffset = 0;
let rightOffset = 0;
let topOffset = 0;
let bottomOffset = 0;
const conformanceWindowFlag = gb.readBoolean();
if (conformanceWindowFlag) {
leftOffset += gb.readUnsignedExpGolomb();
rightOffset += gb.readUnsignedExpGolomb();
topOffset += gb.readUnsignedExpGolomb();
bottomOffset += gb.readUnsignedExpGolomb();
}

const subWc = chromaFormatIdc === 1 || chromaFormatIdc === 2 ? 2 : 1;
const subHc = chromaFormatIdc === 1 ? 2 : 1;
videoInfo.width =
String(picWidthInLumaSamples - (leftOffset + rightOffset) * subWc);
videoInfo.height =
String(picHeightInLumaSamples - (topOffset + bottomOffset) * subHc);

const reverseBits = (integer) => {
let result = 0;
for (let i = 0; i < 32; i++) {
result |= ((integer >> i) & 1) << (31 - i);
}
return result >>> 0;
};

const profileSpace = ['', 'A', 'B', 'C'][generalProfileSpace];
const profileCompatibility = reverseBits(generalProfileCompatibilityFlags);
const tierFlag = generalTierFlag == 1 ? 'H' : 'L';

let codec = 'hvc1';
codec += '.' + profileSpace + generalProfileIdc;
codec += '.' + profileCompatibility.toString(16).toUpperCase();
codec += '.' + tierFlag + generalLevelIdc;
if (generalConstraintIndicatorFlags6) {
codec += '.' +
generalConstraintIndicatorFlags6.toString(16).toUpperCase();
}
if (generalConstraintIndicatorFlags5) {
codec += '.' +
generalConstraintIndicatorFlags5.toString(16).toUpperCase();
}
if (generalConstraintIndicatorFlags4) {
codec += '.' +
generalConstraintIndicatorFlags4.toString(16).toUpperCase();
}
if (generalConstraintIndicatorFlags3) {
codec += '.' +
generalConstraintIndicatorFlags3.toString(16).toUpperCase();
}
if (generalConstraintIndicatorFlags2) {
codec += '.' +
generalConstraintIndicatorFlags2.toString(16).toUpperCase();
}
if (generalConstraintIndicatorFlags1) {
codec += '.' +
generalConstraintIndicatorFlags1.toString(16).toUpperCase();
}
videoInfo.codec = codec;

return videoInfo;
}

/**
* Convert a byte to 2 digits of hex. (Only handles values 0-255.)
*
Expand Down Expand Up @@ -931,6 +1081,14 @@ shaka.util.TsParser.PacketLength_ = 188;
shaka.util.TsParser.H264_NALU_TYPE_SPS_ = 0x07;


/**
* NALU type for Sequence Parameter Set (SPS) for H.265.
* @const {number}
* @private
*/
shaka.util.TsParser.H265_NALU_TYPE_SPS_ = 0x21;


/**
* Values of profile_idc that indicate additional fields are included in the
* SPS.
Expand Down

0 comments on commit e191c75

Please sign in to comment.