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