DecoderCounters.java
/*
* Copyright (C) 2016 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;
import static java.lang.Math.max;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
/**
* Maintains decoder event counts, for debugging purposes only.
*
* <p>Counters should be written from the playback thread only. Counters may be read from any
* thread. To ensure that the counter values are made visible across threads, users of this class
* should invoke {@link #ensureUpdated()} prior to reading and after writing.
*/
@UnstableApi
public final class DecoderCounters {
/** The number of times a decoder has been initialized. */
public int decoderInitCount;
/** The number of times a decoder has been released. */
public int decoderReleaseCount;
/** The number of input buffers queued to the decoder. */
public int queuedInputBufferCount;
/**
* The number of skipped input buffers.
*
* <p>A skipped input buffer is an input buffer that was deliberately not queued to the decoder.
*/
public int skippedInputBufferCount;
/** The number of rendered output buffers. */
public int renderedOutputBufferCount;
/**
* The number of skipped output buffers.
*
* <p>A skipped output buffer is an output buffer that was deliberately not rendered. This
* includes buffers that were never dequeued from the decoder and instead skipped while 'inside'
* the codec due to a flush.
*/
public int skippedOutputBufferCount;
/**
* The number of dropped buffers.
*
* <p>A dropped buffer is a buffer that was supposed to be decoded/rendered, but was instead
* dropped because it could not be rendered in time.
*
* <p>This includes all of {@link #droppedInputBufferCount} in addition to buffers dropped after
* being queued to the decoder.
*/
public int droppedBufferCount;
/**
* The number of input buffers dropped.
*
* <p>A dropped input buffer is a buffer that was not queued to the decoder because it would not
* be rendered in time.
*/
public int droppedInputBufferCount;
/**
* The maximum number of dropped buffers without an interleaving rendered output buffer.
*
* <p>Skipped buffers are ignored for the purposes of calculating this value.
*/
public int maxConsecutiveDroppedBufferCount;
/**
* The number of times all buffers to a keyframe were dropped.
*
* <p>Each time buffers to a keyframe are dropped:
*
* <ul>
* <li>This counter is incremented by one.
* <li>{@link #droppedInputBufferCount} is incremented by the number of buffers dropped from the
* source to advance to the keyframe.
* <li>{@link #droppedBufferCount} is incremented by the sum of the number of buffers dropped
* from the source to advance to the keyframe and the number of buffers 'inside' the
* decoder.
* </ul>
*/
public int droppedToKeyframeCount;
/**
* The sum of the video frame processing offsets in microseconds.
*
* <p>The processing offset for a video frame is the difference between the time at which the
* frame became available to render, and the time at which it was scheduled to be rendered. A
* positive value indicates the frame became available early enough, whereas a negative value
* indicates that the frame wasn't available until after the time at which it should have been
* rendered.
*
* <p>Note: Use {@link #addVideoFrameProcessingOffset(long)} to update this field instead of
* updating it directly.
*/
public long totalVideoFrameProcessingOffsetUs;
/**
* The number of video frame processing offsets added.
*
* <p>Note: Use {@link #addVideoFrameProcessingOffset(long)} to update this field instead of
* updating it directly.
*/
public int videoFrameProcessingOffsetCount;
/**
* Should be called to ensure counter values are made visible across threads. The playback thread
* should call this method after updating the counter values. Any other thread should call this
* method before reading the counters.
*/
public synchronized void ensureUpdated() {
// Do nothing. The use of synchronized ensures a memory barrier should another thread also
// call this method.
}
/**
* Merges the counts from {@code other} into this instance.
*
* @param other The {@link DecoderCounters} to merge into this instance.
*/
public void merge(DecoderCounters other) {
decoderInitCount += other.decoderInitCount;
decoderReleaseCount += other.decoderReleaseCount;
queuedInputBufferCount += other.queuedInputBufferCount;
skippedInputBufferCount += other.skippedInputBufferCount;
renderedOutputBufferCount += other.renderedOutputBufferCount;
skippedOutputBufferCount += other.skippedOutputBufferCount;
droppedBufferCount += other.droppedBufferCount;
droppedInputBufferCount += other.droppedInputBufferCount;
maxConsecutiveDroppedBufferCount =
max(maxConsecutiveDroppedBufferCount, other.maxConsecutiveDroppedBufferCount);
droppedToKeyframeCount += other.droppedToKeyframeCount;
addVideoFrameProcessingOffsets(
other.totalVideoFrameProcessingOffsetUs, other.videoFrameProcessingOffsetCount);
}
/**
* Adds a video frame processing offset to {@link #totalVideoFrameProcessingOffsetUs} and
* increases {@link #videoFrameProcessingOffsetCount} by one.
*
* <p>Convenience method to ensure both fields are updated when adding a single offset.
*
* @param processingOffsetUs The video frame processing offset in microseconds.
*/
public void addVideoFrameProcessingOffset(long processingOffsetUs) {
addVideoFrameProcessingOffsets(processingOffsetUs, /* count= */ 1);
}
private void addVideoFrameProcessingOffsets(long totalProcessingOffsetUs, int count) {
totalVideoFrameProcessingOffsetUs += totalProcessingOffsetUs;
videoFrameProcessingOffsetCount += count;
}
@Override
public String toString() {
return Util.formatInvariant(
"DecoderCounters {\n "
+ "decoderInits=%s,\n "
+ "decoderReleases=%s\n "
+ "queuedInputBuffers=%s\n "
+ "skippedInputBuffers=%s\n "
+ "renderedOutputBuffers=%s\n "
+ "skippedOutputBuffers=%s\n "
+ "droppedBuffers=%s\n "
+ "droppedInputBuffers=%s\n "
+ "maxConsecutiveDroppedBuffers=%s\n "
+ "droppedToKeyframeEvents=%s\n "
+ "totalVideoFrameProcessingOffsetUs=%s\n "
+ "videoFrameProcessingOffsetCount=%s\n}",
decoderInitCount,
decoderReleaseCount,
queuedInputBufferCount,
skippedInputBufferCount,
renderedOutputBufferCount,
skippedOutputBufferCount,
droppedBufferCount,
droppedInputBufferCount,
maxConsecutiveDroppedBufferCount,
droppedToKeyframeCount,
totalVideoFrameProcessingOffsetUs,
videoFrameProcessingOffsetCount);
}
}