Index: webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java |
diff --git a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java |
index b7aae2a33865e02f66e9a84f9c1a67bb54ed049d..ffa78ad6bbd09e65343f5d70f2428c77f3ea9fc4 100644 |
--- a/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java |
+++ b/webrtc/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java |
@@ -11,7 +11,6 @@ |
package org.webrtc; |
import android.annotation.TargetApi; |
-import android.graphics.Matrix; |
import android.media.MediaCodec; |
import android.media.MediaCodecInfo.CodecCapabilities; |
import android.media.MediaFormat; |
@@ -19,9 +18,7 @@ import android.os.SystemClock; |
import android.view.Surface; |
import java.io.IOException; |
import java.nio.ByteBuffer; |
-import java.util.Arrays; |
import java.util.Deque; |
-import java.util.concurrent.CountDownLatch; |
import java.util.concurrent.LinkedBlockingDeque; |
import org.webrtc.ThreadUtils.ThreadChecker; |
@@ -302,18 +299,22 @@ class HardwareVideoDecoder |
// TODO(sakal): This is not called on the correct thread but is still called synchronously. |
// Re-enable the check once this is called on the correct thread. |
// decoderThreadChecker.checkIsOnValidThread(); |
+ if (!running) { |
+ Logging.d(TAG, "release: Decoder is not running."); |
+ return VideoCodecStatus.OK; |
+ } |
try { |
// The outputThread actually stops and releases the codec once running is false. |
running = false; |
if (!ThreadUtils.joinUninterruptibly(outputThread, MEDIA_CODEC_RELEASE_TIMEOUT_MS)) { |
// Log an exception to capture the stack trace and turn it into a TIMEOUT error. |
- Logging.e(TAG, "Media encoder release timeout", new RuntimeException()); |
+ Logging.e(TAG, "Media decoder release timeout", new RuntimeException()); |
return VideoCodecStatus.TIMEOUT; |
} |
if (shutdownException != null) { |
// Log the exception and turn it into an error. Wrap the exception in a new exception to |
// capture both the output thread's stack trace and this thread's stack trace. |
- Logging.e(TAG, "Media encoder release error", new RuntimeException(shutdownException)); |
+ Logging.e(TAG, "Media decoder release error", new RuntimeException(shutdownException)); |
shutdownException = null; |
return VideoCodecStatus.ERROR; |
} |
@@ -453,26 +454,20 @@ class HardwareVideoDecoder |
ByteBuffer buffer = codec.getOutputBuffers()[result]; |
buffer.position(info.offset); |
- buffer.limit(info.size); |
- |
- final VideoFrame.I420Buffer frameBuffer; |
+ buffer.limit(info.offset + info.size); |
+ buffer = buffer.slice(); |
+ final VideoFrame.Buffer frameBuffer; |
- // TODO(mellem): As an optimization, use libyuv via JNI to copy/reformatting data. |
if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar) { |
if (sliceHeight % 2 == 0) { |
- frameBuffer = |
- createBufferFromI420(buffer, result, info.offset, stride, sliceHeight, width, height); |
+ frameBuffer = wrapI420Buffer(buffer, result, stride, sliceHeight, width, height); |
} else { |
- frameBuffer = I420BufferImpl.allocate(width, height); |
- // Optimal path is not possible because we have to copy the last rows of U- and V-planes. |
- copyI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, height); |
- codec.releaseOutputBuffer(result, false); |
+ // WebRTC rounds chroma plane size conversions up so we have to repeat the last row. |
+ frameBuffer = copyI420Buffer(buffer, result, stride, sliceHeight, width, height); |
} |
} else { |
- frameBuffer = I420BufferImpl.allocate(width, height); |
// All other supported color formats are NV12. |
- nv12ToI420(buffer, info.offset, frameBuffer, stride, sliceHeight, width, height); |
- codec.releaseOutputBuffer(result, false); |
+ frameBuffer = wrapNV12Buffer(buffer, result, stride, sliceHeight, width, height); |
} |
long presentationTimeNs = info.presentationTimeUs * 1000; |
@@ -483,6 +478,105 @@ class HardwareVideoDecoder |
frame.release(); |
} |
+ private VideoFrame.Buffer wrapNV12Buffer(ByteBuffer buffer, int outputBufferIndex, int stride, |
+ int sliceHeight, int width, int height) { |
+ synchronized (activeOutputBuffersLock) { |
+ activeOutputBuffers++; |
+ } |
+ |
+ return new NV12Buffer(width, height, stride, sliceHeight, buffer, () -> { |
+ codec.releaseOutputBuffer(outputBufferIndex, false); |
+ synchronized (activeOutputBuffersLock) { |
+ activeOutputBuffers--; |
+ activeOutputBuffersLock.notifyAll(); |
+ } |
+ }); |
+ } |
+ |
+ private VideoFrame.Buffer copyI420Buffer(ByteBuffer buffer, int outputBufferIndex, int stride, |
+ int sliceHeight, int width, int height) { |
+ final int uvStride = stride / 2; |
+ |
+ final int yPos = 0; |
+ final int uPos = yPos + stride * sliceHeight; |
+ final int uEnd = uPos + uvStride * (sliceHeight / 2); |
+ final int vPos = uPos + uvStride * sliceHeight / 2; |
+ final int vEnd = vPos + uvStride * (sliceHeight / 2); |
+ |
+ VideoFrame.I420Buffer frameBuffer = I420BufferImpl.allocate(width, height); |
+ |
+ ByteBuffer dataY = frameBuffer.getDataY(); |
+ dataY.position(0); // Ensure we are in the beginning. |
+ buffer.position(yPos); |
+ buffer.limit(uPos); |
+ dataY.put(buffer); |
+ dataY.position(0); // Go back to beginning. |
+ |
+ ByteBuffer dataU = frameBuffer.getDataU(); |
+ dataU.position(0); // Ensure we are in the beginning. |
+ buffer.position(uPos); |
+ buffer.limit(uEnd); |
+ dataU.put(buffer); |
+ if (sliceHeight % 2 != 0) { |
+ buffer.position(uEnd - uvStride); // Repeat the last row. |
+ dataU.put(buffer); |
+ } |
+ dataU.position(0); // Go back to beginning. |
+ |
+ ByteBuffer dataV = frameBuffer.getDataU(); |
+ dataV.position(0); // Ensure we are in the beginning. |
+ buffer.position(vPos); |
+ buffer.limit(vEnd); |
+ dataV.put(buffer); |
+ if (sliceHeight % 2 != 0) { |
+ buffer.position(vEnd - uvStride); // Repeat the last row. |
+ dataV.put(buffer); |
+ } |
+ dataV.position(0); // Go back to beginning. |
+ |
+ codec.releaseOutputBuffer(outputBufferIndex, false); |
+ |
+ return frameBuffer; |
+ } |
+ |
+ private VideoFrame.Buffer wrapI420Buffer(ByteBuffer buffer, int outputBufferIndex, int stride, |
+ int sliceHeight, int width, int height) { |
+ final int uvStride = stride / 2; |
+ |
+ final int yPos = 0; |
+ final int uPos = yPos + stride * sliceHeight; |
+ final int uEnd = uPos + uvStride * (sliceHeight / 2); |
+ final int vPos = uPos + uvStride * sliceHeight / 2; |
+ final int vEnd = vPos + uvStride * (sliceHeight / 2); |
+ |
+ synchronized (activeOutputBuffersLock) { |
+ activeOutputBuffers++; |
+ } |
+ |
+ Runnable releaseCallback = () -> { |
+ codec.releaseOutputBuffer(outputBufferIndex, false); |
+ synchronized (activeOutputBuffersLock) { |
+ activeOutputBuffers--; |
+ activeOutputBuffersLock.notifyAll(); |
+ } |
+ }; |
+ |
+ buffer.position(yPos); |
+ buffer.limit(uPos); |
+ ByteBuffer dataY = buffer.slice(); |
+ |
+ buffer.position(uPos); |
+ buffer.limit(uEnd); |
+ ByteBuffer dataU = buffer.slice(); |
+ |
+ buffer.position(vPos); |
+ buffer.limit(vEnd); |
+ ByteBuffer dataV = buffer.slice(); |
+ |
+ return new I420BufferImpl( |
+ width, height, dataY, stride, dataU, uvStride, dataV, uvStride, releaseCallback); |
+ } |
+ |
private void reformat(MediaFormat format) { |
outputThreadChecker.checkIsOnValidThread(); |
Logging.d(TAG, "Decoder format changed: " + format.toString()); |
@@ -588,116 +682,4 @@ class HardwareVideoDecoder |
} |
return false; |
} |
- |
- private VideoFrame.I420Buffer createBufferFromI420(final ByteBuffer buffer, |
- final int outputBufferIndex, final int offset, final int stride, final int sliceHeight, |
- final int width, final int height) { |
- final int uvStride = stride / 2; |
- final int chromaWidth = (width + 1) / 2; |
- final int chromaHeight = (height + 1) / 2; |
- |
- final int yPos = offset; |
- final int uPos = yPos + stride * sliceHeight; |
- final int vPos = uPos + uvStride * sliceHeight / 2; |
- |
- synchronized (activeOutputBuffersLock) { |
- activeOutputBuffers++; |
- } |
- |
- Runnable callback = new Runnable() { |
- @Override |
- public void run() { |
- codec.releaseOutputBuffer(outputBufferIndex, false); |
- synchronized (activeOutputBuffersLock) { |
- activeOutputBuffers--; |
- activeOutputBuffersLock.notifyAll(); |
- } |
- } |
- }; |
- |
- buffer.position(yPos); |
- buffer.limit(uPos); |
- ByteBuffer dataY = buffer.slice(); |
- |
- buffer.position(uPos); |
- buffer.limit(vPos); |
- ByteBuffer dataU = buffer.slice(); |
- |
- buffer.position(vPos); |
- buffer.limit(vPos + uvStride * sliceHeight / 2); |
- ByteBuffer dataV = buffer.slice(); |
- |
- return new I420BufferImpl( |
- width, height, dataY, stride, dataU, uvStride, dataV, uvStride, callback); |
- } |
- |
- private static void copyI420(ByteBuffer src, int offset, VideoFrame.I420Buffer frameBuffer, |
- int stride, int sliceHeight, int width, int height) { |
- int uvStride = stride / 2; |
- int chromaWidth = (width + 1) / 2; |
- // Note that hardware truncates instead of rounding. WebRTC expects rounding, so the last |
- // row will be duplicated if the sliceHeight is odd. |
- int chromaHeight = (sliceHeight % 2 == 0) ? (height + 1) / 2 : height / 2; |
- |
- int yPos = offset; |
- int uPos = yPos + stride * sliceHeight; |
- int vPos = uPos + uvStride * sliceHeight / 2; |
- |
- copyPlane( |
- src, yPos, stride, frameBuffer.getDataY(), 0, frameBuffer.getStrideY(), width, height); |
- copyPlane(src, uPos, uvStride, frameBuffer.getDataU(), 0, frameBuffer.getStrideU(), chromaWidth, |
- chromaHeight); |
- copyPlane(src, vPos, uvStride, frameBuffer.getDataV(), 0, frameBuffer.getStrideV(), chromaWidth, |
- chromaHeight); |
- |
- // If the sliceHeight is odd, duplicate the last rows of chroma. Copy the last row of the U and |
- // V channels and append them at the end of each channel. |
- if (sliceHeight % 2 != 0) { |
- int strideU = frameBuffer.getStrideU(); |
- int endU = chromaHeight * strideU; |
- copyRow(frameBuffer.getDataU(), endU - strideU, frameBuffer.getDataU(), endU, chromaWidth); |
- int strideV = frameBuffer.getStrideV(); |
- int endV = chromaHeight * strideV; |
- copyRow(frameBuffer.getDataV(), endV - strideV, frameBuffer.getDataV(), endV, chromaWidth); |
- } |
- } |
- |
- private static void nv12ToI420(ByteBuffer src, int offset, VideoFrame.I420Buffer frameBuffer, |
- int stride, int sliceHeight, int width, int height) { |
- int yPos = offset; |
- int uvPos = yPos + stride * sliceHeight; |
- int chromaWidth = (width + 1) / 2; |
- int chromaHeight = (height + 1) / 2; |
- |
- copyPlane( |
- src, yPos, stride, frameBuffer.getDataY(), 0, frameBuffer.getStrideY(), width, height); |
- |
- // Split U and V rows. |
- int dstUPos = 0; |
- int dstVPos = 0; |
- for (int i = 0; i < chromaHeight; ++i) { |
- for (int j = 0; j < chromaWidth; ++j) { |
- frameBuffer.getDataU().put(dstUPos + j, src.get(uvPos + j * 2)); |
- frameBuffer.getDataV().put(dstVPos + j, src.get(uvPos + j * 2 + 1)); |
- } |
- dstUPos += frameBuffer.getStrideU(); |
- dstVPos += frameBuffer.getStrideV(); |
- uvPos += stride; |
- } |
- } |
- |
- private static void copyPlane(ByteBuffer src, int srcPos, int srcStride, ByteBuffer dst, |
- int dstPos, int dstStride, int width, int height) { |
- for (int i = 0; i < height; ++i) { |
- copyRow(src, srcPos, dst, dstPos, width); |
- srcPos += srcStride; |
- dstPos += dstStride; |
- } |
- } |
- |
- private static void copyRow(ByteBuffer src, int srcPos, ByteBuffer dst, int dstPos, int width) { |
- for (int i = 0; i < width; ++i) { |
- dst.put(dstPos + i, src.get(srcPos + i)); |
- } |
- } |
} |