Commit 4cd8c770 by hoangtc Committed by Oliver Woodman

Add layer of indirection for DRM.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=138383979
parent a6e27701
...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities; ...@@ -22,6 +22,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
/** /**
...@@ -71,7 +72,8 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -71,7 +72,8 @@ public final class FfmpegAudioRenderer extends SimpleDecoderAudioRenderer {
} }
@Override @Override
protected FfmpegDecoder createDecoder(Format format) throws FfmpegDecoderException { protected FfmpegDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
throws FfmpegDecoderException {
decoder = new FfmpegDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, decoder = new FfmpegDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
format.sampleMimeType, format.initializationData); format.sampleMimeType, format.initializationData);
return decoder; return decoder;
......
...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities; ...@@ -21,6 +21,7 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
/** /**
...@@ -63,7 +64,8 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -63,7 +64,8 @@ public class LibflacAudioRenderer extends SimpleDecoderAudioRenderer {
} }
@Override @Override
protected FlacDecoder createDecoder(Format format) throws FlacDecoderException { protected FlacDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
throws FlacDecoderException {
return new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.initializationData); return new FlacDecoder(NUM_BUFFERS, NUM_BUFFERS, format.initializationData);
} }
......
...@@ -21,6 +21,8 @@ import com.google.android.exoplayer2.audio.AudioCapabilities; ...@@ -21,6 +21,8 @@ import com.google.android.exoplayer2.audio.AudioCapabilities;
import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.audio.AudioTrack;
import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer; import com.google.android.exoplayer2.audio.SimpleDecoderAudioRenderer;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
/** /**
...@@ -57,6 +59,21 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -57,6 +59,21 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
super(eventHandler, eventListener, audioCapabilities, streamType); super(eventHandler, eventListener, audioCapabilities, streamType);
} }
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param streamType The type of audio stream for the {@link AudioTrack}.
*/
public LibopusAudioRenderer(Handler eventHandler, AudioRendererEventListener eventListener,
AudioCapabilities audioCapabilities, int streamType,
DrmSessionManager<ExoMediaCrypto> drmSessionManager, boolean playClearSamplesWithoutKeys) {
super(eventHandler, eventListener, audioCapabilities, streamType, drmSessionManager,
playClearSamplesWithoutKeys);
}
@Override @Override
public int supportsFormat(Format format) { public int supportsFormat(Format format) {
return OpusLibrary.isAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType) return OpusLibrary.isAvailable() && MimeTypes.AUDIO_OPUS.equalsIgnoreCase(format.sampleMimeType)
...@@ -64,9 +81,10 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer { ...@@ -64,9 +81,10 @@ public final class LibopusAudioRenderer extends SimpleDecoderAudioRenderer {
} }
@Override @Override
protected OpusDecoder createDecoder(Format format) throws OpusDecoderException { protected OpusDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto)
throws OpusDecoderException {
return new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE, return new OpusDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
format.initializationData); format.initializationData, mediaCrypto);
} }
} }
...@@ -16,9 +16,12 @@ ...@@ -16,9 +16,12 @@
package com.google.android.exoplayer2.ext.opus; package com.google.android.exoplayer2.ext.opus;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.drm.DecryptionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.List; import java.util.List;
...@@ -36,6 +39,12 @@ import java.util.List; ...@@ -36,6 +39,12 @@ import java.util.List;
*/ */
private static final int SAMPLE_RATE = 48000; private static final int SAMPLE_RATE = 48000;
private static final int NO_ERROR = 0;
private static final int DECODE_ERROR = -1;
private static final int DRM_ERROR = -2;
private final ExoMediaCrypto exoMediaCrypto;
private final int channelCount; private final int channelCount;
private final int headerSkipSamples; private final int headerSkipSamples;
private final int headerSeekPreRollSamples; private final int headerSeekPreRollSamples;
...@@ -52,14 +61,20 @@ import java.util.List; ...@@ -52,14 +61,20 @@ import java.util.List;
* @param initializationData Codec-specific initialization data. The first element must contain an * @param initializationData Codec-specific initialization data. The first element must contain an
* opus header. Optionally, the list may contain two additional buffers, which must contain * opus header. Optionally, the list may contain two additional buffers, which must contain
* the encoder delay and seek pre roll values in nanoseconds, encoded as longs. * the encoder delay and seek pre roll values in nanoseconds, encoded as longs.
* @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted
* content. Maybe null and can be ignored if decoder does not handle encrypted content.
* @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder. * @throws OpusDecoderException Thrown if an exception occurs when initializing the decoder.
*/ */
public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize, public OpusDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
List<byte[]> initializationData) throws OpusDecoderException { List<byte[]> initializationData, ExoMediaCrypto exoMediaCrypto) throws OpusDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]); super(new DecoderInputBuffer[numInputBuffers], new SimpleOutputBuffer[numOutputBuffers]);
if (!OpusLibrary.isAvailable()) { if (!OpusLibrary.isAvailable()) {
throw new OpusDecoderException("Failed to load decoder native libraries."); throw new OpusDecoderException("Failed to load decoder native libraries.");
} }
this.exoMediaCrypto = exoMediaCrypto;
if (exoMediaCrypto != null && !OpusLibrary.opusIsSecureDecodeSupported()) {
throw new OpusDecoderException("Opus decoder does not support secure decode.");
}
byte[] headerBytes = initializationData.get(0); byte[] headerBytes = initializationData.get(0);
if (headerBytes.length < 19) { if (headerBytes.length < 19) {
throw new OpusDecoderException("Header size is too small."); throw new OpusDecoderException("Header size is too small.");
...@@ -139,11 +154,25 @@ import java.util.List; ...@@ -139,11 +154,25 @@ import java.util.List;
skipSamples = (inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples; skipSamples = (inputBuffer.timeUs == 0) ? headerSkipSamples : headerSeekPreRollSamples;
} }
ByteBuffer inputData = inputBuffer.data; ByteBuffer inputData = inputBuffer.data;
int result = opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(), CryptoInfo cryptoInfo = inputBuffer.cryptoInfo;
outputBuffer, SAMPLE_RATE); int result = inputBuffer.isEncrypted()
? opusSecureDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(),
outputBuffer, SAMPLE_RATE, exoMediaCrypto, cryptoInfo.mode,
cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples,
cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData)
: opusDecode(nativeDecoderContext, inputBuffer.timeUs, inputData, inputData.limit(),
outputBuffer, SAMPLE_RATE);
if (result < 0) { if (result < 0) {
return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result)); if (result == DRM_ERROR) {
String message = "Drm error: " + opusGetErrorMessage(nativeDecoderContext);
DecryptionException cause = new DecryptionException(
opusGetErrorCode(nativeDecoderContext), message);
return new OpusDecoderException(message, cause);
} else {
return new OpusDecoderException("Decode error: " + opusGetErrorMessage(result));
}
} }
ByteBuffer outputData = outputBuffer.data; ByteBuffer outputData = outputBuffer.data;
outputData.position(0); outputData.position(0);
outputData.limit(result); outputData.limit(result);
...@@ -182,8 +211,13 @@ import java.util.List; ...@@ -182,8 +211,13 @@ import java.util.List;
int gain, byte[] streamMap); int gain, byte[] streamMap);
private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize, private native int opusDecode(long decoder, long timeUs, ByteBuffer inputBuffer, int inputSize,
SimpleOutputBuffer outputBuffer, int sampleRate); SimpleOutputBuffer outputBuffer, int sampleRate);
private native int opusSecureDecode(long decoder, long timeUs, ByteBuffer inputBuffer,
int inputSize, SimpleOutputBuffer outputBuffer, int sampleRate,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native void opusClose(long decoder); private native void opusClose(long decoder);
private native void opusReset(long decoder); private native void opusReset(long decoder);
private native String opusGetErrorMessage(int errorCode); private native int opusGetErrorCode(long decoder);
private native String opusGetErrorMessage(long decoder);
} }
...@@ -26,4 +26,8 @@ public final class OpusDecoderException extends AudioDecoderException { ...@@ -26,4 +26,8 @@ public final class OpusDecoderException extends AudioDecoderException {
super(message); super(message);
} }
/* package */ OpusDecoderException(String message, Throwable cause) {
super(message, cause);
}
} }
...@@ -50,5 +50,5 @@ public final class OpusLibrary { ...@@ -50,5 +50,5 @@ public final class OpusLibrary {
} }
public static native String opusGetVersion(); public static native String opusGetVersion();
public static native boolean opusIsSecureDecodeSupported();
} }
...@@ -60,11 +60,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -60,11 +60,13 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples. static const int kBytesPerSample = 2; // opus fixed point uses 16 bit samples.
static int channelCount; static int channelCount;
static int errorCode;
DECODER_FUNC(jlong, opusInit, jint sampleRate, jint channelCount, DECODER_FUNC(jlong, opusInit, jint sampleRate, jint channelCount,
jint numStreams, jint numCoupled, jint gain, jbyteArray jStreamMap) { jint numStreams, jint numCoupled, jint gain, jbyteArray jStreamMap) {
int status = OPUS_INVALID_STATE; int status = OPUS_INVALID_STATE;
::channelCount = channelCount; ::channelCount = channelCount;
errorCode = 0;
jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0); jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0);
uint8_t* streamMap = reinterpret_cast<uint8_t*>(streamMapBytes); uint8_t* streamMap = reinterpret_cast<uint8_t*>(streamMapBytes);
OpusMSDecoder* decoder = opus_multistream_decoder_create( OpusMSDecoder* decoder = opus_multistream_decoder_create(
...@@ -109,10 +111,24 @@ DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs, ...@@ -109,10 +111,24 @@ DECODER_FUNC(jint, opusDecode, jlong jDecoder, jlong jTimeUs,
env->GetDirectBufferAddress(jOutputBufferData)); env->GetDirectBufferAddress(jOutputBufferData));
int sampleCount = opus_multistream_decode(decoder, inputBuffer, inputSize, int sampleCount = opus_multistream_decode(decoder, inputBuffer, inputSize,
outputBufferData, outputSize, 0); outputBufferData, outputSize, 0);
// record error code
errorCode = (sampleCount < 0) ? sampleCount : 0;
return (sampleCount < 0) ? sampleCount return (sampleCount < 0) ? sampleCount
: sampleCount * kBytesPerSample * channelCount; : sampleCount * kBytesPerSample * channelCount;
} }
DECODER_FUNC(jint, opusSecureDecode, jlong jDecoder, jlong jTimeUs,
jobject jInputBuffer, jint inputSize, jobject jOutputBuffer,
jint sampleRate, jobject mediaCrypto, jint inputMode, jbyteArray key,
jbyteArray javaIv, jint inputNumSubSamples, jintArray numBytesOfClearData,
jintArray numBytesOfEncryptedData) {
// Doesn't support
// Java client should have checked vpxSupportSecureDecode
// and avoid calling this
// return -2 (DRM Error)
return -2;
}
DECODER_FUNC(void, opusClose, jlong jDecoder) { DECODER_FUNC(void, opusClose, jlong jDecoder) {
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder); OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
opus_multistream_decoder_destroy(decoder); opus_multistream_decoder_destroy(decoder);
...@@ -123,10 +139,19 @@ DECODER_FUNC(void, opusReset, jlong jDecoder) { ...@@ -123,10 +139,19 @@ DECODER_FUNC(void, opusReset, jlong jDecoder) {
opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE); opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
} }
DECODER_FUNC(jstring, opusGetErrorMessage, jint errorCode) { DECODER_FUNC(jstring, opusGetErrorMessage, jlong jContext) {
return env->NewStringUTF(opus_strerror(errorCode)); return env->NewStringUTF(opus_strerror(errorCode));
} }
DECODER_FUNC(jint, opusGetErrorCode, jlong jContext) {
return errorCode;
}
LIBRARY_FUNC(jstring, opusIsSecureDecodeSupported) {
// Doesn't support
return 0;
}
LIBRARY_FUNC(jstring, opusGetVersion) { LIBRARY_FUNC(jstring, opusGetVersion) {
return env->NewStringUTF(opus_get_version_string()); return env->NewStringUTF(opus_get_version_string());
} }
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.vp9; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.ext.vp9;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.Surface; import android.view.Surface;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
...@@ -28,8 +29,12 @@ import com.google.android.exoplayer2.Format; ...@@ -28,8 +29,12 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderCounters; import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener; import com.google.android.exoplayer2.video.VideoRendererEventListener;
import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher; import com.google.android.exoplayer2.video.VideoRendererEventListener.EventDispatcher;
...@@ -56,8 +61,10 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -56,8 +61,10 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private final boolean scaleToFit; private final boolean scaleToFit;
private final long allowedJoiningTimeMs; private final long allowedJoiningTimeMs;
private final int maxDroppedFramesToNotify; private final int maxDroppedFramesToNotify;
private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private DecoderCounters decoderCounters; private DecoderCounters decoderCounters;
private Format format; private Format format;
...@@ -65,6 +72,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -65,6 +72,8 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private DecoderInputBuffer inputBuffer; private DecoderInputBuffer inputBuffer;
private VpxOutputBuffer outputBuffer; private VpxOutputBuffer outputBuffer;
private VpxOutputBuffer nextOutputBuffer; private VpxOutputBuffer nextOutputBuffer;
private DrmSession<ExoMediaCrypto> drmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession;
private Bitmap bitmap; private Bitmap bitmap;
private boolean renderedFirstFrame; private boolean renderedFirstFrame;
...@@ -72,6 +81,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -72,6 +81,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private Surface surface; private Surface surface;
private VpxOutputBufferRenderer outputBufferRenderer; private VpxOutputBufferRenderer outputBufferRenderer;
private int outputMode; private int outputMode;
private boolean waitingForKeys;
private boolean inputStreamEnded; private boolean inputStreamEnded;
private boolean outputStreamEnded; private boolean outputStreamEnded;
...@@ -104,10 +114,37 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -104,10 +114,37 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
public LibvpxVideoRenderer(boolean scaleToFit, long allowedJoiningTimeMs, public LibvpxVideoRenderer(boolean scaleToFit, long allowedJoiningTimeMs,
Handler eventHandler, VideoRendererEventListener eventListener, Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify) { int maxDroppedFramesToNotify) {
this(scaleToFit, allowedJoiningTimeMs, eventHandler, eventListener, maxDroppedFramesToNotify,
null, false);
}
/**
* @param scaleToFit Whether video frames should be scaled to fit when rendering.
* @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer
* can attempt to seamlessly join an ongoing playback.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between
* invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}.
* @param drmSessionManager For use with encrypted media. May be null if support for encrypted
* media is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
*/
public LibvpxVideoRenderer(boolean scaleToFit, long allowedJoiningTimeMs,
Handler eventHandler, VideoRendererEventListener eventListener,
int maxDroppedFramesToNotify, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(C.TRACK_TYPE_VIDEO); super(C.TRACK_TYPE_VIDEO);
this.scaleToFit = scaleToFit; this.scaleToFit = scaleToFit;
this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.allowedJoiningTimeMs = allowedJoiningTimeMs;
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
joiningDeadlineMs = -1; joiningDeadlineMs = -1;
previousWidth = -1; previousWidth = -1;
previousHeight = -1; previousHeight = -1;
...@@ -135,12 +172,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -135,12 +172,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
if (isRendererAvailable()) { if (isRendererAvailable()) {
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
} else {
// The drm session isn't open yet.
return;
}
}
try { try {
if (decoder == null) { if (decoder == null) {
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
long codecInitializingTimestamp = SystemClock.elapsedRealtime(); long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createVpxDecoder"); TraceUtil.beginSection("createVpxDecoder");
decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE); decoder = new VpxDecoder(NUM_BUFFERS, NUM_BUFFERS, INITIAL_INPUT_BUFFER_SIZE,
mediaCrypto);
decoder.setOutputMode(outputMode); decoder.setOutputMode(outputMode);
TraceUtil.endSection(); TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime(); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
...@@ -258,7 +310,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -258,7 +310,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
surface.unlockCanvasAndPost(canvas); surface.unlockCanvasAndPost(canvas);
} }
private boolean feedInputBuffer() throws VpxDecoderException { private boolean feedInputBuffer() throws VpxDecoderException, ExoPlaybackException {
if (inputStreamEnded) { if (inputStreamEnded) {
return false; return false;
} }
...@@ -270,7 +322,14 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -270,7 +322,14 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
} }
int result = readSource(formatHolder, inputBuffer); int result;
if (waitingForKeys) {
// We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ;
} else {
result = readSource(formatHolder, inputBuffer);
}
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {
return false; return false;
} }
...@@ -284,6 +343,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -284,6 +343,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
inputBuffer = null; inputBuffer = null;
return false; return false;
} }
boolean bufferEncrypted = inputBuffer.isEncrypted();
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
if (waitingForKeys) {
return false;
}
inputBuffer.flip(); inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer);
decoderCounters.inputBufferCount++; decoderCounters.inputBufferCount++;
...@@ -291,8 +355,21 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -291,8 +355,21 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return true; return true;
} }
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
if (drmSession == null) {
return false;
}
int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
}
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
}
private void flushDecoder() { private void flushDecoder() {
inputBuffer = null; inputBuffer = null;
waitingForKeys = false;
if (outputBuffer != null) { if (outputBuffer != null) {
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
...@@ -311,6 +388,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -311,6 +388,9 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
@Override @Override
public boolean isReady() { public boolean isReady() {
if (waitingForKeys) {
return false;
}
if (format != null && (isSourceReady() || outputBuffer != null) if (format != null && (isSourceReady() || outputBuffer != null)
&& (renderedFirstFrame || !isRendererAvailable())) { && (renderedFirstFrame || !isRendererAvailable())) {
// Ready. If we were joining then we've now joined, so clear the joining deadline. // Ready. If we were joining then we've now joined, so clear the joining deadline.
...@@ -365,11 +445,26 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -365,11 +445,26 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
inputBuffer = null; inputBuffer = null;
outputBuffer = null; outputBuffer = null;
format = null; format = null;
waitingForKeys = false;
try { try {
releaseDecoder(); releaseDecoder();
} finally { } finally {
decoderCounters.ensureUpdated(); try {
eventDispatcher.disabled(decoderCounters); if (drmSession != null) {
drmSessionManager.releaseSession(drmSession);
}
} finally {
try {
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} finally {
drmSession = null;
pendingDrmSession = null;
decoderCounters.ensureUpdated();
eventDispatcher.disabled(decoderCounters);
}
}
} }
} }
...@@ -378,10 +473,18 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -378,10 +473,18 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
decoder.release(); decoder.release();
decoder = null; decoder = null;
decoderCounters.decoderReleaseCount++; decoderCounters.decoderReleaseCount++;
waitingForKeys = false;
if (drmSession != null && pendingDrmSession != drmSession) {
try {
drmSessionManager.releaseSession(drmSession);
} finally {
drmSession = null;
}
}
} }
} }
private boolean readFormat() { private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null); int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format); onInputFormatChanged(formatHolder.format);
...@@ -390,8 +493,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -390,8 +493,27 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false; return false;
} }
private void onInputFormatChanged(Format newFormat) { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = format;
format = newFormat; format = newFormat;
boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null
: oldFormat.drmInitData);
if (drmInitDataChanged) {
if (format.drmInitData != null) {
if (drmSessionManager == null) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
}
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData);
if (pendingDrmSession == drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} else {
pendingDrmSession = null;
}
}
eventDispatcher.inputFormatChanged(format); eventDispatcher.inputFormatChanged(format);
} }
......
...@@ -16,8 +16,11 @@ ...@@ -16,8 +16,11 @@
package com.google.android.exoplayer2.ext.vp9; package com.google.android.exoplayer2.ext.vp9;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.decoder.CryptoInfo;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.drm.DecryptionException;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
...@@ -30,6 +33,11 @@ import java.nio.ByteBuffer; ...@@ -30,6 +33,11 @@ import java.nio.ByteBuffer;
public static final int OUTPUT_MODE_YUV = 0; public static final int OUTPUT_MODE_YUV = 0;
public static final int OUTPUT_MODE_RGB = 1; public static final int OUTPUT_MODE_RGB = 1;
private static final int NO_ERROR = 0;
private static final int DECODE_ERROR = 1;
private static final int DRM_ERROR = 2;
private final ExoMediaCrypto exoMediaCrypto;
private final long vpxDecContext; private final long vpxDecContext;
private volatile int outputMode; private volatile int outputMode;
...@@ -40,14 +48,20 @@ import java.nio.ByteBuffer; ...@@ -40,14 +48,20 @@ import java.nio.ByteBuffer;
* @param numInputBuffers The number of input buffers. * @param numInputBuffers The number of input buffers.
* @param numOutputBuffers The number of output buffers. * @param numOutputBuffers The number of output buffers.
* @param initialInputBufferSize The initial size of each input buffer. * @param initialInputBufferSize The initial size of each input buffer.
* @param exoMediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted
* content. Maybe null and can be ignored if decoder does not handle encrypted content.
* @throws VpxDecoderException Thrown if an exception occurs when initializing the decoder. * @throws VpxDecoderException Thrown if an exception occurs when initializing the decoder.
*/ */
public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize) public VpxDecoder(int numInputBuffers, int numOutputBuffers, int initialInputBufferSize,
throws VpxDecoderException { ExoMediaCrypto exoMediaCrypto) throws VpxDecoderException {
super(new DecoderInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]); super(new DecoderInputBuffer[numInputBuffers], new VpxOutputBuffer[numOutputBuffers]);
if (!VpxLibrary.isAvailable()) { if (!VpxLibrary.isAvailable()) {
throw new VpxDecoderException("Failed to load decoder native libraries."); throw new VpxDecoderException("Failed to load decoder native libraries.");
} }
this.exoMediaCrypto = exoMediaCrypto;
if (exoMediaCrypto != null && !VpxLibrary.vpxIsSecureDecodeSupported()) {
throw new VpxDecoderException("Vpx decoder does not support secure decode.");
}
vpxDecContext = vpxInit(); vpxDecContext = vpxInit();
if (vpxDecContext == 0) { if (vpxDecContext == 0) {
throw new VpxDecoderException("Failed to initialize decoder"); throw new VpxDecoderException("Failed to initialize decoder");
...@@ -90,9 +104,23 @@ import java.nio.ByteBuffer; ...@@ -90,9 +104,23 @@ import java.nio.ByteBuffer;
boolean reset) { boolean reset) {
ByteBuffer inputData = inputBuffer.data; ByteBuffer inputData = inputBuffer.data;
int inputSize = inputData.limit(); int inputSize = inputData.limit();
if (vpxDecode(vpxDecContext, inputData, inputSize) != 0) { CryptoInfo cryptoInfo = inputBuffer.cryptoInfo;
return new VpxDecoderException("Decode error: " + vpxGetErrorMessage(vpxDecContext)); final long result = inputBuffer.isEncrypted()
? vpxSecureDecode(vpxDecContext, inputData, inputSize, exoMediaCrypto,
cryptoInfo.mode, cryptoInfo.key, cryptoInfo.iv, cryptoInfo.numSubSamples,
cryptoInfo.numBytesOfClearData, cryptoInfo.numBytesOfEncryptedData)
: vpxDecode(vpxDecContext, inputData, inputSize);
if (result != NO_ERROR) {
if (result == DRM_ERROR) {
String message = "Drm error: " + vpxGetErrorMessage(vpxDecContext);
DecryptionException cause = new DecryptionException(
vpxGetErrorCode(vpxDecContext), message);
return new VpxDecoderException(message, cause);
} else {
return new VpxDecoderException("Decode error: " + vpxGetErrorMessage(vpxDecContext));
}
} }
outputBuffer.init(inputBuffer.timeUs, outputMode); outputBuffer.init(inputBuffer.timeUs, outputMode);
if (vpxGetFrame(vpxDecContext, outputBuffer) != 0) { if (vpxGetFrame(vpxDecContext, outputBuffer) != 0) {
outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY);
...@@ -109,7 +137,11 @@ import java.nio.ByteBuffer; ...@@ -109,7 +137,11 @@ import java.nio.ByteBuffer;
private native long vpxInit(); private native long vpxInit();
private native long vpxClose(long context); private native long vpxClose(long context);
private native long vpxDecode(long context, ByteBuffer encoded, int length); private native long vpxDecode(long context, ByteBuffer encoded, int length);
private native long vpxSecureDecode(long context, ByteBuffer encoded, int length,
ExoMediaCrypto wvCrypto, int inputMode, byte[] key, byte[] iv,
int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData);
private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer); private native int vpxGetFrame(long context, VpxOutputBuffer outputBuffer);
private native int vpxGetErrorCode(long context);
private native String vpxGetErrorMessage(long context); private native String vpxGetErrorMessage(long context);
} }
...@@ -20,8 +20,11 @@ package com.google.android.exoplayer2.ext.vp9; ...@@ -20,8 +20,11 @@ package com.google.android.exoplayer2.ext.vp9;
*/ */
public class VpxDecoderException extends Exception { public class VpxDecoderException extends Exception {
/* package */ VpxDecoderException(String message) { /* package */ VpxDecoderException(String message) {
super(message); super(message);
} }
/* package */ VpxDecoderException(String message, Throwable cause) {
super(message, cause);
}
} }
...@@ -59,5 +59,5 @@ public final class VpxLibrary { ...@@ -59,5 +59,5 @@ public final class VpxLibrary {
private static native String vpxGetVersion(); private static native String vpxGetVersion();
private static native String vpxGetBuildConfig(); private static native String vpxGetBuildConfig();
public static native boolean vpxIsSecureDecodeSupported();
} }
...@@ -59,6 +59,7 @@ static jmethodID initForRgbFrame; ...@@ -59,6 +59,7 @@ static jmethodID initForRgbFrame;
static jmethodID initForYuvFrame; static jmethodID initForYuvFrame;
static jfieldID dataField; static jfieldID dataField;
static jfieldID outputModeField; static jfieldID outputModeField;
static int errorCode;
jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env; JNIEnv* env;
...@@ -72,6 +73,7 @@ DECODER_FUNC(jlong, vpxInit) { ...@@ -72,6 +73,7 @@ DECODER_FUNC(jlong, vpxInit) {
vpx_codec_ctx_t* context = new vpx_codec_ctx_t(); vpx_codec_ctx_t* context = new vpx_codec_ctx_t();
vpx_codec_dec_cfg_t cfg = {0, 0, 0}; vpx_codec_dec_cfg_t cfg = {0, 0, 0};
cfg.threads = android_getCpuCount(); cfg.threads = android_getCpuCount();
errorCode = 0;
if (vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, &cfg, 0)) { if (vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, &cfg, 0)) {
LOGE("ERROR: Fail to initialize libvpx decoder."); LOGE("ERROR: Fail to initialize libvpx decoder.");
return 0; return 0;
...@@ -97,13 +99,26 @@ DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) { ...@@ -97,13 +99,26 @@ DECODER_FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded)); reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
const vpx_codec_err_t status = const vpx_codec_err_t status =
vpx_codec_decode(context, buffer, len, NULL, 0); vpx_codec_decode(context, buffer, len, NULL, 0);
errorCode = 0;
if (status != VPX_CODEC_OK) { if (status != VPX_CODEC_OK) {
LOGE("ERROR: vpx_codec_decode() failed, status= %d", status); LOGE("ERROR: vpx_codec_decode() failed, status= %d", status);
errorCode = status;
return -1; return -1;
} }
return 0; return 0;
} }
DECODER_FUNC(jlong, vpxSecureDecode, jlong jContext, jobject encoded, jint len,
jobject mediaCrypto, jint inputMode, jbyteArray&, jbyteArray&,
jint inputNumSubSamples, jintArray numBytesOfClearData,
jintArray numBytesOfEncryptedData) {
// Doesn't support
// Java client should have checked vpxSupportSecureDecode
// and avoid calling this
// return -2 (DRM Error)
return -2;
}
DECODER_FUNC(jlong, vpxClose, jlong jContext) { DECODER_FUNC(jlong, vpxClose, jlong jContext) {
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext); vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
vpx_codec_destroy(context); vpx_codec_destroy(context);
...@@ -181,6 +196,15 @@ DECODER_FUNC(jstring, vpxGetErrorMessage, jlong jContext) { ...@@ -181,6 +196,15 @@ DECODER_FUNC(jstring, vpxGetErrorMessage, jlong jContext) {
return env->NewStringUTF(vpx_codec_error(context)); return env->NewStringUTF(vpx_codec_error(context));
} }
DECODER_FUNC(jint, vpxGetErrorCode, jlong jContext) {
return errorCode;
}
LIBRARY_FUNC(jstring, vpxIsSecureDecodeSupported) {
// Doesn't support
return 0;
}
LIBRARY_FUNC(jstring, vpxGetVersion) { LIBRARY_FUNC(jstring, vpxGetVersion) {
return env->NewStringUTF(vpx_codec_version_str()); return env->NewStringUTF(vpx_codec_version_str());
} }
......
...@@ -27,4 +27,15 @@ public abstract class AudioDecoderException extends Exception { ...@@ -27,4 +27,15 @@ public abstract class AudioDecoderException extends Exception {
super(detailMessage); super(detailMessage);
} }
/**
* @param detailMessage The detail message for this exception.
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A <tt>null</tt> value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
*/
public AudioDecoderException(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
} }
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.audio;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.PlaybackParams; import android.media.PlaybackParams;
import android.os.Handler; import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -29,17 +30,24 @@ import com.google.android.exoplayer2.decoder.DecoderCounters; ...@@ -29,17 +30,24 @@ import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.decoder.SimpleDecoder; import com.google.android.exoplayer2.decoder.SimpleDecoder;
import com.google.android.exoplayer2.decoder.SimpleOutputBuffer; import com.google.android.exoplayer2.decoder.SimpleOutputBuffer;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.util.MediaClock; import com.google.android.exoplayer2.util.MediaClock;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.TraceUtil;
import com.google.android.exoplayer2.util.Util;
/** /**
* Decodes and renders audio using a {@link SimpleDecoder}. * Decodes and renders audio using a {@link SimpleDecoder}.
*/ */
public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock { public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock {
private final boolean playClearSamplesWithoutKeys;
private final EventDispatcher eventDispatcher; private final EventDispatcher eventDispatcher;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final DrmSessionManager<ExoMediaCrypto> drmSessionManager;
private DecoderCounters decoderCounters; private DecoderCounters decoderCounters;
private Format inputFormat; private Format inputFormat;
...@@ -47,11 +55,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -47,11 +55,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
? extends AudioDecoderException> decoder; ? extends AudioDecoderException> decoder;
private DecoderInputBuffer inputBuffer; private DecoderInputBuffer inputBuffer;
private SimpleOutputBuffer outputBuffer; private SimpleOutputBuffer outputBuffer;
private DrmSession<ExoMediaCrypto> drmSession;
private DrmSession<ExoMediaCrypto> pendingDrmSession;
private long currentPositionUs; private long currentPositionUs;
private boolean allowPositionDiscontinuity; private boolean allowPositionDiscontinuity;
private boolean inputStreamEnded; private boolean inputStreamEnded;
private boolean outputStreamEnded; private boolean outputStreamEnded;
private boolean waitingForKeys;
private final AudioTrack audioTrack; private final AudioTrack audioTrack;
private int audioSessionId; private int audioSessionId;
...@@ -70,7 +81,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -70,7 +81,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
*/ */
public SimpleDecoderAudioRenderer(Handler eventHandler, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener) { AudioRendererEventListener eventListener) {
this (eventHandler, eventListener, null, AudioManager.STREAM_MUSIC); this(eventHandler, eventListener, null, AudioManager.STREAM_MUSIC);
} }
/** /**
...@@ -84,7 +95,31 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -84,7 +95,31 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
public SimpleDecoderAudioRenderer(Handler eventHandler, public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
int streamType) { int streamType) {
this(eventHandler, eventListener, audioCapabilities, streamType, null, false);
}
/**
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
* @param audioCapabilities The audio capabilities for playback on this device. May be null if the
* default capabilities (no encoded audio passthrough support) should be assumed.
* @param streamType The type of audio stream for the {@link AudioTrack}.
* @param drmSessionManager For use with encrypted media. May be null if support for encrypted
* media is not required.
* @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions.
* For example a media file may start with a short clear region so as to allow playback to
* begin in parallel with key acquisition. This parameter specifies whether the renderer is
* permitted to play clear regions of encrypted media files before {@code drmSessionManager}
* has obtained the keys necessary to decrypt encrypted regions of the media.
*/
public SimpleDecoderAudioRenderer(Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities,
int streamType, DrmSessionManager<ExoMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys) {
super(C.TRACK_TYPE_AUDIO); super(C.TRACK_TYPE_AUDIO);
this.drmSessionManager = drmSessionManager;
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
eventDispatcher = new EventDispatcher(eventHandler, eventListener); eventDispatcher = new EventDispatcher(eventHandler, eventListener);
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrack = new AudioTrack(audioCapabilities, streamType); audioTrack = new AudioTrack(audioCapabilities, streamType);
...@@ -108,12 +143,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -108,12 +143,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return; return;
} }
drmSession = pendingDrmSession;
ExoMediaCrypto mediaCrypto = null;
if (drmSession != null) {
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
} else if (drmSessionState == DrmSession.STATE_OPENED
|| drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) {
mediaCrypto = drmSession.getMediaCrypto();
} else {
// The drm session isn't open yet.
return;
}
}
// If we don't have a decoder yet, we need to instantiate one. // If we don't have a decoder yet, we need to instantiate one.
if (decoder == null) { if (decoder == null) {
try { try {
long codecInitializingTimestamp = SystemClock.elapsedRealtime(); long codecInitializingTimestamp = SystemClock.elapsedRealtime();
TraceUtil.beginSection("createAudioDecoder"); TraceUtil.beginSection("createAudioDecoder");
decoder = createDecoder(inputFormat); decoder = createDecoder(inputFormat, mediaCrypto);
TraceUtil.endSection(); TraceUtil.endSection();
long codecInitializedTimestamp = SystemClock.elapsedRealtime(); long codecInitializedTimestamp = SystemClock.elapsedRealtime();
eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp,
...@@ -141,11 +190,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -141,11 +190,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
* Creates a decoder for the given format. * Creates a decoder for the given format.
* *
* @param format The format for which a decoder is required. * @param format The format for which a decoder is required.
* @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content.
* Maybe null and can be ignored if decoder does not handle encrypted content.
* @return The decoder. * @return The decoder.
* @throws AudioDecoderException If an error occurred creating a suitable decoder. * @throws AudioDecoderException If an error occurred creating a suitable decoder.
*/ */
protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer, protected abstract SimpleDecoder<DecoderInputBuffer, ? extends SimpleOutputBuffer,
? extends AudioDecoderException> createDecoder(Format format) throws AudioDecoderException; ? extends AudioDecoderException> createDecoder(Format format, ExoMediaCrypto mediaCrypto)
throws AudioDecoderException;
/** /**
* Returns the format of audio buffers output by the decoder. Will not be called until the first * Returns the format of audio buffers output by the decoder. Will not be called until the first
...@@ -228,7 +280,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -228,7 +280,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return false; return false;
} }
private boolean feedInputBuffer() throws AudioDecoderException { private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException {
if (inputStreamEnded) { if (inputStreamEnded) {
return false; return false;
} }
...@@ -240,7 +292,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -240,7 +292,14 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
} }
int result = readSource(formatHolder, inputBuffer); int result;
if (waitingForKeys) {
// We've already read an encrypted sample into buffer, and are waiting for keys.
result = C.RESULT_BUFFER_READ;
} else {
result = readSource(formatHolder, inputBuffer);
}
if (result == C.RESULT_NOTHING_READ) { if (result == C.RESULT_NOTHING_READ) {
return false; return false;
} }
...@@ -254,6 +313,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -254,6 +313,11 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
inputBuffer = null; inputBuffer = null;
return false; return false;
} }
boolean bufferEncrypted = inputBuffer.isEncrypted();
waitingForKeys = shouldWaitForKeys(bufferEncrypted);
if (waitingForKeys) {
return false;
}
inputBuffer.flip(); inputBuffer.flip();
decoder.queueInputBuffer(inputBuffer); decoder.queueInputBuffer(inputBuffer);
decoderCounters.inputBufferCount++; decoderCounters.inputBufferCount++;
...@@ -261,8 +325,21 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -261,8 +325,21 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return true; return true;
} }
private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException {
if (drmSession == null) {
return false;
}
@DrmSession.State int drmSessionState = drmSession.getState();
if (drmSessionState == DrmSession.STATE_ERROR) {
throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex());
}
return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS
&& (bufferEncrypted || !playClearSamplesWithoutKeys);
}
private void flushDecoder() { private void flushDecoder() {
inputBuffer = null; inputBuffer = null;
waitingForKeys = false;
if (outputBuffer != null) { if (outputBuffer != null) {
outputBuffer.release(); outputBuffer.release();
outputBuffer = null; outputBuffer = null;
...@@ -278,7 +355,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -278,7 +355,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override @Override
public boolean isReady() { public boolean isReady() {
return audioTrack.hasPendingData() return audioTrack.hasPendingData()
|| (inputFormat != null && (isSourceReady() || outputBuffer != null)); || (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null));
} }
@Override @Override
...@@ -339,6 +416,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -339,6 +416,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
outputBuffer = null; outputBuffer = null;
inputFormat = null; inputFormat = null;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET; audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
waitingForKeys = false;
try { try {
if (decoder != null) { if (decoder != null) {
decoder.release(); decoder.release();
...@@ -347,12 +425,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -347,12 +425,26 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
audioTrack.release(); audioTrack.release();
} finally { } finally {
decoderCounters.ensureUpdated(); try {
eventDispatcher.disabled(decoderCounters); if (drmSession != null) {
drmSessionManager.releaseSession(drmSession);
}
} finally {
try {
if (pendingDrmSession != null && pendingDrmSession != drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} finally {
drmSession = null;
pendingDrmSession = null;
decoderCounters.ensureUpdated();
eventDispatcher.disabled(decoderCounters);
}
}
} }
} }
private boolean readFormat() { private boolean readFormat() throws ExoPlaybackException {
int result = readSource(formatHolder, null); int result = readSource(formatHolder, null);
if (result == C.RESULT_FORMAT_READ) { if (result == C.RESULT_FORMAT_READ) {
onInputFormatChanged(formatHolder.format); onInputFormatChanged(formatHolder.format);
...@@ -361,8 +453,28 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -361,8 +453,28 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
return false; return false;
} }
private void onInputFormatChanged(Format newFormat) { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
Format oldFormat = inputFormat;
inputFormat = newFormat; inputFormat = newFormat;
boolean drmInitDataChanged = !Util.areEqual(inputFormat.drmInitData, oldFormat == null ? null
: oldFormat.drmInitData);
if (drmInitDataChanged) {
if (inputFormat.drmInitData != null) {
if (drmSessionManager == null) {
throw ExoPlaybackException.createForRenderer(
new IllegalStateException("Media requires a DrmSessionManager"), getIndex());
}
pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(),
inputFormat.drmInitData);
if (pendingDrmSession == drmSession) {
drmSessionManager.releaseSession(pendingDrmSession);
}
} else {
pendingDrmSession = null;
}
}
eventDispatcher.inputFormatChanged(newFormat); eventDispatcher.inputFormatChanged(newFormat);
} }
......
package com.google.android.exoplayer2.drm;
/**
* An exception when doing drm decryption using the In-App Drm
*/
public class DecryptionException extends Exception {
private final int errorCode;
public DecryptionException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
/**
* Get error code
*/
public int getErrorCode() {
return errorCode;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment