Use different wraparound assumptions for duration readers

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: androidx/media#855

#minor-release

PiperOrigin-RevId: 590936953
This commit is contained in:
tonihei 2023-12-14 07:44:32 -08:00 committed by Copybara-Service
parent 4974f960e7
commit 01578780a6
5 changed files with 191 additions and 12 deletions

View File

@ -58,6 +58,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)).

View File

@ -187,6 +187,9 @@ public final class TimestampAdjuster {
/**
* 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.
*/
@ -209,6 +212,30 @@ public final class TimestampAdjuster {
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.
*

View File

@ -82,4 +82,162 @@ public class TimestampAdjusterTest {
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);
}
}

View File

@ -18,7 +18,6 @@ package androidx.media3.extractor.ts;
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;
@ -103,12 +102,9 @@ import java.io.IOException;
}
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);
}

View File

@ -18,7 +18,6 @@ package androidx.media3.extractor.ts;
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;
@ -99,12 +98,9 @@ import java.io.IOException;
}
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);
}