1 /* 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import android.support.annotation.Nullable; 14 import java.nio.ByteBuffer; 15 import org.webrtc.VideoFrame.I420Buffer; 16 17 /** Implementation of VideoFrame.I420Buffer backed by Java direct byte buffers. */ 18 public class JavaI420Buffer implements VideoFrame.I420Buffer { 19 private final int width; 20 private final int height; 21 private final ByteBuffer dataY; 22 private final ByteBuffer dataU; 23 private final ByteBuffer dataV; 24 private final int strideY; 25 private final int strideU; 26 private final int strideV; 27 private final RefCountDelegate refCountDelegate; 28 JavaI420Buffer(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, @Nullable Runnable releaseCallback)29 private JavaI420Buffer(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU, 30 int strideU, ByteBuffer dataV, int strideV, @Nullable Runnable releaseCallback) { 31 this.width = width; 32 this.height = height; 33 this.dataY = dataY; 34 this.dataU = dataU; 35 this.dataV = dataV; 36 this.strideY = strideY; 37 this.strideU = strideU; 38 this.strideV = strideV; 39 this.refCountDelegate = new RefCountDelegate(releaseCallback); 40 } 41 checkCapacity(ByteBuffer data, int width, int height, int stride)42 private static void checkCapacity(ByteBuffer data, int width, int height, int stride) { 43 // The last row does not necessarily need padding. 44 final int minCapacity = stride * (height - 1) + width; 45 if (data.capacity() < minCapacity) { 46 throw new IllegalArgumentException( 47 "Buffer must be at least " + minCapacity + " bytes, but was " + data.capacity()); 48 } 49 } 50 51 /** Wraps existing ByteBuffers into JavaI420Buffer object without copying the contents. */ wrap(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, @Nullable Runnable releaseCallback)52 public static JavaI420Buffer wrap(int width, int height, ByteBuffer dataY, int strideY, 53 ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, 54 @Nullable Runnable releaseCallback) { 55 if (dataY == null || dataU == null || dataV == null) { 56 throw new IllegalArgumentException("Data buffers cannot be null."); 57 } 58 if (!dataY.isDirect() || !dataU.isDirect() || !dataV.isDirect()) { 59 throw new IllegalArgumentException("Data buffers must be direct byte buffers."); 60 } 61 62 // Slice the buffers to prevent external modifications to the position / limit of the buffer. 63 // Note that this doesn't protect the contents of the buffers from modifications. 64 dataY = dataY.slice(); 65 dataU = dataU.slice(); 66 dataV = dataV.slice(); 67 68 final int chromaWidth = (width + 1) / 2; 69 final int chromaHeight = (height + 1) / 2; 70 checkCapacity(dataY, width, height, strideY); 71 checkCapacity(dataU, chromaWidth, chromaHeight, strideU); 72 checkCapacity(dataV, chromaWidth, chromaHeight, strideV); 73 74 return new JavaI420Buffer( 75 width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback); 76 } 77 78 /** Allocates an empty I420Buffer suitable for an image of the given dimensions. */ allocate(int width, int height)79 public static JavaI420Buffer allocate(int width, int height) { 80 int chromaHeight = (height + 1) / 2; 81 int strideUV = (width + 1) / 2; 82 int yPos = 0; 83 int uPos = yPos + width * height; 84 int vPos = uPos + strideUV * chromaHeight; 85 86 ByteBuffer buffer = 87 JniCommon.nativeAllocateByteBuffer(width * height + 2 * strideUV * chromaHeight); 88 89 buffer.position(yPos); 90 buffer.limit(uPos); 91 ByteBuffer dataY = buffer.slice(); 92 93 buffer.position(uPos); 94 buffer.limit(vPos); 95 ByteBuffer dataU = buffer.slice(); 96 97 buffer.position(vPos); 98 buffer.limit(vPos + strideUV * chromaHeight); 99 ByteBuffer dataV = buffer.slice(); 100 101 return new JavaI420Buffer(width, height, dataY, width, dataU, strideUV, dataV, strideUV, 102 () -> { JniCommon.nativeFreeByteBuffer(buffer); }); 103 } 104 105 @Override getWidth()106 public int getWidth() { 107 return width; 108 } 109 110 @Override getHeight()111 public int getHeight() { 112 return height; 113 } 114 115 @Override getDataY()116 public ByteBuffer getDataY() { 117 // Return a slice to prevent relative reads from changing the position. 118 return dataY.slice(); 119 } 120 121 @Override getDataU()122 public ByteBuffer getDataU() { 123 // Return a slice to prevent relative reads from changing the position. 124 return dataU.slice(); 125 } 126 127 @Override getDataV()128 public ByteBuffer getDataV() { 129 // Return a slice to prevent relative reads from changing the position. 130 return dataV.slice(); 131 } 132 133 @Override getStrideY()134 public int getStrideY() { 135 return strideY; 136 } 137 138 @Override getStrideU()139 public int getStrideU() { 140 return strideU; 141 } 142 143 @Override getStrideV()144 public int getStrideV() { 145 return strideV; 146 } 147 148 @Override toI420()149 public I420Buffer toI420() { 150 retain(); 151 return this; 152 } 153 154 @Override retain()155 public void retain() { 156 refCountDelegate.retain(); 157 } 158 159 @Override release()160 public void release() { 161 refCountDelegate.release(); 162 } 163 164 @Override cropAndScale( int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight)165 public VideoFrame.Buffer cropAndScale( 166 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { 167 return cropAndScaleI420(this, cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight); 168 } 169 cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight)170 public static VideoFrame.Buffer cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY, 171 int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) { 172 if (cropWidth == scaleWidth && cropHeight == scaleHeight) { 173 // No scaling. 174 ByteBuffer dataY = buffer.getDataY(); 175 ByteBuffer dataU = buffer.getDataU(); 176 ByteBuffer dataV = buffer.getDataV(); 177 178 dataY.position(cropX + cropY * buffer.getStrideY()); 179 dataU.position(cropX / 2 + cropY / 2 * buffer.getStrideU()); 180 dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV()); 181 182 buffer.retain(); 183 return JavaI420Buffer.wrap(scaleWidth, scaleHeight, dataY.slice(), buffer.getStrideY(), 184 dataU.slice(), buffer.getStrideU(), dataV.slice(), buffer.getStrideV(), buffer::release); 185 } 186 187 JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight); 188 nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(), 189 buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth, 190 cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(), 191 newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV(), scaleWidth, 192 scaleHeight); 193 return newBuffer; 194 } 195 nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY, ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY, int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU, int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight)196 private static native void nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY, 197 ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY, 198 int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU, 199 int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight); 200 } 201