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
(cherry picked from commit d1e38abf93353af1bc3fb2d9a0dfbac01e387f3e)
This commit is contained in:
michaelkatz 2023-12-05 03:41:54 -08:00 committed by microkatz
parent 57187aa899
commit 03eae4f9ac
3 changed files with 53 additions and 16 deletions

View File

@ -2295,10 +2295,6 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
// Process any batched data.
checkState(!outputStreamEnded);
if (bypassBatchBuffer.hasSamples()) {
boolean isDecodeOnly =
bypassBatchBuffer.getLastSampleTimeUs() < getLastResetPositionUs()
&& (outputFormat == null
|| !Objects.equals(outputFormat.sampleMimeType, MimeTypes.AUDIO_OPUS));
if (processOutputBuffer(
positionUs,
elapsedRealtimeUs,
@ -2308,7 +2304,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
/* bufferFlags= */ 0,
bypassBatchBuffer.getSampleCount(),
bypassBatchBuffer.getFirstSampleTimeUs(),
isDecodeOnly,
isDecodeOnly(getLastResetPositionUs(), bypassBatchBuffer.getLastSampleTimeUs()),
bypassBatchBuffer.isEndOfStream(),
checkNotNull(outputFormat))) {
// The batch buffer has been fully processed.
@ -2405,9 +2401,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
bypassSampleBuffer.format = outputFormat;
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(
bypassSampleBuffer, checkNotNull(outputFormat).initializationData);
}
}
if (!haveBypassBatchBufferAndNewSampleSameDecodeOnlyState()
|| !bypassBatchBuffer.append(bypassSampleBuffer)) {
bypassSampleBufferPending = true;
@ -2428,11 +2429,31 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return true;
}
long lastResetPositionUs = getLastResetPositionUs();
boolean batchBufferIsDecodeOnly = bypassBatchBuffer.getLastSampleTimeUs() < lastResetPositionUs;
boolean sampleBufferIsDecodeOnly = bypassSampleBuffer.timeUs < lastResetPositionUs;
boolean batchBufferIsDecodeOnly =
isDecodeOnly(lastResetPositionUs, bypassBatchBuffer.getLastSampleTimeUs());
boolean sampleBufferIsDecodeOnly = isDecodeOnly(lastResetPositionUs, bypassSampleBuffer.timeUs);
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) {
if (Util.SDK_INT >= 21 && isMediaCodecExceptionV21(error)) {
return true;

View File

@ -149,6 +149,23 @@ public class OpusUtil {
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) {
// See RFC6716, Sections 3.1 and 3.2.
int toc = packetByte0 & 0xFF;

View File

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