diff --git a/demo/build.gradle b/demo/build.gradle
index a91c1ab2ca..c8db67dba5 100644
--- a/demo/build.gradle
+++ b/demo/build.gradle
@@ -19,7 +19,7 @@ android {
defaultConfig {
minSdkVersion 16
- targetSdkVersion 19
+ targetSdkVersion 21
}
buildTypes {
release {
diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml
index 8812dbc014..42075d9d8c 100644
--- a/demo/src/main/AndroidManifest.xml
+++ b/demo/src/main/AndroidManifest.xml
@@ -25,7 +25,7 @@
-
+
-
+
diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
index 8a75331465..cd4ed45292 100644
--- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
+++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java
@@ -563,12 +563,9 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected void onDisabled() {
+ super.onDisabled();
+ releaseAudioTrack();
audioSessionId = 0;
- try {
- releaseAudioTrack();
- } finally {
- super.onDisabled();
- }
}
@Override
@@ -620,42 +617,52 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer {
}
}
- // Copy {@code buffer} into {@code temporaryBuffer}.
- // TODO: Bypass this copy step on versions of Android where [redacted] is implemented.
- if (temporaryBuffer == null || temporaryBuffer.length < bufferInfo.size) {
- temporaryBuffer = new byte[bufferInfo.size];
- }
- buffer.position(bufferInfo.offset);
- buffer.get(temporaryBuffer, 0, bufferInfo.size);
- temporaryBufferOffset = 0;
temporaryBufferSize = bufferInfo.size;
+ buffer.position(bufferInfo.offset);
+ if (Util.SDK_INT < 21) {
+ // Copy {@code buffer} into {@code temporaryBuffer}.
+ if (temporaryBuffer == null || temporaryBuffer.length < bufferInfo.size) {
+ temporaryBuffer = new byte[bufferInfo.size];
+ }
+ buffer.get(temporaryBuffer, 0, bufferInfo.size);
+ temporaryBufferOffset = 0;
+ }
}
if (audioTrack == null) {
initAudioTrack();
}
- // TODO: Don't bother doing this once [redacted] is fixed.
- // Work out how many bytes we can write without the risk of blocking.
- int bytesPending = (int) (submittedBytes - getPlaybackHeadPosition() * frameSize);
- int bytesToWrite = bufferSize - bytesPending;
-
- if (bytesToWrite > 0) {
- bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite);
- audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite);
- temporaryBufferOffset += bytesToWrite;
- temporaryBufferSize -= bytesToWrite;
- submittedBytes += bytesToWrite;
- if (temporaryBufferSize == 0) {
- codec.releaseOutputBuffer(bufferIndex, false);
- codecCounters.renderedOutputBufferCount++;
- return true;
+ int bytesWritten = 0;
+ if (Util.SDK_INT < 21) {
+ // Work out how many bytes we can write without the risk of blocking.
+ int bytesPending = (int) (submittedBytes - getPlaybackHeadPosition() * frameSize);
+ int bytesToWrite = bufferSize - bytesPending;
+ if (bytesToWrite > 0) {
+ bytesToWrite = Math.min(temporaryBufferSize, bytesToWrite);
+ bytesWritten = audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite);
+ temporaryBufferOffset += bytesWritten;
}
+ } else {
+ bytesWritten = writeNonBlockingV21(audioTrack, buffer, temporaryBufferSize);
+ }
+
+ temporaryBufferSize -= bytesWritten;
+ submittedBytes += bytesWritten;
+ if (temporaryBufferSize == 0) {
+ codec.releaseOutputBuffer(bufferIndex, false);
+ codecCounters.renderedOutputBufferCount++;
+ return true;
}
return false;
}
+ @TargetApi(21)
+ private int writeNonBlockingV21(AudioTrack audioTrack, ByteBuffer buffer, int size) {
+ return audioTrack.write(buffer, size, AudioTrack.WRITE_NON_BLOCKING);
+ }
+
/**
* {@link AudioTrack#getPlaybackHeadPosition()} returns a value intended to be interpreted as
* an unsigned 32 bit integer, which also wraps around periodically. This method returns the
diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
index 565ab41723..3c9473585c 100644
--- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
+++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java
@@ -18,6 +18,7 @@ package com.google.android.exoplayer;
import com.google.android.exoplayer.drm.DrmSessionManager;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.TraceUtil;
+import com.google.android.exoplayer.util.Util;
import android.annotation.TargetApi;
import android.media.MediaCodec;
@@ -93,7 +94,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
private final int maxDroppedFrameCountToNotify;
private Surface surface;
- private boolean drawnToSurface;
+ private boolean reportedDrawnToSurface;
private boolean renderedFirstFrame;
private long joiningDeadlineUs;
private long droppedFrameAccumulationStartTimeMs;
@@ -270,7 +271,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
@Override
protected void onStopped() {
joiningDeadlineUs = -1;
- notifyAndResetDroppedFrameCount();
+ maybeNotifyDroppedFrameCount();
super.onStopped();
}
@@ -303,7 +304,7 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
return;
}
this.surface = surface;
- this.drawnToSurface = false;
+ this.reportedDrawnToSurface = false;
int state = getState();
if (state == TrackRenderer.STATE_ENABLED || state == TrackRenderer.STATE_STARTED) {
releaseCodec();
@@ -369,24 +370,37 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
}
if (!renderedFirstFrame) {
- renderOutputBuffer(codec, bufferIndex);
+ renderOutputBufferImmediate(codec, bufferIndex);
renderedFirstFrame = true;
return true;
}
if (getState() == TrackRenderer.STATE_STARTED && earlyUs < 30000) {
- if (earlyUs > 11000) {
- // We're a little too early to render the frame. Sleep until the frame can be rendered.
- // Note: The 11ms threshold was chosen fairly arbitrarily.
- try {
- // Subtracting 10000 rather than 11000 ensures that the sleep time will be at least 1ms.
- Thread.sleep((earlyUs - 10000) / 1000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
+ if (Util.SDK_INT >= 21) {
+ // Let the underlying framework time the release.
+ if (earlyUs < 50000) {
+ renderOutputBufferTimedV21(codec, bufferIndex, System.nanoTime() + (earlyUs * 1000L));
+ return true;
}
+ return false;
+ } else {
+ // We need to time the release ourselves.
+ if (earlyUs < 30000) {
+ if (earlyUs > 11000) {
+ // We're a little too early to render the frame. Sleep until the frame can be rendered.
+ // Note: The 11ms threshold was chosen fairly arbitrarily.
+ try {
+ // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms.
+ Thread.sleep((earlyUs - 10000) / 1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ renderOutputBufferImmediate(codec, bufferIndex);
+ return true;
+ }
+ return false;
}
- renderOutputBuffer(codec, bufferIndex);
- return true;
}
// We're either not playing, or it's not time to render the frame yet.
@@ -407,65 +421,84 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
codecCounters.droppedOutputBufferCount++;
droppedFrameCount++;
if (droppedFrameCount == maxDroppedFrameCountToNotify) {
- notifyAndResetDroppedFrameCount();
+ maybeNotifyDroppedFrameCount();
}
}
- private void renderOutputBuffer(MediaCodec codec, int bufferIndex) {
- if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight
- || lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) {
- lastReportedWidth = currentWidth;
- lastReportedHeight = currentHeight;
- lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
- notifyVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio);
- }
- TraceUtil.beginSection("renderVideoBuffer");
+ private void renderOutputBufferImmediate(MediaCodec codec, int bufferIndex) {
+ maybeNotifyVideoSizeChanged();
+ TraceUtil.beginSection("renderVideoBufferImmediate");
codec.releaseOutputBuffer(bufferIndex, true);
TraceUtil.endSection();
codecCounters.renderedOutputBufferCount++;
- if (!drawnToSurface) {
- drawnToSurface = true;
- notifyDrawnToSurface(surface);
- }
+ maybeNotifyDrawnToSurface();
}
- private void notifyVideoSizeChanged(final int width, final int height,
- final float pixelWidthHeightRatio) {
- if (eventHandler != null && eventListener != null) {
- eventHandler.post(new Runnable() {
- @Override
- public void run() {
- eventListener.onVideoSizeChanged(width, height, pixelWidthHeightRatio);
- }
- });
- }
+ @TargetApi(21)
+ private void renderOutputBufferTimedV21(MediaCodec codec, int bufferIndex, long nanoTime) {
+ maybeNotifyVideoSizeChanged();
+ TraceUtil.beginSection("releaseOutputBufferTimed");
+ codec.releaseOutputBuffer(bufferIndex, nanoTime);
+ TraceUtil.endSection();
+ codecCounters.renderedOutputBufferCount++;
+ maybeNotifyDrawnToSurface();
}
- private void notifyDrawnToSurface(final Surface surface) {
- if (eventHandler != null && eventListener != null) {
- eventHandler.post(new Runnable() {
- @Override
- public void run() {
- eventListener.onDrawnToSurface(surface);
- }
- });
+ private void maybeNotifyVideoSizeChanged() {
+ if (eventHandler == null || eventListener == null
+ || (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight
+ && lastReportedPixelWidthHeightRatio == currentPixelWidthHeightRatio)) {
+ return;
}
+ // Make final copies to ensure the runnable reports the correct values.
+ final int currentWidth = this.currentWidth;
+ final int currentHeight = this.currentHeight;
+ final float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio;
+ eventHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ eventListener.onVideoSizeChanged(currentWidth, currentHeight, currentPixelWidthHeightRatio);
+ }
+ });
+ // Update the last reported values.
+ lastReportedWidth = currentWidth;
+ lastReportedHeight = currentHeight;
+ lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio;
}
- private void notifyAndResetDroppedFrameCount() {
- if (eventHandler != null && eventListener != null && droppedFrameCount > 0) {
- long now = SystemClock.elapsedRealtime();
- final int countToNotify = droppedFrameCount;
- final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs;
- droppedFrameCount = 0;
- droppedFrameAccumulationStartTimeMs = now;
- eventHandler.post(new Runnable() {
- @Override
- public void run() {
- eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
- }
- });
+ private void maybeNotifyDrawnToSurface() {
+ if (eventHandler == null || eventListener == null || reportedDrawnToSurface) {
+ return;
}
+ // Make a final copy to ensure the runnable reports the correct surface.
+ final Surface surface = this.surface;
+ eventHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ eventListener.onDrawnToSurface(surface);
+ }
+ });
+ // Record that we have reported that the surface has been drawn to.
+ reportedDrawnToSurface = true;
+ }
+
+ private void maybeNotifyDroppedFrameCount() {
+ if (eventHandler == null || eventListener == null || droppedFrameCount == 0) {
+ return;
+ }
+ long now = SystemClock.elapsedRealtime();
+ // Make final copies to ensure the runnable reports the correct values.
+ final int countToNotify = droppedFrameCount;
+ final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs;
+ eventHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
+ }
+ });
+ // Reset the dropped frame tracking.
+ droppedFrameCount = 0;
+ droppedFrameAccumulationStartTimeMs = now;
}
}
diff --git a/library/src/main/project.properties b/library/src/main/project.properties
index 8e4bc5fdce..b756f4487f 100644
--- a/library/src/main/project.properties
+++ b/library/src/main/project.properties
@@ -8,5 +8,5 @@
# project structure.
# Project target.
-target=android-19
+target=android-21
android.library=true