Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow generate segments with Mp4Generator #5185

Merged
merged 3 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions lib/mss/mss_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,15 +618,20 @@ shaka.mss.MssParser = class {
if (this.initSegmentDataByStreamId_.has(stream.id)) {
initSegmentData = this.initSegmentDataByStreamId_.get(stream.id);
} else {
const timescale = stream.mssPrivateData.timescale;
const duration = stream.mssPrivateData.duration;
let videoNalus = null;
let videoNalus = [];
if (stream.type == ContentType.VIDEO) {
const codecPrivateData = stream.mssPrivateData.codecPrivateData;
videoNalus = codecPrivateData.split('00000001').slice(1);
}
const mp4Generator = new shaka.util.Mp4Generator(
stream, timescale, duration, videoNalus);
/** @type {shaka.util.Mp4Generator.StreamInfo} */
const streamInfo = {
timescale: stream.mssPrivateData.timescale,
duration: stream.mssPrivateData.duration,
videoNalus: videoNalus,
data: null, // Data is not necessary for init segement.
stream: stream,
};
const mp4Generator = new shaka.util.Mp4Generator(streamInfo);
initSegmentData = mp4Generator.initSegment();
this.initSegmentDataByStreamId_.set(stream.id, initSegmentData);
}
Expand Down
287 changes: 273 additions & 14 deletions lib/util/mp4_generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,43 @@ goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.Uint8ArrayUtils');


/**
* @export
*/
shaka.util.Mp4Generator = class {
/**
* @param {shaka.extern.Stream} stream
* @param {number} timescale
* @param {?number} duration
* @param {?Array.<string>} videoNalus
* @param {shaka.util.Mp4Generator.StreamInfo} streamInfo
*/
constructor(stream, timescale, duration, videoNalus) {
constructor(streamInfo) {
shaka.util.Mp4Generator.initStaticProperties_();

/** @private {shaka.extern.Stream} */
this.stream_ = stream;
/** @private {!shaka.extern.Stream} */
this.stream_ = streamInfo.stream;
avelad marked this conversation as resolved.
Show resolved Hide resolved

/** @private {number} */
this.timescale_ = timescale;
this.timescale_ = streamInfo.timescale;

/** @private {number} */
this.duration_ = duration || 0xffffffff;
this.duration_ = streamInfo.duration;
if (this.duration_ === Infinity) {
this.duration_ = 0xffffffff;
}

/** @private {Array.<string>} */
this.videoNalus_ = videoNalus || [];
/** @private {!Array.<string>} */
this.videoNalus_ = streamInfo.videoNalus;

/** @private {number} */
this.sequenceNumber_ = 0;

/** @private {number} */
this.baseMediaDecodeTime_ = 0;

/** @private {!Array.<shaka.util.Mp4Generator.Mp4Sample>} */
this.samples_ = [];

const data = streamInfo.data;
if (data) {
this.sequenceNumber_ = data.sequenceNumber;
this.baseMediaDecodeTime_ = data.baseMediaDecodeTime;
this.samples_ = data.samples;
}
}

/**
Expand Down Expand Up @@ -698,6 +708,179 @@ shaka.util.Mp4Generator = class {
return Mp4Generator.box('tenc', bytes, defaultKeyId);
}

/**
* Generate a Segment Data (MP4).
*
* @return {!Uint8Array}
*/
segmentData() {
const Mp4Generator = shaka.util.Mp4Generator;
const movie = this.moof_();
const length = Mp4Generator.FTYP_.byteLength + movie.byteLength;
const result = new Uint8Array(length);
result.set(Mp4Generator.FTYP_);
result.set(movie, Mp4Generator.FTYP_.byteLength);
return result;
}

/**
* Generate a MOOF box
*
* @return {!Uint8Array}
* @private
*/
moof_() {
const Mp4Generator = shaka.util.Mp4Generator;
return Mp4Generator.box('moof', this.mfhd_(), this.traf_());
}

/**
* Generate a MOOF box
*
* @return {!Uint8Array}
* @private
*/
mfhd_() {
const Mp4Generator = shaka.util.Mp4Generator;
const bytes = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
...this.breakNumberIntoBytes_(this.sequenceNumber_, 4),
]);
return Mp4Generator.box('mfhd', bytes);
}

/**
* Generate a TRAF box
*
* @return {!Uint8Array}
* @private
*/
traf_() {
const Mp4Generator = shaka.util.Mp4Generator;
const sampleDependencyTable = this.sdtp_();
const offset = sampleDependencyTable.length +
32 + // tfhd
20 + // tfdt
8 + // traf header
16 + // mfhd
8 + // moof header
8; // mdat header;
return Mp4Generator.box('traf', this.tfhd_(), this.tfdt_(),
this.trun_(offset), sampleDependencyTable);
}

/**
* Generate a SDTP box
*
* @return {!Uint8Array}
* @private
*/
sdtp_() {
const Mp4Generator = shaka.util.Mp4Generator;
const bytes = new Uint8Array(4 + this.samples_.length);
// leave the full box header (4 bytes) all zero
// write the sample table
for (let i = 0; i < this.samples_.length; i++) {
const flags = this.samples_[i].flags;
bytes[i + 4] = (flags.dependsOn << 4) |
(flags.isDependedOn << 2) |
flags.hasRedundancy;
}
return Mp4Generator.box('sdtp', bytes);
}

/**
* Generate a TFHD box
*
* @return {!Uint8Array}
* @private
*/
tfhd_() {
const Mp4Generator = shaka.util.Mp4Generator;
const id = this.stream_.id + 1;
const bytes = new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x3a, // flags
...this.breakNumberIntoBytes_(id, 4), // track_ID
0x00, 0x00, 0x00, 0x01, // sample_description_index
0x00, 0x00, 0x00, 0x00, // default_sample_duration
0x00, 0x00, 0x00, 0x00, // default_sample_size
0x00, 0x00, 0x00, 0x00, // default_sample_flags
]);
return Mp4Generator.box('tfhd', bytes);
}

