blob: 322b8f38c9765305ea8e608a270bbb36dd546b23 [file] [log] [blame]
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -07001/*
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
11package org.webrtc;
12
Byoungchan Lee02334e02021-08-14 11:41:59 +090013import androidx.annotation.Nullable;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -070014import java.nio.ByteBuffer;
15import org.webrtc.VideoFrame.I420Buffer;
16
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020017/** Implementation of VideoFrame.I420Buffer backed by Java direct byte buffers. */
18public class JavaI420Buffer implements VideoFrame.I420Buffer {
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -070019 private final int width;
20 private final int height;
sakal836f60c2017-07-28 07:12:23 -070021 private final ByteBuffer dataY;
22 private final ByteBuffer dataU;
23 private final ByteBuffer dataV;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070024 private final int strideY;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070025 private final int strideU;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070026 private final int strideV;
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020027 private final RefCountDelegate refCountDelegate;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070028
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020029 private JavaI420Buffer(int width, int height, ByteBuffer dataY, int strideY, ByteBuffer dataU,
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020030 int strideU, ByteBuffer dataV, int strideV, @Nullable Runnable releaseCallback) {
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -070031 this.width = width;
32 this.height = height;
sakal836f60c2017-07-28 07:12:23 -070033 this.dataY = dataY;
34 this.dataU = dataU;
35 this.dataV = dataV;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070036 this.strideY = strideY;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070037 this.strideU = strideU;
Bjorn Mellem8fb23612017-07-18 11:33:39 -070038 this.strideV = strideV;
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020039 this.refCountDelegate = new RefCountDelegate(releaseCallback);
Bjorn Mellem8fb23612017-07-18 11:33:39 -070040 }
41
Magnus Jedvert65070542018-06-14 12:23:01 +020042 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
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020051 /** Wraps existing ByteBuffers into JavaI420Buffer object without copying the contents. */
52 public static JavaI420Buffer wrap(int width, int height, ByteBuffer dataY, int strideY,
Sami Kalliomäki15f0a122018-08-03 14:06:59 +020053 ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV,
54 @Nullable Runnable releaseCallback) {
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020055 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
Magnus Jedvert65070542018-06-14 12:23:01 +020068 final int chromaWidth = (width + 1) / 2;
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020069 final int chromaHeight = (height + 1) / 2;
Magnus Jedvert65070542018-06-14 12:23:01 +020070 checkCapacity(dataY, width, height, strideY);
71 checkCapacity(dataU, chromaWidth, chromaHeight, strideU);
72 checkCapacity(dataV, chromaWidth, chromaHeight, strideV);
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020073
74 return new JavaI420Buffer(
75 width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback);
76 }
77
Bjorn Mellem8fb23612017-07-18 11:33:39 -070078 /** Allocates an empty I420Buffer suitable for an image of the given dimensions. */
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020079 public static JavaI420Buffer allocate(int width, int height) {
Bjorn Mellem8fb23612017-07-18 11:33:39 -070080 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;
sakal836f60c2017-07-28 07:12:23 -070085
Sami Kalliomäki06110652018-02-20 11:19:58 +010086 ByteBuffer buffer =
87 JniCommon.nativeAllocateByteBuffer(width * height + 2 * strideUV * chromaHeight);
sakal836f60c2017-07-28 07:12:23 -070088
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
Sami Kalliomäki06110652018-02-20 11:19:58 +0100101 return new JavaI420Buffer(width, height, dataY, width, dataU, strideUV, dataV, strideUV,
102 () -> { JniCommon.nativeFreeByteBuffer(buffer); });
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700103 }
104
105 @Override
106 public int getWidth() {
107 return width;
108 }
109
110 @Override
111 public int getHeight() {
112 return height;
113 }
114
115 @Override
116 public ByteBuffer getDataY() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700117 // Return a slice to prevent relative reads from changing the position.
118 return dataY.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700119 }
120
121 @Override
122 public ByteBuffer getDataU() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700123 // Return a slice to prevent relative reads from changing the position.
124 return dataU.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700125 }
126
127 @Override
128 public ByteBuffer getDataV() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700129 // Return a slice to prevent relative reads from changing the position.
130 return dataV.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700131 }
132
133 @Override
134 public int getStrideY() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700135 return strideY;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700136 }
137
138 @Override
139 public int getStrideU() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700140 return strideU;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700141 }
142
143 @Override
144 public int getStrideV() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700145 return strideV;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700146 }
147
148 @Override
149 public I420Buffer toI420() {
sakal5ca60cc2017-08-09 05:25:49 -0700150 retain();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700151 return this;
152 }
153
154 @Override
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700155 public void retain() {
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200156 refCountDelegate.retain();
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700157 }
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700158
159 @Override
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700160 public void release() {
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200161 refCountDelegate.release();
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700162 }
163
sakal836f60c2017-07-28 07:12:23 -0700164 @Override
165 public VideoFrame.Buffer cropAndScale(
166 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
Sami Kalliomäkiae1bcea2018-07-24 16:59:05 +0200167 return cropAndScaleI420(this, cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700168 }
Sami Kalliomäkiae1bcea2018-07-24 16:59:05 +0200169
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
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);
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700200}