diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 211337423c..9f770761a3 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -395,6 +395,7 @@ android_library("libjingle_peerconnection_java") { "api/org/webrtc/HardwareVideoDecoderFactory.java", "api/org/webrtc/HardwareVideoEncoderFactory.java", "api/org/webrtc/IceCandidate.java", + "api/org/webrtc/JavaI420Buffer.java", "api/org/webrtc/MediaCodecVideoDecoder.java", "api/org/webrtc/MediaCodecVideoEncoder.java", "api/org/webrtc/MediaConstraints.java", @@ -449,7 +450,6 @@ android_library("libjingle_peerconnection_java") { "src/java/org/webrtc/HardwareVideoDecoder.java", "src/java/org/webrtc/HardwareVideoEncoder.java", "src/java/org/webrtc/Histogram.java", - "src/java/org/webrtc/I420BufferImpl.java", "src/java/org/webrtc/JniCommon.java", "src/java/org/webrtc/MediaCodecUtils.java", "src/java/org/webrtc/NativeLibrary.java", diff --git a/sdk/android/src/java/org/webrtc/I420BufferImpl.java b/sdk/android/api/org/webrtc/JavaI420Buffer.java similarity index 61% rename from sdk/android/src/java/org/webrtc/I420BufferImpl.java rename to sdk/android/api/org/webrtc/JavaI420Buffer.java index 48efc257ae..13ed63bf18 100644 --- a/sdk/android/src/java/org/webrtc/I420BufferImpl.java +++ b/sdk/android/api/org/webrtc/JavaI420Buffer.java @@ -13,8 +13,8 @@ package org.webrtc; import java.nio.ByteBuffer; import org.webrtc.VideoFrame.I420Buffer; -/** Implementation of an I420 VideoFrame buffer. */ -class I420BufferImpl implements VideoFrame.I420Buffer { +/** Implementation of VideoFrame.I420Buffer backed by Java direct byte buffers. */ +public class JavaI420Buffer implements VideoFrame.I420Buffer { private final int width; private final int height; private final ByteBuffer dataY; @@ -28,8 +28,7 @@ class I420BufferImpl implements VideoFrame.I420Buffer { private int refCount; - /** Constructs an I420Buffer backed by existing data. */ - I420BufferImpl(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU, + private JavaI420Buffer(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) { this.width = width; this.height = height; @@ -44,8 +43,42 @@ class I420BufferImpl implements VideoFrame.I420Buffer { this.refCount = 1; } + /** Wraps existing ByteBuffers into JavaI420Buffer object without copying the contents. */ + public static JavaI420Buffer wrap(int width, int height, ByteBuffer dataY, int strideY, + ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) { + if (dataY == null || dataU == null || dataV == null) { + throw new IllegalArgumentException("Data buffers cannot be null."); + } + if (!dataY.isDirect() || !dataU.isDirect() || !dataV.isDirect()) { + throw new IllegalArgumentException("Data buffers must be direct byte buffers."); + } + + // Slice the buffers to prevent external modifications to the position / limit of the buffer. + // Note that this doesn't protect the contents of the buffers from modifications. + dataY = dataY.slice(); + dataU = dataU.slice(); + dataV = dataV.slice(); + + final int chromaHeight = (height + 1) / 2; + final int minCapacityY = strideY * height; + final int minCapacityU = strideU * chromaHeight; + final int minCapacityV = strideV * chromaHeight; + if (dataY.capacity() < minCapacityY) { + throw new IllegalArgumentException("Y-buffer must be at least " + minCapacityY + " bytes."); + } + if (dataU.capacity() < minCapacityU) { + throw new IllegalArgumentException("U-buffer must be at least " + minCapacityU + " bytes."); + } + if (dataV.capacity() < minCapacityV) { + throw new IllegalArgumentException("V-buffer must be at least " + minCapacityV + " bytes."); + } + + return new JavaI420Buffer( + width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback); + } + /** Allocates an empty I420Buffer suitable for an image of the given dimensions. */ - static I420BufferImpl allocate(int width, int height) { + public static JavaI420Buffer allocate(int width, int height) { int chromaHeight = (height + 1) / 2; int strideUV = (width + 1) / 2; int yPos = 0; @@ -66,7 +99,8 @@ class I420BufferImpl implements VideoFrame.I420Buffer { buffer.limit(vPos + strideUV * chromaHeight); ByteBuffer dataV = buffer.slice(); - return new I420BufferImpl(width, height, dataY, width, dataU, strideUV, dataV, strideUV, null); + return new JavaI420Buffer( + width, height, dataY, width, dataU, strideUV, dataV, strideUV, null /* releaseCallback */); } @Override diff --git a/sdk/android/api/org/webrtc/VideoFrame.java b/sdk/android/api/org/webrtc/VideoFrame.java index 6ff5412e4c..b49262b104 100644 --- a/sdk/android/api/org/webrtc/VideoFrame.java +++ b/sdk/android/api/org/webrtc/VideoFrame.java @@ -173,17 +173,12 @@ public class VideoFrame { dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV()); buffer.retain(); - return new I420BufferImpl(buffer.getWidth(), buffer.getHeight(), dataY.slice(), + return JavaI420Buffer.wrap(buffer.getWidth(), buffer.getHeight(), dataY.slice(), buffer.getStrideY(), dataU.slice(), buffer.getStrideU(), dataV.slice(), - buffer.getStrideV(), new Runnable() { - @Override - public void run() { - buffer.release(); - } - }); + buffer.getStrideV(), buffer::release); } - I420BufferImpl newBuffer = I420BufferImpl.allocate(scaleWidth, scaleHeight); + JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight); nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(), buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth, cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(), diff --git a/sdk/android/api/org/webrtc/VideoRenderer.java b/sdk/android/api/org/webrtc/VideoRenderer.java index e44578c084..182093c932 100644 --- a/sdk/android/api/org/webrtc/VideoRenderer.java +++ b/sdk/android/api/org/webrtc/VideoRenderer.java @@ -163,7 +163,7 @@ public class VideoRenderer { VideoRenderer.renderFrameDone(this); buffer = backingBuffer; } else if (yuvFrame) { - buffer = new I420BufferImpl(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], + buffer = JavaI420Buffer.wrap(width, height, yuvPlanes[0], yuvStrides[0], yuvPlanes[1], yuvStrides[1], yuvPlanes[2], yuvStrides[2], () -> { VideoRenderer.renderFrameDone(this); }); } else { diff --git a/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java b/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java index 49c7dd33ce..9b95b769f6 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/EglRendererTest.java @@ -37,33 +37,32 @@ import org.junit.runner.RunWith; // EmptyActivity is needed for the surface. @RunWith(BaseJUnit4ClassRunner.class) public class EglRendererTest { - final static String TAG = "EglRendererTest"; - final static int RENDER_WAIT_MS = 1000; - final static int SURFACE_WAIT_MS = 1000; - final static int TEST_FRAME_WIDTH = 4; - final static int TEST_FRAME_HEIGHT = 4; - final static int REMOVE_FRAME_LISTENER_RACY_NUM_TESTS = 10; + private final static String TAG = "EglRendererTest"; + private final static int RENDER_WAIT_MS = 1000; + private final static int SURFACE_WAIT_MS = 1000; + private final static int TEST_FRAME_WIDTH = 4; + private final static int TEST_FRAME_HEIGHT = 4; + private final static int REMOVE_FRAME_LISTENER_RACY_NUM_TESTS = 10; // Some arbitrary frames. - final static ByteBuffer[][] TEST_FRAMES = { + private final static byte[][][] TEST_FRAMES_DATA = { { - ByteBuffer.wrap(new byte[] { - 11, -12, 13, -14, -15, 16, -17, 18, 19, -110, 111, -112, -113, 114, -115, 116}), - ByteBuffer.wrap(new byte[] {117, 118, 119, 120}), - ByteBuffer.wrap(new byte[] {121, 122, 123, 124}), + new byte[] { + 11, -12, 13, -14, -15, 16, -17, 18, 19, -110, 111, -112, -113, 114, -115, 116}, + new byte[] {117, 118, 119, 120}, new byte[] {121, 122, 123, 124}, }, { - ByteBuffer.wrap(new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, - -113, -114, -115, -116}), - ByteBuffer.wrap(new byte[] {-121, -122, -123, -124}), - ByteBuffer.wrap(new byte[] {-117, -118, -119, -120}), + new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, -113, -114, + -115, -116}, + new byte[] {-121, -122, -123, -124}, new byte[] {-117, -118, -119, -120}, }, { - ByteBuffer.wrap(new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, - -113, -114, -115, -116}), - ByteBuffer.wrap(new byte[] {117, 118, 119, 120}), - ByteBuffer.wrap(new byte[] {121, 122, 123, 124}), + new byte[] {-11, -12, -13, -14, -15, -16, -17, -18, -19, -110, -111, -112, -113, -114, + -115, -116}, + new byte[] {117, 118, 119, 120}, new byte[] {121, 122, 123, 124}, }, }; + private final static ByteBuffer[][] TEST_FRAMES = + copyTestDataToDirectByteBuffers(TEST_FRAMES_DATA); private class TestFrameListener implements EglRenderer.FrameListener { final private ArrayList bitmaps = new ArrayList(); @@ -335,4 +334,18 @@ public class EglRendererTest { feedFrame(1); assertFalse(testFrameListener.waitForBitmap(RENDER_WAIT_MS)); } + + private static ByteBuffer[][] copyTestDataToDirectByteBuffers(byte[][][] testData) { + final ByteBuffer[][] result = new ByteBuffer[testData.length][]; + + for (int i = 0; i < testData.length; i++) { + result[i] = new ByteBuffer[testData[i].length]; + for (int j = 0; j < testData[i].length; j++) { + result[i][j] = ByteBuffer.allocateDirect(testData[i][j].length); + result[i][j].put(testData[i][j]); + result[i][j].rewind(); + } + } + return result; + } } diff --git a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java index 38d10e198f..58b8d44a26 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoDecoderTest.java @@ -122,7 +122,7 @@ public final class HardwareVideoDecoderTest { VideoCodecStatus.OK); // First, encode a frame. - VideoFrame.I420Buffer buffer = I420BufferImpl.allocate(SETTINGS.width, SETTINGS.height); + VideoFrame.I420Buffer buffer = JavaI420Buffer.allocate(SETTINGS.width, SETTINGS.height); VideoFrame frame = new VideoFrame(buffer, rotation, presentationTimestampUs * 1000); VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo( new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey}); @@ -197,7 +197,7 @@ public final class HardwareVideoDecoderTest { VideoCodecStatus.OK); // First, encode a frame. - VideoFrame.I420Buffer buffer = I420BufferImpl.allocate(SETTINGS.width, SETTINGS.height); + VideoFrame.I420Buffer buffer = JavaI420Buffer.allocate(SETTINGS.width, SETTINGS.height); VideoFrame frame = new VideoFrame(buffer, rotation, presentationTimestampUs * 1000); VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo( new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameKey}); diff --git a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java index 629c831e48..7e3c5d0c19 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/HardwareVideoEncoderTest.java @@ -179,7 +179,7 @@ public class HardwareVideoEncoderTest { @Override public VideoFrame.I420Buffer toI420() { - return I420BufferImpl.allocate(width, height); + return JavaI420Buffer.allocate(width, height); } @Override @@ -191,12 +191,12 @@ public class HardwareVideoEncoderTest { } private static class MockI420Buffer extends MockBufferBase implements VideoFrame.I420Buffer { - private final I420BufferImpl realBuffer; + private final JavaI420Buffer realBuffer; public MockI420Buffer(int width, int height, Runnable releaseCallback) { super(width, height, releaseCallback); // We never release this but it is not a problem in practice because the release is a no-op. - realBuffer = I420BufferImpl.allocate(width, height); + realBuffer = JavaI420Buffer.allocate(width, height); } @Override diff --git a/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java b/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java index 2330dd83d5..070ceeb821 100644 --- a/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java +++ b/sdk/android/src/java/org/webrtc/HardwareVideoDecoder.java @@ -535,7 +535,7 @@ class HardwareVideoDecoder final int vPos = uPos + uvStride * sliceHeight / 2; final int vEnd = vPos + uvStride * (sliceHeight / 2); - VideoFrame.I420Buffer frameBuffer = I420BufferImpl.allocate(width, height); + VideoFrame.I420Buffer frameBuffer = JavaI420Buffer.allocate(width, height); ByteBuffer dataY = frameBuffer.getDataY(); dataY.position(0); // Ensure we are in the beginning. @@ -605,7 +605,7 @@ class HardwareVideoDecoder buffer.limit(vEnd); ByteBuffer dataV = buffer.slice(); - return new I420BufferImpl( + return JavaI420Buffer.wrap( width, height, dataY, stride, dataU, uvStride, dataV, uvStride, releaseCallback); } diff --git a/sdk/android/src/java/org/webrtc/NV12Buffer.java b/sdk/android/src/java/org/webrtc/NV12Buffer.java index af9ac253f1..9c5df02562 100644 --- a/sdk/android/src/java/org/webrtc/NV12Buffer.java +++ b/sdk/android/src/java/org/webrtc/NV12Buffer.java @@ -69,7 +69,7 @@ public class NV12Buffer implements VideoFrame.Buffer { @Override public VideoFrame.Buffer cropAndScale( int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { - I420BufferImpl newBuffer = I420BufferImpl.allocate(scaleWidth, scaleHeight); + JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight); nativeCropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight, buffer, width, height, stride, sliceHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(), newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV()); diff --git a/sdk/android/src/java/org/webrtc/NV21Buffer.java b/sdk/android/src/java/org/webrtc/NV21Buffer.java index defcc1bcc1..3a528be6df 100644 --- a/sdk/android/src/java/org/webrtc/NV21Buffer.java +++ b/sdk/android/src/java/org/webrtc/NV21Buffer.java @@ -64,7 +64,7 @@ public class NV21Buffer implements VideoFrame.Buffer { @Override public VideoFrame.Buffer cropAndScale( int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { - I420BufferImpl newBuffer = I420BufferImpl.allocate(scaleWidth, scaleHeight); + JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight); nativeCropAndScale(cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight, data, width, height, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(), newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV()); diff --git a/sdk/android/src/java/org/webrtc/TextureBufferImpl.java b/sdk/android/src/java/org/webrtc/TextureBufferImpl.java index 3a7da8d45c..78a34a34d7 100644 --- a/sdk/android/src/java/org/webrtc/TextureBufferImpl.java +++ b/sdk/android/src/java/org/webrtc/TextureBufferImpl.java @@ -101,7 +101,7 @@ class TextureBufferImpl implements VideoFrame.TextureBuffer { ByteBuffer dataV = buffer.slice(); // SurfaceTextureHelper uses the same stride for Y, U, and V data. - return new I420BufferImpl(width, height, dataY, stride, dataU, stride, dataV, stride, null); + return JavaI420Buffer.wrap(width, height, dataY, stride, dataU, stride, dataV, stride, null); } @Override