Support tunneling in video renderer

At this point the renderers all have TODOs in enable(),
and turning on tunneling is reduced to the problem of
propagating a tunneling ID to these points.

Issue: #1688

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=144619393
This commit is contained in:
olly 2017-01-16 03:29:56 -08:00 committed by Oliver Woodman
parent 5742c87755
commit 60a3eda1e0
2 changed files with 83 additions and 12 deletions

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.MediaCodec;
@ -550,4 +552,13 @@ public final class C {
return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000);
}
/**
* Returns a newly generated {@link android.media.AudioTrack} session identifier.
*/
@TargetApi(21)
public static int generateAudioSessionIdV21(Context context) {
return ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE))
.generateAudioSessionId();
}
}

View File

@ -19,6 +19,7 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
@ -28,6 +29,7 @@ import android.view.Surface;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
@ -83,6 +85,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private int lastReportedUnappliedRotationDegrees;
private float lastReportedPixelWidthHeightRatio;
private boolean tunneling;
private int tunnelingAudioSessionId;
/* package */ OnFrameRenderedListenerV23 tunnelingOnFrameRenderedListener;
/**
* @param context A context.
* @param mediaCodecSelector A decoder selector.
@ -204,6 +210,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected void onEnabled(boolean joining) throws ExoPlaybackException {
super.onEnabled(joining);
// TODO: Allow this to be set.
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
tunneling = tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET;
eventDispatcher.enabled(decoderCounters);
frameReleaseTimeHelper.enable();
}
@ -217,7 +226,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
super.onPositionReset(positionUs, joining);
renderedFirstFrame = false;
clearRenderedFirstFrame();
consecutiveDroppedFrameCount = 0;
joiningDeadlineMs = joining && allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
@ -264,6 +273,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
pendingPixelWidthHeightRatio = Format.NO_VALUE;
clearLastReportedVideoSize();
frameReleaseTimeHelper.disable();
tunnelingOnFrameRenderedListener = null;
try {
super.onDisabled();
} finally {
@ -288,11 +298,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
private void setSurface(Surface surface) throws ExoPlaybackException {
// Clear state so that we always call the event listener with the video size and when a frame
// is rendered, even if the surface hasn't changed.
renderedFirstFrame = false;
clearLastReportedVideoSize();
// We only need to actually release and reinitialize the codec if the surface has changed.
// We only need to release and reinitialize the codec if the surface has changed.
if (this.surface != surface) {
this.surface = surface;
int state = getState();
@ -301,6 +307,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeInitCodec();
}
}
// Clear state so that we always call the event listener with the video size and when a frame
// is rendered, even if the surface hasn't changed.
clearRenderedFirstFrame();
clearLastReportedVideoSize();
}
@Override
@ -311,8 +321,12 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@Override
protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) {
codecMaxValues = getCodecMaxValues(format, streamFormats);
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround);
MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround,
tunnelingAudioSessionId);
codec.configure(mediaFormat, surface, crypto, 0);
if (Util.SDK_INT >= 23 && tunneling) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
}
}
@Override
@ -329,6 +343,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
pendingRotationDegrees = getRotationDegrees(newFormat);
}
@Override
protected void onQueueInputBuffer(DecoderInputBuffer buffer) {
if (Util.SDK_INT < 23 && tunneling) {
maybeNotifyRenderedFirstFrame();
}
}
@Override
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
@ -479,10 +500,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
TraceUtil.endSection();
decoderCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0;
if (!renderedFirstFrame) {
renderedFirstFrame = true;
eventDispatcher.renderedFirstFrame(surface);
}
maybeNotifyRenderedFirstFrame();
}
@TargetApi(21)
@ -493,6 +511,25 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
TraceUtil.endSection();
decoderCounters.renderedOutputBufferCount++;
consecutiveDroppedFrameCount = 0;
maybeNotifyRenderedFirstFrame();
}
private void clearRenderedFirstFrame() {
renderedFirstFrame = false;
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
// non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and
// OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and
// above.
if (Util.SDK_INT >= 23 && tunneling) {
MediaCodec codec = getCodec();
// If codec is null then the listener will be instantiated in configureCodec.
if (codec != null) {
tunnelingOnFrameRenderedListener = new OnFrameRenderedListenerV23(codec);
}
}
}
/* package */ void maybeNotifyRenderedFirstFrame() {
if (!renderedFirstFrame) {
renderedFirstFrame = true;
eventDispatcher.renderedFirstFrame(surface);
@ -531,7 +568,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
@SuppressLint("InlinedApi")
private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues,
boolean deviceNeedsAutoFrcWorkaround) {
boolean deviceNeedsAutoFrcWorkaround, int tunnelingAudioSessionId) {
MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16();
// Set the maximum adaptive video dimensions.
frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width);
@ -544,6 +581,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
if (deviceNeedsAutoFrcWorkaround) {
frameworkMediaFormat.setInteger("auto-frc", 0);
}
// Configure tunneling if enabled.
if (tunnelingAudioSessionId != C.AUDIO_SESSION_ID_UNSET) {
frameworkMediaFormat.setFeatureEnabled(CodecCapabilities.FEATURE_TunneledPlayback, true);
frameworkMediaFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, tunnelingAudioSessionId);
}
return frameworkMediaFormat;
}
@ -682,4 +724,22 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
@TargetApi(23)
private final class OnFrameRenderedListenerV23 implements MediaCodec.OnFrameRenderedListener {
private OnFrameRenderedListenerV23(MediaCodec codec) {
codec.setOnFrameRenderedListener(this, new Handler());
}
@Override
public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
if (this != tunnelingOnFrameRenderedListener) {
// Stale event.
return;
}
maybeNotifyRenderedFirstFrame();
}
}
}