Skip to content

Commit

Permalink
Use different wraparound assumptions for duration readers
Browse files Browse the repository at this point in the history
The timestamp adjuster also estimates the number of wraparounds
of the 90Khz TS timestamp. It does that by assuming that a new
timestamp is always close to the previous one (in either direction).

This logic doesn't always work for duration estimates because the
timestamp at the end of the media is not close to the one at the
beginning and it may also never be less than the one at the beginning.

This can be fixed by introducing a new estimation model that assumes
the new timestamp is strictly greater than the previous one without
making the assumption that it has to be close to it.

Issue: #855

#minor-release

PiperOrigin-RevId: 590936953
(cherry picked from commit 0157878)
  • Loading branch information
tonihei authored and microkatz committed Jan 9, 2024
1 parent 09d30a1 commit 4231a1d
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 12 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
playback because of their higher resolution.
* Fix wrong keyframe detection for TS H264 streams
([#864](https://github.com/androidx/media/pull/864)).
* Fix duration estimation of TS streams that are longer than 47721 seconds
([#855](https://github.com/androidx/media/issues/855)).
* Audio:
* Fix handling of EOS for `SilenceSkippingAudioProcessor` when called
multiple times ([#712](https://github.com/androidx/media/issues/712)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ public synchronized void reset(long firstSampleTimestampUs) {
/**
* Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
*
* <p>When estimating the wraparound, the method assumes that this timestamp is close to the
* previous adjusted timestamp.
*
* @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
* @return The adjusted timestamp in microseconds.
*/
Expand All @@ -209,6 +212,30 @@ public synchronized long adjustTsTimestamp(long pts90Khz) {
return adjustSampleTimestamp(ptsToUs(pts90Khz));
}

/**
* Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
*
* <p>When estimating the wraparound, the method assumes that the timestamp is strictly greater
* than the previous adjusted timestamp.
*
* @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
* @return The adjusted timestamp in microseconds.
*/
public synchronized long adjustTsTimestampGreaterThanPreviousTimestamp(long pts90Khz) {
if (pts90Khz == C.TIME_UNSET) {
return C.TIME_UNSET;
}
if (lastUnadjustedTimestampUs != C.TIME_UNSET) {
// The wrap count for the current PTS must be same or greater than the previous one.
long lastPts = usToNonWrappedPts(lastUnadjustedTimestampUs);
long wrapCount = lastPts / MAX_PTS_PLUS_ONE;
long ptsSameWrap = pts90Khz + (MAX_PTS_PLUS_ONE * wrapCount);
long ptsNextWrap = pts90Khz + (MAX_PTS_PLUS_ONE * (wrapCount + 1));
pts90Khz = ptsSameWrap >= lastPts ? ptsSameWrap : ptsNextWrap;
}
return adjustSampleTimestamp(ptsToUs(pts90Khz));
}

/**
* Offsets a timestamp in microseconds.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,162 @@ public void adjustSampleTimestamp_afterResetToDifferentStartTime() {
assertThat(firstAdjustedTimestampUs).isEqualTo(5000);
assertThat(secondAdjustedTimestampUs).isEqualTo(9000);
}

@Test
public void
adjustTsTimestamp_closeToWraparoundFollowedBySlightlySmallerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 180_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 1_000_000);
}

@Test
public void
adjustTsTimestamp_closeToWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 45_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 500_000);
}

@Test
public void adjustTsTimestamp_closeToWraparoundFollowedByMuchSmallerValue_assumesWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 2_000_000);
}

@Test
public void
adjustTsTimestamp_justBeyondWraparoundFollowedBySlightlySmallerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(45_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 500_000);
}

@Test
public void
adjustTsTimestamp_justBeyondWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(180_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 1_000_000);
}

@Test
public void adjustTsTimestamp_justBeyondWraparoundFollowedByMuchLargerValue_assumesWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 2_000_000);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedBySlightlySmallerValue_assumesWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs =
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 180_000);

assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs =
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 45_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 500_000);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedByMuchSmallerValue_assumesWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(90_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 2_000_000);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedBySlightlySmallerValue_assumesWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs = adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(45_000);

assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs =
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(180_000);

assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 1_000_000);
}

@Test
public void
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedByMuchLargerValue_doesNotAssumeWraparound() {
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
TimestampAdjuster adjuster =
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));

long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
long secondAdjustedTimestampUs =
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 90_000);

assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import static java.lang.Math.min;

import androidx.media3.common.C;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.TimestampAdjuster;
import androidx.media3.common.util.Util;
Expand Down Expand Up @@ -103,12 +102,9 @@ public TimestampAdjuster getScrTimestampAdjuster() {
}

long minScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(firstScrValue);
long maxScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(lastScrValue);
long maxScrPositionUs =
scrTimestampAdjuster.adjustTsTimestampGreaterThanPreviousTimestamp(lastScrValue);
durationUs = maxScrPositionUs - minScrPositionUs;
if (durationUs < 0) {
Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
durationUs = C.TIME_UNSET;
}
return finishReadDuration(input);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import static java.lang.Math.min;

import androidx.media3.common.C;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.TimestampAdjuster;
import androidx.media3.common.util.Util;
Expand Down Expand Up @@ -99,12 +98,9 @@ public boolean isDurationReadFinished() {
}

long minPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(firstPcrValue);
long maxPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(lastPcrValue);
long maxPcrPositionUs =
pcrTimestampAdjuster.adjustTsTimestampGreaterThanPreviousTimestamp(lastPcrValue);
durationUs = maxPcrPositionUs - minPcrPositionUs;
if (durationUs < 0) {
Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
durationUs = C.TIME_UNSET;
}
return finishReadDuration(input);
}

Expand Down

0 comments on commit 4231a1d

Please sign in to comment.