Skip to content

Commit 0157878

Browse files
toniheicopybara-github
authored andcommitted
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#855 #minor-release PiperOrigin-RevId: 590936953
1 parent 4974f96 commit 0157878

File tree

5 files changed

+191
-12
lines changed

5 files changed

+191
-12
lines changed

RELEASENOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
playback because of their higher resolution.
5959
* Fix wrong keyframe detection for TS H264 streams
6060
([#864](https://github.com/androidx/media/pull/864)).
61+
* Fix duration estimation of TS streams that are longer than 47721 seconds
62+
([#855](https://github.com/androidx/media/issues/855)).
6163
* Audio:
6264
* Fix handling of EOS for `SilenceSkippingAudioProcessor` when called
6365
multiple times ([#712](https://github.com/androidx/media/issues/712)).

libraries/common/src/main/java/androidx/media3/common/util/TimestampAdjuster.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ public synchronized void reset(long firstSampleTimestampUs) {
187187
/**
188188
* Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
189189
*
190+
* <p>When estimating the wraparound, the method assumes that this timestamp is close to the
191+
* previous adjusted timestamp.
192+
*
190193
* @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
191194
* @return The adjusted timestamp in microseconds.
192195
*/
@@ -209,6 +212,30 @@ public synchronized long adjustTsTimestamp(long pts90Khz) {
209212
return adjustSampleTimestamp(ptsToUs(pts90Khz));
210213
}
211214

215+
/**
216+
* Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound.
217+
*
218+
* <p>When estimating the wraparound, the method assumes that the timestamp is strictly greater
219+
* than the previous adjusted timestamp.
220+
*
221+
* @param pts90Khz A 90 kHz clock MPEG-2 TS presentation timestamp.
222+
* @return The adjusted timestamp in microseconds.
223+
*/
224+
public synchronized long adjustTsTimestampGreaterThanPreviousTimestamp(long pts90Khz) {
225+
if (pts90Khz == C.TIME_UNSET) {
226+
return C.TIME_UNSET;
227+
}
228+
if (lastUnadjustedTimestampUs != C.TIME_UNSET) {
229+
// The wrap count for the current PTS must be same or greater than the previous one.
230+
long lastPts = usToNonWrappedPts(lastUnadjustedTimestampUs);
231+
long wrapCount = lastPts / MAX_PTS_PLUS_ONE;
232+
long ptsSameWrap = pts90Khz + (MAX_PTS_PLUS_ONE * wrapCount);
233+
long ptsNextWrap = pts90Khz + (MAX_PTS_PLUS_ONE * (wrapCount + 1));
234+
pts90Khz = ptsSameWrap >= lastPts ? ptsSameWrap : ptsNextWrap;
235+
}
236+
return adjustSampleTimestamp(ptsToUs(pts90Khz));
237+
}
238+
212239
/**
213240
* Offsets a timestamp in microseconds.
214241
*

libraries/common/src/test/java/androidx/media3/common/util/TimestampAdjusterTest.java

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,162 @@ public void adjustSampleTimestamp_afterResetToDifferentStartTime() {
8282
assertThat(firstAdjustedTimestampUs).isEqualTo(5000);
8383
assertThat(secondAdjustedTimestampUs).isEqualTo(9000);
8484
}
85+
86+
@Test
87+
public void
88+
adjustTsTimestamp_closeToWraparoundFollowedBySlightlySmallerValue_doesNotAssumeWraparound() {
89+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
90+
TimestampAdjuster adjuster =
91+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
92+
93+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
94+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 180_000);
95+
96+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 1_000_000);
97+
}
98+
99+
@Test
100+
public void
101+
adjustTsTimestamp_closeToWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
102+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
103+
TimestampAdjuster adjuster =
104+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
105+
106+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
107+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 45_000);
108+
109+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 500_000);
110+
}
111+
112+
@Test
113+
public void adjustTsTimestamp_closeToWraparoundFollowedByMuchSmallerValue_assumesWraparound() {
114+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
115+
TimestampAdjuster adjuster =
116+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
117+
118+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
119+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
120+
121+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 2_000_000);
122+
}
123+
124+
@Test
125+
public void
126+
adjustTsTimestamp_justBeyondWraparoundFollowedBySlightlySmallerValue_doesNotAssumeWraparound() {
127+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
128+
TimestampAdjuster adjuster =
129+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
130+
131+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
132+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(45_000);
133+
134+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 500_000);
135+
}
136+
137+
@Test
138+
public void
139+
adjustTsTimestamp_justBeyondWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
140+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
141+
TimestampAdjuster adjuster =
142+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
143+
144+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
145+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(180_000);
146+
147+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 1_000_000);
148+
}
149+
150+
@Test
151+
public void adjustTsTimestamp_justBeyondWraparoundFollowedByMuchLargerValue_assumesWraparound() {
152+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
153+
TimestampAdjuster adjuster =
154+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
155+
156+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
157+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
158+
159+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs - 2_000_000);
160+
}
161+
162+
@Test
163+
public void
164+
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedBySlightlySmallerValue_assumesWraparound() {
165+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
166+
TimestampAdjuster adjuster =
167+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
168+
169+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
170+
long secondAdjustedTimestampUs =
171+
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 180_000);
172+
173+
assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
174+
}
175+
176+
@Test
177+
public void
178+
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
179+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
180+
TimestampAdjuster adjuster =
181+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
182+
183+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
184+
long secondAdjustedTimestampUs =
185+
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 45_000);
186+
187+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 500_000);
188+
}
189+
190+
@Test
191+
public void
192+
adjustTsTimestampGreaterThanPreviousTimestamp_closeToWraparoundFollowedByMuchSmallerValue_assumesWraparound() {
193+
// Init timestamp with a non-zero wraparound (multiple of 33-bit) and close to the next one.
194+
TimestampAdjuster adjuster =
195+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L - 90_000));
196+
197+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(0x200000000L - 90_000);
198+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(90_000);
199+
200+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 2_000_000);
201+
}
202+
203+
@Test
204+
public void
205+
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedBySlightlySmallerValue_assumesWraparound() {
206+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
207+
TimestampAdjuster adjuster =
208+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
209+
210+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
211+
long secondAdjustedTimestampUs = adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(45_000);
212+
213+
assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
214+
}
215+
216+
@Test
217+
public void
218+
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedBySlightlyLargerValue_doesNotAssumeWraparound() {
219+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
220+
TimestampAdjuster adjuster =
221+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
222+
223+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
224+
long secondAdjustedTimestampUs =
225+
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(180_000);
226+
227+
assertThat(secondAdjustedTimestampUs).isEqualTo(firstAdjustedTimestampUs + 1_000_000);
228+
}
229+
230+
@Test
231+
public void
232+
adjustTsTimestampGreaterThanPreviousTimestamp_justBeyondWraparoundFollowedByMuchLargerValue_doesNotAssumeWraparound() {
233+
// Init timestamp with a non-zero wraparound (multiple of 33-bit), just beyond the last one.
234+
TimestampAdjuster adjuster =
235+
new TimestampAdjuster(TimestampAdjuster.ptsToUs(3 * 0x200000000L + 90_000));
236+
237+
long firstAdjustedTimestampUs = adjuster.adjustTsTimestamp(90_000);
238+
long secondAdjustedTimestampUs =
239+
adjuster.adjustTsTimestampGreaterThanPreviousTimestamp(0x200000000L - 90_000);
240+
241+
assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
242+
}
85243
}

libraries/extractor/src/main/java/androidx/media3/extractor/ts/PsDurationReader.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import static java.lang.Math.min;
1919

2020
import androidx.media3.common.C;
21-
import androidx.media3.common.util.Log;
2221
import androidx.media3.common.util.ParsableByteArray;
2322
import androidx.media3.common.util.TimestampAdjuster;
2423
import androidx.media3.common.util.Util;
@@ -103,12 +102,9 @@ public TimestampAdjuster getScrTimestampAdjuster() {
103102
}
104103

105104
long minScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(firstScrValue);
106-
long maxScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(lastScrValue);
105+
long maxScrPositionUs =
106+
scrTimestampAdjuster.adjustTsTimestampGreaterThanPreviousTimestamp(lastScrValue);
107107
durationUs = maxScrPositionUs - minScrPositionUs;
108-
if (durationUs < 0) {
109-
Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
110-
durationUs = C.TIME_UNSET;
111-
}
112108
return finishReadDuration(input);
113109
}
114110

libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsDurationReader.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import static java.lang.Math.min;
1919

2020
import androidx.media3.common.C;
21-
import androidx.media3.common.util.Log;
2221
import androidx.media3.common.util.ParsableByteArray;
2322
import androidx.media3.common.util.TimestampAdjuster;
2423
import androidx.media3.common.util.Util;
@@ -99,12 +98,9 @@ public boolean isDurationReadFinished() {
9998
}
10099

101100
long minPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(firstPcrValue);
102-
long maxPcrPositionUs = pcrTimestampAdjuster.adjustTsTimestamp(lastPcrValue);
101+
long maxPcrPositionUs =
102+
pcrTimestampAdjuster.adjustTsTimestampGreaterThanPreviousTimestamp(lastPcrValue);
103103
durationUs = maxPcrPositionUs - minPcrPositionUs;
104-
if (durationUs < 0) {
105-
Log.w(TAG, "Invalid duration: " + durationUs + ". Using TIME_UNSET instead.");
106-
durationUs = C.TIME_UNSET;
107-
}
108104
return finishReadDuration(input);
109105
}
110106

0 commit comments

Comments
 (0)