/**
* Generate a TFDT box
*
* @return {!Uint8Array}
* @private
*/
tfdt_() {
const Mp4Generator = shaka.util.Mp4Generator;
const upperWordBaseMediaDecodeTime =
Math.floor(this.baseMediaDecodeTime_ / (Mp4Generator.UINT32_MAX_ + 1));
const lowerWordBaseMediaDecodeTime =
Math.floor(this.baseMediaDecodeTime_ % (Mp4Generator.UINT32_MAX_ + 1));
const bytes = new Uint8Array([
0x01, // version 1
0x00, 0x00, 0x00, // flags
...this.breakNumberIntoBytes_(upperWordBaseMediaDecodeTime, 4),
...this.breakNumberIntoBytes_(lowerWordBaseMediaDecodeTime, 4),
]);
return Mp4Generator.box('mfhd', bytes);
}

/**
* Generate a TRUN box
*
* @return {!Uint8Array}
* @private
*/
trun_(offset) {
const ContentType = shaka.util.ManifestParserUtils.ContentType;
const Mp4Generator = shaka.util.Mp4Generator;

const samplesLength = this.samples_.length;
const byteslen = 12 + 16 * samplesLength;
const bytes = new Uint8Array(byteslen);
offset += 8 + byteslen;
const isVideo = this.stream_.type === ContentType.VIDEO;
bytes.set(
[
// version 1 for video with signed-int sample_composition_time_offset
isVideo ? 0x01 : 0x00,
0x00, 0x0f, 0x01, // flags
...this.breakNumberIntoBytes_(samplesLength, 4), // sample_count
...this.breakNumberIntoBytes_(offset, 4), // data_offset
],
0,
);
for (let i = 0; i < samplesLength; i++) {
const sample = this.samples_[i];
const duration = this.breakNumberIntoBytes_(sample.duration, 4);
const size = this.breakNumberIntoBytes_(sample.size, 4);
const flags = sample.flags;
const cts = this.breakNumberIntoBytes_(sample.cts, 4);
bytes.set(
[
...duration, // sample_duration
...size, // sample_size
(flags.isLeading << 2) | flags.dependsOn,
(flags.isDependedOn << 6) | (flags.hasRedundancy << 4) |
flags.isNonSync,
flags.degradPrio & (0xf0 << 8),
flags.degradPrio & 0x0f, // sample_flags
...cts, // sample_composition_time_offset
],
12 + 16 * i,
);
}
return Mp4Generator.box('trun', bytes);
}


/**
* @param {number} number
* @param {number} numBytes
Expand Down Expand Up @@ -940,3 +1123,79 @@ shaka.util.Mp4Generator.DREF_ = new Uint8Array([
* @private {!Uint8Array}
*/
shaka.util.Mp4Generator.DINF_ = new Uint8Array([]);

/**
* @typedef {{
* timescale: number,
* duration: number,
* videoNalus: !Array.<string>,
* data: ?shaka.util.Mp4Generator.Data,
* stream: !shaka.extern.Stream
* }}
*
* @property {number} timescale
* The Stream's timescale.
* @property {number} duration
* The Stream's duration.
* @property {!Array.<string>} videoNalus
* The stream's video nalus.
* @property {?shaka.util.Mp4Generator.Data} data
* The stream's data.
* @property {!shaka.extern.Stream} stream
* The Stream.
*/
shaka.util.Mp4Generator.StreamInfo;

/**
* @typedef {{
* sequenceNumber: number,
* baseMediaDecodeTime: number,
* samples: !Array.<shaka.util.Mp4Generator.Mp4Sample>
* }}
*
* @property {number} sequenceNumber
* The sequence number.
* @property {number} baseMediaDecodeTime
* The base media decode time.
* @property {!Array.<shaka.util.Mp4Generator.Mp4Sample>} samples
* The data samples.
*/
shaka.util.Mp4Generator.Data;

/**
* @typedef {{
* size: number,
* duration: number,
* cts: number,
* flags: !shaka.util.Mp4Generator.Mp4SampleFlags
* }}
*
* @property {number} size
* The sample size.
* @property {number} duration
* The sample duration.
* @property {number} cts
* The sample composition time.
* @property {!shaka.util.Mp4Generator.Mp4SampleFlags} flags
* The sample flags.
*/
shaka.util.Mp4Generator.Mp4Sample;

/**
* @typedef {{
* isLeading: number,
* isDependedOn: number,
* hasRedundancy: number,
* degradPrio: number,
* dependsOn: number,
* isNonSync: number
* }}
*
* @property {number} isLeading
* @property {number} isDependedOn
* @property {number} hasRedundancy
* @property {number} degradPrio
* @property {number} dependsOn
* @property {number} isNonSync
*/
shaka.util.Mp4Generator.Mp4SampleFlags;