diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTracker.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTracker.java deleted file mode 100644 index 3b143b3b76..0000000000 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTracker.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.media3.exoplayer.mediacodec; - -import static java.lang.Math.max; - -import androidx.media3.common.C; -import androidx.media3.common.Format; -import androidx.media3.common.util.Assertions; -import androidx.media3.common.util.Log; -import androidx.media3.decoder.DecoderInputBuffer; -import androidx.media3.extractor.MpegAudioUtil; -import java.nio.ByteBuffer; - -/** - * Tracks the number of processed samples to calculate an accurate current timestamp, matching the - * calculations made in the Codec2 Mp3 decoder. - */ -/* package */ final class C2Mp3TimestampTracker { - - private static final long DECODER_DELAY_FRAMES = 529; - private static final String TAG = "C2Mp3TimestampTracker"; - - private long anchorTimestampUs; - private long processedFrames; - private boolean seenInvalidMpegAudioHeader; - - /** - * Resets the timestamp tracker. - * - *

This should be done when the codec is flushed. - */ - public void reset() { - anchorTimestampUs = 0; - processedFrames = 0; - seenInvalidMpegAudioHeader = false; - } - - /** - * Updates the tracker with the given input buffer and returns the expected output timestamp. - * - * @param format The format associated with the buffer. - * @param buffer The current input buffer. - * @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; - } - - ByteBuffer data = Assertions.checkNotNull(buffer.data); - int sampleHeaderData = 0; - for (int i = 0; i < 4; i++) { - sampleHeaderData <<= 8; - sampleHeaderData |= data.get(i) & 0xFF; - } - - 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; - } - long currentBufferTimestampUs = getBufferTimestampUs(format.sampleRate); - processedFrames += frameCount; - return currentBufferTimestampUs; - } - - /** - * 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/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java index cfecc4b4f9..83cdeef82a 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java @@ -378,7 +378,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { private boolean codecNeedsAdaptationWorkaroundBuffer; private boolean shouldSkipAdaptationWorkaroundOutputBuffer; private boolean codecNeedsEosPropagation; - @Nullable private C2Mp3TimestampTracker c2Mp3TimestampTracker; private long codecHotswapDeadlineMs; private int inputIndex; private int outputIndex; @@ -956,9 +955,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { largestQueuedPresentationTimeUs = C.TIME_UNSET; lastBufferInStreamPresentationTimeUs = C.TIME_UNSET; lastProcessedOutputBufferTimeUs = C.TIME_UNSET; - if (c2Mp3TimestampTracker != null) { - c2Mp3TimestampTracker.reset(); - } codecDrainState = DRAIN_STATE_NONE; codecDrainAction = DRAIN_ACTION_NONE; // Reconfiguration data sent shortly before the flush may not have been processed by the @@ -979,7 +975,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { resetCodecStateForFlush(); pendingPlaybackException = null; - c2Mp3TimestampTracker = null; availableCodecInfos = null; codecInfo = null; codecInputFormat = null; @@ -1202,9 +1197,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { this.codecNeedsAdaptationWorkaroundBuffer = codecAdaptationWorkaroundMode != ADAPTATION_WORKAROUND_MODE_NEVER; } - if ("c2.android.mp3.decoder".equals(codecInfo.name)) { - c2Mp3TimestampTracker = new C2Mp3TimestampTracker(); - } if (getState() == STATE_STARTED) { codecHotswapDeadlineMs = getClock().elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS; @@ -1396,19 +1388,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer { long presentationTimeUs = buffer.timeUs; - 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 (waitingForFirstSampleInFormat) { if (!pendingOutputStreamChanges.isEmpty()) { pendingOutputStreamChanges.peekLast().formatQueue.add(presentationTimeUs, inputFormat); diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTrackerTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTrackerTest.java deleted file mode 100644 index fb76a4dbce..0000000000 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/mediacodec/C2Mp3TimestampTrackerTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.media3.exoplayer.mediacodec; - -import static androidx.media3.test.utils.TestUtil.createByteArray; -import static com.google.common.truth.Truth.assertThat; - -import androidx.media3.common.Format; -import androidx.media3.common.MimeTypes; -import androidx.media3.decoder.DecoderInputBuffer; -import androidx.test.ext.junit.runners.AndroidJUnit4; -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; - -/** Unit tests for {@link C2Mp3TimestampTracker}. */ -@RunWith(AndroidJUnit4.class) -public final class C2Mp3TimestampTrackerTest { - - private static final Format FORMAT = - new Format.Builder() - .setSampleMimeType(MimeTypes.AUDIO_MPEG) - .setChannelCount(2) - .setSampleRate(44_100) - .build(); - - private C2Mp3TimestampTracker timestampTracker; - private DecoderInputBuffer buffer; - private DecoderInputBuffer invalidBuffer; - - @Before - public void setUp() { - timestampTracker = new C2Mp3TimestampTracker(); - 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 handleBuffers_outputsCorrectTimestamps() { - List presentationTimesUs = new ArrayList<>(); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.getLastOutputBufferPresentationTimeUs(FORMAT)); - - assertThat(presentationTimesUs).containsExactly(100_000L, 114_126L, 140_249L, 166_371L); - } - - @Test - public void handleBuffersWithReset_resetsTimestamps() { - List presentationTimesUs = new ArrayList<>(); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - timestampTracker.reset(); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.getLastOutputBufferPresentationTimeUs(FORMAT)); - - assertThat(presentationTimesUs).containsExactly(100_000L, 114_126L, 100_000L, 114_126L); - } - - @Test - public void handleInvalidBuffer_stopsUpdatingTimestamps() { - List presentationTimesUs = new ArrayList<>(); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)); - presentationTimesUs.add(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, invalidBuffer)); - presentationTimesUs.add(timestampTracker.getLastOutputBufferPresentationTimeUs(FORMAT)); - - assertThat(presentationTimesUs).containsExactly(100_000L, 114_126L, 120_000L, 120_000L); - } - - @Test - public void firstTimestamp_matchesBuffer() { - assertThat(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, buffer)) - .isEqualTo(buffer.timeUs); - timestampTracker.reset(); - assertThat(timestampTracker.updateAndGetPresentationTimeUs(FORMAT, invalidBuffer)) - .isEqualTo(invalidBuffer.timeUs); - } -}