Skip to content

Commit

Permalink
feat: Allow generate segments with Mp4Generator (shaka-project#5185)
Browse files Browse the repository at this point in the history
This is part of the work to have our own transmuxers
  • Loading branch information
avelad committed Apr 28, 2023
1 parent 05aa931 commit 8da971f
Show file tree
Hide file tree
Showing 2 changed files with 283 additions and 19 deletions.
15 changes: 10 additions & 5 deletions lib/mss/mss_parser.js
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
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;

/** @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;

0 comments on commit 8da971f

Please sign in to comment.