Remove C2Mp3TimestampTracker
This tracker aims to replicate the behavior of a specific codec to ensure MediaCodecRenderer correctly detects stream and output format transitions. The class was needed because MediaCodecRenderer made assumptions about codec behavior this codec did not fulfil (in particular, changing timestamps and number of samples). Since then, MediaCodecRenderer was made more robust to this kind of codec behavior in general and currently has no assumptions that require any special handling of this codec. This means we can remove the workaround completely. PiperOrigin-RevId: 549610989
This commit is contained in:
parent
27bda610aa
commit
85cde93e6c
@ -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.
|
||||
*
|
||||
* <p>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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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<Long> 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<Long> 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<Long> 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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user