blob: 9bbd9b2d91ce3a8ebe0b4127dd718082eb0ddf78 [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
13import java.nio.ByteBuffer;
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020014import javax.annotation.Nullable;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -070015import 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,
53 ByteBuffer dataU, int strideU, ByteBuffer dataV, int strideV, Runnable releaseCallback) {
54 if (dataY == null || dataU == null || dataV == null) {
55 throw new IllegalArgumentException("Data buffers cannot be null.");
56 }
57 if (!dataY.isDirect() || !dataU.isDirect() || !dataV.isDirect()) {
58 throw new IllegalArgumentException("Data buffers must be direct byte buffers.");
59 }
60
61 // Slice the buffers to prevent external modifications to the position / limit of the buffer.
62 // Note that this doesn't protect the contents of the buffers from modifications.
63 dataY = dataY.slice();
64 dataU = dataU.slice();
65 dataV = dataV.slice();
66
Magnus Jedvert65070542018-06-14 12:23:01 +020067 final int chromaWidth = (width + 1) / 2;
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020068 final int chromaHeight = (height + 1) / 2;
Magnus Jedvert65070542018-06-14 12:23:01 +020069 checkCapacity(dataY, width, height, strideY);
70 checkCapacity(dataU, chromaWidth, chromaHeight, strideU);
71 checkCapacity(dataV, chromaWidth, chromaHeight, strideV);
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020072
73 return new JavaI420Buffer(
74 width, height, dataY, strideY, dataU, strideU, dataV, strideV, releaseCallback);
75 }
76
Bjorn Mellem8fb23612017-07-18 11:33:39 -070077 /** Allocates an empty I420Buffer suitable for an image of the given dimensions. */
Sami Kalliomäki48b3c022017-10-04 17:01:00 +020078 public static JavaI420Buffer allocate(int width, int height) {
Bjorn Mellem8fb23612017-07-18 11:33:39 -070079 int chromaHeight = (height + 1) / 2;
80 int strideUV = (width + 1) / 2;
81 int yPos = 0;
82 int uPos = yPos + width * height;
83 int vPos = uPos + strideUV * chromaHeight;
sakal836f60c2017-07-28 07:12:23 -070084
Sami Kalliomäki06110652018-02-20 11:19:58 +010085 ByteBuffer buffer =
86 JniCommon.nativeAllocateByteBuffer(width * height + 2 * strideUV * chromaHeight);
sakal836f60c2017-07-28 07:12:23 -070087
88 buffer.position(yPos);
89 buffer.limit(uPos);
90 ByteBuffer dataY = buffer.slice();
91
92 buffer.position(uPos);
93 buffer.limit(vPos);
94 ByteBuffer dataU = buffer.slice();
95
96 buffer.position(vPos);
97 buffer.limit(vPos + strideUV * chromaHeight);
98 ByteBuffer dataV = buffer.slice();
99
Sami Kalliomäki06110652018-02-20 11:19:58 +0100100 return new JavaI420Buffer(width, height, dataY, width, dataU, strideUV, dataV, strideUV,
101 () -> { JniCommon.nativeFreeByteBuffer(buffer); });
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700102 }
103
104 @Override
105 public int getWidth() {
106 return width;
107 }
108
109 @Override
110 public int getHeight() {
111 return height;
112 }
113
114 @Override
115 public ByteBuffer getDataY() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700116 // Return a slice to prevent relative reads from changing the position.
117 return dataY.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700118 }
119
120 @Override
121 public ByteBuffer getDataU() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700122 // Return a slice to prevent relative reads from changing the position.
123 return dataU.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700124 }
125
126 @Override
127 public ByteBuffer getDataV() {
Bjorn Mellemd6293142017-09-29 11:33:52 -0700128 // Return a slice to prevent relative reads from changing the position.
129 return dataV.slice();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700130 }
131
132 @Override
133 public int getStrideY() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700134 return strideY;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700135 }
136
137 @Override
138 public int getStrideU() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700139 return strideU;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700140 }
141
142 @Override
143 public int getStrideV() {
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700144 return strideV;
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700145 }
146
147 @Override
148 public I420Buffer toI420() {
sakal5ca60cc2017-08-09 05:25:49 -0700149 retain();
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700150 return this;
151 }
152
153 @Override
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700154 public void retain() {
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200155 refCountDelegate.retain();
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700156 }
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700157
158 @Override
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700159 public void release() {
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200160 refCountDelegate.release();
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700161 }
162
sakal836f60c2017-07-28 07:12:23 -0700163 @Override
164 public VideoFrame.Buffer cropAndScale(
165 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
Sami Kalliomäkiae1bcea2018-07-24 16:59:05 +0200166 return cropAndScaleI420(this, cropX, cropY, cropWidth, cropHeight, scaleWidth, scaleHeight);
Bjorn Mellem8fb23612017-07-18 11:33:39 -0700167 }
Sami Kalliomäkiae1bcea2018-07-24 16:59:05 +0200168
169 public static VideoFrame.Buffer cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY,
170 int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
171 if (cropWidth == scaleWidth && cropHeight == scaleHeight) {
172 // No scaling.
173 ByteBuffer dataY = buffer.getDataY();
174 ByteBuffer dataU = buffer.getDataU();
175 ByteBuffer dataV = buffer.getDataV();
176
177 dataY.position(cropX + cropY * buffer.getStrideY());
178 dataU.position(cropX / 2 + cropY / 2 * buffer.getStrideU());
179 dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV());
180
181 buffer.retain();
182 return JavaI420Buffer.wrap(scaleWidth, scaleHeight, dataY.slice(), buffer.getStrideY(),
183 dataU.slice(), buffer.getStrideU(), dataV.slice(), buffer.getStrideV(), buffer::release);
184 }
185
186 JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight);
187 nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(),
188 buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth,
189 cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(),
190 newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV(), scaleWidth,
191 scaleHeight);
192 return newBuffer;
193 }
194
195 private static native void nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY,
196 ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY,
197 int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU,
198 int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight);
Bjorn Mellem9fbbdc22017-06-16 09:23:50 -0700199}