Skip to content

Commit ce2837c

Browse files
authored
feat: add audioSegmentTimingInfo event to match videoSegmentTimingInfo (#364)
1 parent ce827f7 commit ce2837c

File tree

3 files changed

+92
-13
lines changed

3 files changed

+92
-13
lines changed

lib/mp4/audio-frame-utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ var prefixWithSilence = function(
8787

8888
track.baseMediaDecodeTime -=
8989
Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
90+
91+
return audioFillDuration;
9092
};
9193

9294
// If the audio segment extends before the earliest allowed dts

lib/mp4/transmuxer.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ var arrayEquals = function(a, b) {
4949
return true;
5050
};
5151

52-
var generateVideoSegmentTimingInfo = function(
52+
var generateSegmentTimingInfo = function(
5353
baseMediaDecodeTime,
5454
startDts,
5555
startPts,
@@ -132,7 +132,9 @@ AudioSegmentStream = function(track, options) {
132132
moof,
133133
mdat,
134134
boxes,
135-
frameDuration;
135+
frameDuration,
136+
segmentDuration,
137+
videoClockCyclesOfSilencePrefixed;
136138

137139
// return early if no audio data has been observed
138140
if (adtsFrames.length === 0) {
@@ -145,7 +147,8 @@ AudioSegmentStream = function(track, options) {
145147
track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
146148
track, options.keepOriginalTimestamps);
147149

148-
audioFrameUtils.prefixWithSilence(
150+
// amount of audio filled but the value is in video clock rather than audio clock
151+
videoClockCyclesOfSilencePrefixed = audioFrameUtils.prefixWithSilence(
149152
track, frames, audioAppendStartTs, videoBaseMediaDecodeTime);
150153

151154
// we have to build the index from byte locations to
@@ -175,9 +178,27 @@ AudioSegmentStream = function(track, options) {
175178
// valid use-case where an init segment/data should be triggered without associated
176179
// frames. Leaving for now, but should be looked into.
177180
if (frames.length) {
181+
segmentDuration = frames.length * frameDuration;
182+
183+
this.trigger(
184+
'segmentTimingInfo',
185+
generateSegmentTimingInfo(
186+
// The audio track's baseMediaDecodeTime is in audio clock cycles, but the
187+
// frame info is in video clock cycles. Convert to match expectation of
188+
// listeners (that all timestamps will be based on video clock cycles).
189+
clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate),
190+
// frame times are already in video clock, as is segment duration
191+
frames[0].dts,
192+
frames[0].pts,
193+
frames[0].dts + segmentDuration,
194+
frames[0].pts + segmentDuration,
195+
videoClockCyclesOfSilencePrefixed || 0
196+
)
197+
);
198+
178199
this.trigger('timingInfo', {
179200
start: frames[0].pts,
180-
end: frames[0].pts + (frames.length * frameDuration)
201+
end: frames[0].pts + segmentDuration
181202
});
182203
}
183204
this.trigger('data', {track: track, boxes: boxes});
@@ -392,7 +413,7 @@ VideoSegmentStream = function(track, options) {
392413

393414
this.trigger(
394415
'segmentTimingInfo',
395-
generateVideoSegmentTimingInfo(
416+
generateSegmentTimingInfo(
396417
track.baseMediaDecodeTime,
397418
firstGop.dts,
398419
firstGop.pts,
@@ -1051,6 +1072,8 @@ Transmuxer = function(options) {
10511072

10521073
pipeline.audioSegmentStream.on('timingInfo',
10531074
self.trigger.bind(self, 'audioTimingInfo'));
1075+
pipeline.audioSegmentStream.on('segmentTimingInfo',
1076+
self.trigger.bind(self, 'audioSegmentTimingInfo'));
10541077

10551078
// Set up the final part of the audio pipeline
10561079
pipeline.adtsStream
@@ -1180,5 +1203,5 @@ module.exports = {
11801203
AUDIO_PROPERTIES: AUDIO_PROPERTIES,
11811204
VIDEO_PROPERTIES: VIDEO_PROPERTIES,
11821205
// exported for testing
1183-
generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
1206+
generateSegmentTimingInfo: generateSegmentTimingInfo
11841207
};

test/transmuxer.test.js

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var mp2t = require('../lib/m2ts'),
1111
mp4Transmuxer = require('../lib/mp4/transmuxer'),
1212
mp4AudioProperties = mp4Transmuxer.AUDIO_PROPERTIES,
1313
mp4VideoProperties = mp4Transmuxer.VIDEO_PROPERTIES,
14-
generateVideoSegmentTimingInfo = mp4Transmuxer.generateVideoSegmentTimingInfo,
14+
generateSegmentTimingInfo = mp4Transmuxer.generateSegmentTimingInfo,
1515
clock = require('../lib/utils/clock'),
1616
utils = require('./utils'),
1717
TransportPacketStream = mp2t.TransportPacketStream,
@@ -2532,7 +2532,7 @@ QUnit.test('alignGopsAtEnd_ filters gops appropriately', function(assert) {
25322532
'match with an alignment candidate');
25332533
});
25342534

2535-
QUnit.test('generateVideoSegmentTimingInfo generates correct timing info object', function(assert) {
2535+
QUnit.test('generateSegmentTimingInfo generates correct timing info object', function(assert) {
25362536
var
25372537
firstFrame = {
25382538
dts: 12,
@@ -2548,7 +2548,7 @@ QUnit.test('generateVideoSegmentTimingInfo generates correct timing info object'
25482548
prependedContentDuration = 0;
25492549

25502550
assert.deepEqual(
2551-
generateVideoSegmentTimingInfo(
2551+
generateSegmentTimingInfo(
25522552
baseMediaDecodeTime,
25532553
firstFrame.dts,
25542554
firstFrame.pts,
@@ -2573,7 +2573,7 @@ QUnit.test('generateVideoSegmentTimingInfo generates correct timing info object'
25732573
}, 'generated correct timing info object');
25742574
});
25752575

2576-
QUnit.test('generateVideoSegmentTimingInfo accounts for prepended GOPs', function(assert) {
2576+
QUnit.test('generateSegmentTimingInfo accounts for prepended GOPs', function(assert) {
25772577
var
25782578
firstFrame = {
25792579
dts: 12,
@@ -2589,7 +2589,7 @@ QUnit.test('generateVideoSegmentTimingInfo accounts for prepended GOPs', functio
25892589
prependedContentDuration = 7;
25902590

25912591
assert.deepEqual(
2592-
generateVideoSegmentTimingInfo(
2592+
generateSegmentTimingInfo(
25932593
baseMediaDecodeTime,
25942594
firstFrame.dts,
25952595
firstFrame.pts,
@@ -2615,7 +2615,7 @@ QUnit.test('generateVideoSegmentTimingInfo accounts for prepended GOPs', functio
26152615
'included prepended content duration in timing info');
26162616
});
26172617

2618-
QUnit.test('generateVideoSegmentTimingInfo handles GOPS where pts is < dts', function(assert) {
2618+
QUnit.test('generateSegmentTimingInfo handles GOPS where pts is < dts', function(assert) {
26192619
var
26202620
firstFrame = {
26212621
dts: 14,
@@ -2631,7 +2631,7 @@ QUnit.test('generateVideoSegmentTimingInfo handles GOPS where pts is < dts', fun
26312631
prependedContentDuration = 7;
26322632

26332633
assert.deepEqual(
2634-
generateVideoSegmentTimingInfo(
2634+
generateSegmentTimingInfo(
26352635
baseMediaDecodeTime,
26362636
firstFrame.dts,
26372637
firstFrame.pts,
@@ -3221,6 +3221,60 @@ QUnit.test('audio track metadata takes on the value of the last metadata seen',
32213221
assert.equal(events[0].track.channelcount, 4, 'kept the later channelcount');
32223222
});
32233223

3224+
QUnit.test('audio segment stream triggers segmentTimingInfo with timing info',
3225+
function(assert) {
3226+
var
3227+
events = [],
3228+
samplerate = 48000,
3229+
baseMediaDecodeTimeInVideoClock = 30,
3230+
audioFrameDurationInVideoClock = 90000 * 1024 / samplerate,
3231+
firstFrame = {
3232+
channelcount: 2,
3233+
samplerate: samplerate,
3234+
pts: 112,
3235+
dts: 111,
3236+
data: new Uint8Array([0])
3237+
},
3238+
secondFrame = {
3239+
channelcount: 2,
3240+
samplerate: samplerate,
3241+
pts: firstFrame.pts + audioFrameDurationInVideoClock,
3242+
dts: firstFrame.dts + audioFrameDurationInVideoClock,
3243+
data: new Uint8Array([1])
3244+
};
3245+
3246+
audioSegmentStream.on('segmentTimingInfo', function(event) {
3247+
events.push(event);
3248+
});
3249+
audioSegmentStream.track.timelineStartInfo.baseMediaDecodeTime =
3250+
baseMediaDecodeTimeInVideoClock;
3251+
3252+
audioSegmentStream.push(firstFrame);
3253+
audioSegmentStream.push(secondFrame);
3254+
audioSegmentStream.flush();
3255+
3256+
assert.equal(events.length, 1, 'a segmentTimingInfo event was fired');
3257+
assert.deepEqual(
3258+
events[0],
3259+
{
3260+
start: {
3261+
dts: baseMediaDecodeTimeInVideoClock,
3262+
pts: baseMediaDecodeTimeInVideoClock + (firstFrame.pts - firstFrame.dts)
3263+
},
3264+
end: {
3265+
dts: baseMediaDecodeTimeInVideoClock + (secondFrame.dts - firstFrame.dts) +
3266+
audioFrameDurationInVideoClock,
3267+
pts: baseMediaDecodeTimeInVideoClock + (secondFrame.pts - firstFrame.pts) +
3268+
audioFrameDurationInVideoClock
3269+
},
3270+
prependedContentDuration: 0,
3271+
baseMediaDecodeTime: baseMediaDecodeTimeInVideoClock
3272+
},
3273+
'has correct segmentTimingInfo'
3274+
);
3275+
});
3276+
3277+
32243278
QUnit.module('Transmuxer - options');
32253279

32263280
QUnit.test('no options creates combined output', function(assert) {

0 commit comments

Comments
 (0)