diff --git a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java index 9805ee19be..868ce1fa01 100644 --- a/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java +++ b/libraries/exoplayer_hls/src/test/java/androidx/media3/exoplayer/hls/playlist/HlsMediaPlaylistParserTest.java @@ -78,6 +78,7 @@ public class HlsMediaPlaylistParserTest { + "#EXTINF:7.975,\n" + "https://priv.example.com/fileSequence2683.ts\n" + "\n" + // 2.002 tests correct rounding, see https://github.com/google/ExoPlayer/issues/9575. + "#EXTINF:2.002,\n" + "https://priv.example.com/fileSequence2684.ts\n" + "#EXT-X-ENDLIST"; @@ -395,7 +396,7 @@ public class HlsMediaPlaylistParserTest { .parse(playlistUri, inputStream); assertThat(playlist.segments).hasSize(3); - assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000080); assertThat(previousPlaylist.segments.get(0).relativeDiscontinuitySequence).isEqualTo(0); assertThat(previousPlaylist.segments.get(1).relativeDiscontinuitySequence).isEqualTo(1); assertThat(previousPlaylist.segments.get(2).relativeDiscontinuitySequence).isEqualTo(1); @@ -454,12 +455,12 @@ public class HlsMediaPlaylistParserTest { assertThat(playlist.segments.get(0).parts.get(0).relativeDiscontinuitySequence).isEqualTo(1); assertThat(playlist.segments.get(0).parts.get(1).relativeStartTimeUs).isEqualTo(2000000); assertThat(playlist.segments.get(0).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000079); - assertThat(playlist.segments.get(1).parts.get(0).relativeStartTimeUs).isEqualTo(4000079); + assertThat(playlist.segments.get(1).relativeStartTimeUs).isEqualTo(4000080); + assertThat(playlist.segments.get(1).parts.get(0).relativeStartTimeUs).isEqualTo(4000080); assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.segments.get(1).parts.get(1).relativeStartTimeUs).isEqualTo(6000079); + assertThat(playlist.segments.get(1).parts.get(1).relativeStartTimeUs).isEqualTo(6000080); assertThat(playlist.segments.get(1).parts.get(1).relativeDiscontinuitySequence).isEqualTo(1); - assertThat(playlist.trailingParts.get(0).relativeStartTimeUs).isEqualTo(8000158); + assertThat(playlist.trailingParts.get(0).relativeStartTimeUs).isEqualTo(8000160); assertThat(playlist.trailingParts.get(0).relativeDiscontinuitySequence).isEqualTo(1); } diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump index 707be77d53..816e26e384 100644 --- a/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_sef_slow_motion.mp4.dump @@ -135,145 +135,145 @@ sample: dataHashCode = 1000136444 size = 140 isKeyFrame = true - presentationTimeUs = 416 + presentationTimeUs = 417 sample: trackIndex = 0 dataHashCode = 217961709 size = 172 isKeyFrame = true - presentationTimeUs = 3332 + presentationTimeUs = 3334 sample: trackIndex = 0 dataHashCode = -879376936 size = 176 isKeyFrame = true - presentationTimeUs = 6915 + presentationTimeUs = 6917 sample: trackIndex = 0 dataHashCode = 1259979587 size = 192 isKeyFrame = true - presentationTimeUs = 10581 + presentationTimeUs = 10584 sample: trackIndex = 0 dataHashCode = 907407225 size = 188 isKeyFrame = true - presentationTimeUs = 14581 + presentationTimeUs = 14584 sample: trackIndex = 0 dataHashCode = -904354707 size = 176 isKeyFrame = true - presentationTimeUs = 18497 + presentationTimeUs = 18500 sample: trackIndex = 0 dataHashCode = 1001385853 size = 172 isKeyFrame = true - presentationTimeUs = 22163 + presentationTimeUs = 22167 sample: trackIndex = 0 dataHashCode = 1545716086 size = 196 isKeyFrame = true - presentationTimeUs = 25746 + presentationTimeUs = 25750 sample: trackIndex = 0 dataHashCode = 358710839 size = 180 isKeyFrame = true - presentationTimeUs = 29829 + presentationTimeUs = 29834 sample: trackIndex = 0 dataHashCode = -671124798 size = 140 isKeyFrame = true - presentationTimeUs = 33579 + presentationTimeUs = 33584 sample: trackIndex = 0 dataHashCode = -945404910 size = 120 isKeyFrame = true - presentationTimeUs = 36495 + presentationTimeUs = 36500 sample: trackIndex = 0 dataHashCode = 1881048379 size = 88 isKeyFrame = true - presentationTimeUs = 38995 + presentationTimeUs = 39000 sample: trackIndex = 0 dataHashCode = 1059579897 size = 88 isKeyFrame = true - presentationTimeUs = 40828 + presentationTimeUs = 40834 sample: trackIndex = 0 dataHashCode = 1496098648 size = 84 isKeyFrame = true - presentationTimeUs = 42661 + presentationTimeUs = 42667 sample: trackIndex = 0 dataHashCode = 250093960 size = 751 isKeyFrame = true - presentationTimeUs = 44411 + presentationTimeUs = 44417 sample: trackIndex = 0 dataHashCode = 1895536226 size = 1045 isKeyFrame = true - presentationTimeUs = 59994 + presentationTimeUs = 60063 sample: trackIndex = 0 dataHashCode = 1723596464 size = 947 isKeyFrame = true - presentationTimeUs = 81744 + presentationTimeUs = 81834 sample: trackIndex = 0 dataHashCode = -978803114 size = 946 isKeyFrame = true - presentationTimeUs = 101410 + presentationTimeUs = 101563 sample: trackIndex = 0 dataHashCode = 387377078 size = 946 isKeyFrame = true - presentationTimeUs = 121076 + presentationTimeUs = 121271 sample: trackIndex = 0 dataHashCode = -132658698 size = 901 isKeyFrame = true - presentationTimeUs = 140742 + presentationTimeUs = 140980 sample: trackIndex = 0 dataHashCode = 1495036471 size = 899 isKeyFrame = true - presentationTimeUs = 159492 + presentationTimeUs = 159750 sample: trackIndex = 0 dataHashCode = 304440590 size = 878 isKeyFrame = true - presentationTimeUs = 178158 + presentationTimeUs = 178480 sample: trackIndex = 0 dataHashCode = -1955900344 size = 112 isKeyFrame = true - presentationTimeUs = 196408 + presentationTimeUs = 196771 sample: trackIndex = 0 dataHashCode = 88896626 size = 116 isKeyFrame = true - presentationTimeUs = 198741 + presentationTimeUs = 199105 sample: trackIndex = 1 dataHashCode = 2139021989 diff --git a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java index 5f3977a681..4ca6c25586 100644 --- a/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java +++ b/libraries/transformer/src/main/java/androidx/media3/transformer/AudioSamplePipeline.java @@ -31,7 +31,9 @@ import androidx.media3.exoplayer.ExoPlaybackException; import androidx.media3.exoplayer.audio.AudioProcessor; import androidx.media3.exoplayer.audio.AudioProcessor.AudioFormat; import androidx.media3.exoplayer.audio.SonicAudioProcessor; +import com.google.common.math.LongMath; import java.io.IOException; +import java.math.RoundingMode; import java.nio.ByteBuffer; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -62,6 +64,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; private @MonotonicNonNull AudioFormat encoderInputAudioFormat; private @MonotonicNonNull MediaCodecAdapterWrapper encoder; private long nextEncoderInputBufferTimeUs; + private long encoderBufferDurationRemainder; private ByteBuffer sonicOutputBuffer; private boolean drainingSonicForSpeedChange; @@ -82,6 +85,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; sonicAudioProcessor = new SonicAudioProcessor(); sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER; nextEncoderInputBufferTimeUs = 0; + encoderBufferDurationRemainder = 0; speedProvider = new SegmentSpeedProvider(decoderInputFormat); currentSpeed = speedProvider.getSpeed(0); try { @@ -269,7 +273,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; encoderInputBufferData.put(inputBuffer); encoderInputBuffer.timeUs = nextEncoderInputBufferTimeUs; nextEncoderInputBufferTimeUs += - getBufferDurationUs( + getEncoderBufferDurationUs( /* bytesWritten= */ encoderInputBufferData.position(), encoderInputAudioFormat.bytesPerFrame, encoderInputAudioFormat.sampleRate); @@ -366,9 +370,17 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; errorCode); } - // TODO(internal b/204978301): Ensure encoder and decoder timestamps match when no speed change. - private static long getBufferDurationUs(long bytesWritten, int bytesPerFrame, int sampleRate) { - long framesWritten = bytesWritten / bytesPerFrame; - return framesWritten * C.MICROS_PER_SECOND / sampleRate; + private long getEncoderBufferDurationUs(long bytesWritten, int bytesPerFrame, int sampleRate) { + // The calculation below accounts for remainders and rounding. Without that it corresponds to + // the following: + // bufferDurationUs = numberOfFramesInBuffer * sampleDurationUs + // where numberOfFramesInBuffer = bytesWritten / bytesPerFrame + // and sampleDurationUs = C.MICROS_PER_SECOND / sampleRate + long framesWrittenMicrosPerSecond = + bytesWritten * C.MICROS_PER_SECOND / bytesPerFrame + encoderBufferDurationRemainder; + long bufferDurationUs = + LongMath.divide(framesWrittenMicrosPerSecond, sampleRate, RoundingMode.CEILING); + encoderBufferDurationRemainder = framesWrittenMicrosPerSecond - bufferDurationUs * sampleRate; + return bufferDurationUs; } }