Limit processing Opus decode-only frames by seek-preroll in offload

As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only. However, the renderer should not send samples with time preceding that range. This change adds that constraint.

#minor-release

PiperOrigin-RevId: 588014983
This commit is contained in:
michaelkatz 2023-12-05 03:41:54 -08:00 committed by Copybara-Service
parent b1541b096f
commit d1e38abf93
3 changed files with 53 additions and 16 deletions

View File

@ -2307,10 +2307,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Process any batched data. // Process any batched data.
checkState(!outputStreamEnded); checkState(!outputStreamEnded);
if (bypassBatchBuffer.hasSamples()) { if (bypassBatchBuffer.hasSamples()) {
boolean isDecodeOnly =
bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs()
&& (outputFormat == null
|| !Objects.equals(outputFormat.sampleMimeType, MimeTypes.AUDIO_OPUS));
if (processOutputBuffer( if (processOutputBuffer(
positionUs, positionUs,
elapsedRealtimeUs, elapsedRealtimeUs,
@ -2320,7 +2316,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/* bufferFlags= */ 0, /* bufferFlags= */ 0,
bypassBatchBuffer.getSampleCount(), bypassBatchBuffer.getSampleCount(),
bypassBatchBuffer.getFirstSampleTimeUs(), bypassBatchBuffer.getFirstSampleTimeUs(),
isDecodeOnly, isDecodeOnly(getLastResetPositionUs(), bypassBatchBuffer.getLastSampleTimeUs()),
bypassBatchBuffer.isEndOfStream(), bypassBatchBuffer.isEndOfStream(),
checkNotNull(outputFormat))) { checkNotNull(outputFormat))) {
// The batch buffer has been fully processed. // The batch buffer has been fully processed.
@ -2417,9 +2413,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
bypassSampleBuffer.format = outputFormat; bypassSampleBuffer.format = outputFormat;
handleInputBufferSupplementalData(bypassSampleBuffer); handleInputBufferSupplementalData(bypassSampleBuffer);
} }
if (OpusUtil.needToDecodeOpusFrame(
getLastResetPositionUs(), bypassSampleBuffer.timeUs)) {
// Packetize as long as frame does not precede the last reset position by more than
// seek-preroll.
oggOpusAudioPacketizer.packetize( oggOpusAudioPacketizer.packetize(
bypassSampleBuffer, checkNotNull(outputFormat).initializationData); bypassSampleBuffer, checkNotNull(outputFormat).initializationData);
} }
}
if (!haveBypassBatchBufferAndNewSampleSameDecodeOnlyState() if (!haveBypassBatchBufferAndNewSampleSameDecodeOnlyState()
|| !bypassBatchBuffer.append(bypassSampleBuffer)) { || !bypassBatchBuffer.append(bypassSampleBuffer)) {
bypassSampleBufferPending = true; bypassSampleBufferPending = true;
@ -2440,11 +2441,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true; return true;
} }
long lastResetPositionUs = getLastResetPositionUs(); long lastResetPositionUs = getLastResetPositionUs();
boolean batchBufferIsDecodeOnly = bypassBatchBuffer.getLastSampleTimeUs() < lastResetPositionUs; boolean batchBufferIsDecodeOnly =
boolean sampleBufferIsDecodeOnly = bypassSampleBuffer.timeUs < lastResetPositionUs; isDecodeOnly(lastResetPositionUs, bypassBatchBuffer.getLastSampleTimeUs());
boolean sampleBufferIsDecodeOnly = isDecodeOnly(lastResetPositionUs, bypassSampleBuffer.timeUs);
return batchBufferIsDecodeOnly == sampleBufferIsDecodeOnly; return batchBufferIsDecodeOnly == sampleBufferIsDecodeOnly;
} }
/**
* Returns if based on target play time and frame time that the frame should be decode-only.
*
* <p>If the format is Opus, then the frame is decode-only if the frame time precedes the start
* time by more than seek-preroll.
*
* @param startTimeUs The time to start playing at.
* @param frameTimeUs The time of the sample.
* @return Whether the frame is decode-only.
*/
private boolean isDecodeOnly(long startTimeUs, long frameTimeUs) {
// Opus frames that precede the target position by more than seek-preroll should be skipped.
return frameTimeUs < startTimeUs
&& (outputFormat == null
|| !Objects.equals(outputFormat.sampleMimeType, MimeTypes.AUDIO_OPUS)
|| !OpusUtil.needToDecodeOpusFrame(
/* startTimeUs= */ startTimeUs, /* frameTimeUs= */ frameTimeUs));
}
private static boolean isMediaCodecException(IllegalStateException error) { private static boolean isMediaCodecException(IllegalStateException error) {
if (Util.SDK_INT >= 21 && isMediaCodecExceptionV21(error)) { if (Util.SDK_INT >= 21 && isMediaCodecExceptionV21(error)) {
return true; return true;

View File

@ -149,6 +149,23 @@ public class OpusUtil {
return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF); return ((header[11] & 0xFF) << 8) | (header[10] & 0xFF);
} }
/**
* Returns whether an Opus frame should be sent to the decoder as it is either past the start
* position or within the seek-preroll duration.
*
* <p>The measure of whether an Opus frame should not be decoded is if its time precedes the start
* position by more than the default seek-preroll value.
*
* @param startTimeUs The time to start playing at.
* @param frameTimeUs The time of the Opus sample.
* @return Whether the frame should be decoded.
*/
public static boolean needToDecodeOpusFrame(long startTimeUs, long frameTimeUs) {
// Divide by 1000 in rhs value to convert nanoseconds to microseconds.
return (startTimeUs - frameTimeUs)
<= (sampleCountToNanoseconds(DEFAULT_SEEK_PRE_ROLL_SAMPLES) / 1000);
}
private static long getPacketDurationUs(byte packetByte0, byte packetByte1) { private static long getPacketDurationUs(byte packetByte0, byte packetByte1) {
// See RFC6716, Sections 3.1 and 3.2. // See RFC6716, Sections 3.1 and 3.2.
int toc = packetByte0 & 0xFF; int toc = packetByte0 & 0xFF;

View File

@ -1,8 +1,7 @@
SinkDump (OggOpus): SinkDump (OggOpus):
buffers.length = 6 buffers.length = 5
buffers[0] = length 207, hash D462AF66 buffers[0] = length 3979, hash 63A08FAD
buffers[1] = length 3891, hash FE9EE7C1 buffers[1] = length 3696, hash ABE4B611
buffers[2] = length 3732, hash C2249BC1 buffers[2] = length 3770, hash 67EAAA4
buffers[3] = length 3731, hash A9384B0F buffers[3] = length 4057, hash C53B8DF3
buffers[4] = length 4091, hash 9631FA86 buffers[4] = length 926, hash B21B9A24
buffers[5] = length 776, hash 4BC27E65