diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTracker.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTracker.java index 04b453d529..8fde836637 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTracker.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTracker.java @@ -15,6 +15,8 @@ */ package com.google.android.exoplayer2.mediacodec; +import static java.lang.Math.max; + import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.MpegAudioUtil; @@ -29,13 +31,11 @@ import java.nio.ByteBuffer; */ /* package */ final class C2Mp3TimestampTracker { - // Mirroring the actual codec, as can be found at - // https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.h;l=55;drc=3665390c9d32a917398b240c5a46ced07a3b65eb - private static final long DECODER_DELAY_SAMPLES = 529; + private static final long DECODER_DELAY_FRAMES = 529; private static final String TAG = "C2Mp3TimestampTracker"; - private long processedSamples; private long anchorTimestampUs; + private long processedFrames; private boolean seenInvalidMpegAudioHeader; /** @@ -44,8 +44,8 @@ import java.nio.ByteBuffer; *
This should be done when the codec is flushed.
*/
public void reset() {
- processedSamples = 0;
anchorTimestampUs = 0;
+ processedFrames = 0;
seenInvalidMpegAudioHeader = false;
}
@@ -57,6 +57,10 @@ import java.nio.ByteBuffer;
* @return The expected output presentation time, in microseconds.
*/
public long updateAndGetPresentationTimeUs(Format format, DecoderInputBuffer buffer) {
+ if (processedFrames == 0) {
+ anchorTimestampUs = buffer.timeUs;
+ }
+
if (seenInvalidMpegAudioHeader) {
return buffer.timeUs;
}
@@ -71,23 +75,32 @@ import java.nio.ByteBuffer;
int frameCount = MpegAudioUtil.parseMpegAudioFrameSampleCount(sampleHeaderData);
if (frameCount == C.LENGTH_UNSET) {
seenInvalidMpegAudioHeader = true;
+ processedFrames = 0;
+ anchorTimestampUs = buffer.timeUs;
Log.w(TAG, "MPEG audio header is invalid.");
return buffer.timeUs;
}
-
- // These calculations mirror the timestamp calculations in the Codec2 Mp3 Decoder.
- // https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
- if (processedSamples == 0) {
- anchorTimestampUs = buffer.timeUs;
- processedSamples = frameCount - DECODER_DELAY_SAMPLES;
- return anchorTimestampUs;
- }
- long processedDurationUs = getProcessedDurationUs(format);
- processedSamples += frameCount;
- return anchorTimestampUs + processedDurationUs;
+ long currentBufferTimestampUs = getBufferTimestampUs(format.sampleRate);
+ processedFrames += frameCount;
+ return currentBufferTimestampUs;
}
- private long getProcessedDurationUs(Format format) {
- return processedSamples * C.MICROS_PER_SECOND / format.sampleRate;
+ /**
+ * Returns the timestamp of the last buffer that will be produced if the stream ends at the
+ * current position, in microseconds.
+ *
+ * @param format The format associated with input buffers.
+ * @return The timestamp of the last buffer that will be produced if the stream ends at the
+ * current position, in microseconds.
+ */
+ public long getLastOutputBufferPresentationTimeUs(Format format) {
+ return getBufferTimestampUs(format.sampleRate);
+ }
+
+ private long getBufferTimestampUs(long sampleRate) {
+ // This calculation matches the timestamp calculation in the Codec2 Mp3 Decoder.
+ // https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
+ return anchorTimestampUs
+ + max(0, (processedFrames - DECODER_DELAY_FRAMES) * C.MICROS_PER_SECOND / sampleRate);
}
}
diff --git a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
index 39f2622caa..8d02b95324 100644
--- a/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
+++ b/library/core/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java
@@ -1338,6 +1338,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (c2Mp3TimestampTracker != null) {
presentationTimeUs =
c2Mp3TimestampTracker.updateAndGetPresentationTimeUs(inputFormat, buffer);
+ // When draining the C2 MP3 decoder it produces an extra non-empty buffer with a timestamp
+ // after all queued input buffer timestamps (unlike other decoders, which generally propagate
+ // the input timestamps to output buffers 1:1). To detect the end of the stream when this
+ // buffer is dequeued we override the largest queued timestamp accordingly.
+ largestQueuedPresentationTimeUs =
+ max(
+ largestQueuedPresentationTimeUs,
+ c2Mp3TimestampTracker.getLastOutputBufferPresentationTimeUs(inputFormat));
}
if (buffer.isDecodeOnly()) {
@@ -1347,14 +1355,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
formatQueue.add(presentationTimeUs, inputFormat);
waitingForFirstSampleInFormat = false;
}
-
- // TODO(b/158483277): Find the root cause of why a gap is introduced in MP3 playback when using
- // presentationTimeUs from the c2Mp3TimestampTracker.
- if (c2Mp3TimestampTracker != null) {
- largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, buffer.timeUs);
- } else {
- largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
- }
+ largestQueuedPresentationTimeUs = max(largestQueuedPresentationTimeUs, presentationTimeUs);
buffer.flip();
if (buffer.hasSupplementalData()) {
handleInputBufferSupplementalData(buffer);
diff --git a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTrackerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTrackerTest.java
index 1108b882e4..eaec4a4717 100644
--- a/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTrackerTest.java
+++ b/library/core/src/test/java/com/google/android/exoplayer2/mediacodec/C2Mp3TimestampTrackerTest.java
@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.mediacodec;
+import static com.google.android.exoplayer2.testutil.TestUtil.createByteArray;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -22,6 +23,8 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.util.MimeTypes;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -30,49 +33,68 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public final class C2Mp3TimestampTrackerTest {
- private static final Format AUDIO_MP3 =
+ private static final Format FORMAT =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_MPEG)
.setChannelCount(2)
.setSampleRate(44_100)
.build();
- private DecoderInputBuffer buffer;
private C2Mp3TimestampTracker timestampTracker;
+ private DecoderInputBuffer buffer;
+ private DecoderInputBuffer invalidBuffer;
@Before
public void setUp() {
- buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
timestampTracker = new C2Mp3TimestampTracker();
- buffer.data = ByteBuffer.wrap(new byte[] {-1, -5, -24, 60});
+ buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
+ buffer.data = ByteBuffer.wrap(createByteArray(0xFF, 0xFB, 0xE8, 0x3C));
buffer.timeUs = 100_000;
+ invalidBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED);
+ invalidBuffer.data = ByteBuffer.wrap(createByteArray(0, 0, 0, 0));
+ invalidBuffer.timeUs = 120_000;
}
@Test
- public void whenUpdateCalledMultipleTimes_timestampsIncrease() {
- long first = timestampTracker.updateAndGetPresentationTimeUs(AUDIO_MP3, buffer);
- long second = timestampTracker.updateAndGetPresentationTimeUs(AUDIO_MP3, buffer);
- long third = timestampTracker.updateAndGetPresentationTimeUs(AUDIO_MP3, buffer);
+ public void handleBuffers_outputsCorrectTimestamps() {
+ List