blob: b496f00910941c28965d1c30396a4450351d45ee [file] [log] [blame]
magjed55220212017-06-02 02:45:56 -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 android.graphics.Matrix;
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020014import android.opengl.GLES11Ext;
15import android.opengl.GLES20;
magjed55220212017-06-02 02:45:56 -070016import java.nio.ByteBuffer;
Sami Kalliomäkie7592d82018-03-22 13:32:44 +010017import javax.annotation.Nullable;
magjed55220212017-06-02 02:45:56 -070018
19/**
20 * Java version of webrtc::VideoFrame and webrtc::VideoFrameBuffer. A difference from the C++
21 * version is that no explicit tag is used, and clients are expected to use 'instanceof' to find the
22 * right subclass of the buffer. This allows clients to create custom VideoFrame.Buffer in
23 * arbitrary format in their custom VideoSources, and then cast it back to the correct subclass in
24 * their custom VideoSinks. All implementations must also implement the toI420() function,
25 * converting from the underlying representation if necessary. I420 is the most widely accepted
26 * format and serves as a fallback for video sinks that can only handle I420, e.g. the internal
27 * WebRTC software encoders.
28 */
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020029public class VideoFrame implements RefCounted {
30 /**
31 * Implements image storage medium. Might be for example an OpenGL texture or a memory region
32 * containing I420-data.
33 *
34 * <p>Reference counting is needed since a video buffer can be shared between multiple VideoSinks,
35 * and the buffer needs to be returned to the VideoSource as soon as all references are gone.
36 */
37 public interface Buffer extends RefCounted {
magjed55220212017-06-02 02:45:56 -070038 /**
39 * Resolution of the buffer in pixels.
40 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010041 @CalledByNative("Buffer") int getWidth();
42 @CalledByNative("Buffer") int getHeight();
magjed55220212017-06-02 02:45:56 -070043
44 /**
45 * Returns a memory-backed frame in I420 format. If the pixel data is in another format, a
46 * conversion will take place. All implementations must provide a fallback to I420 for
47 * compatibility with e.g. the internal WebRTC software encoders.
48 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010049 @CalledByNative("Buffer") I420Buffer toI420();
magjed55220212017-06-02 02:45:56 -070050
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020051 @Override @CalledByNative("Buffer") void retain();
52 @Override @CalledByNative("Buffer") void release();
sakal836f60c2017-07-28 07:12:23 -070053
54 /**
55 * Crops a region defined by |cropx|, |cropY|, |cropWidth| and |cropHeight|. Scales it to size
56 * |scaleWidth| x |scaleHeight|.
57 */
Magnus Jedvert202be392017-11-18 16:09:17 +010058 @CalledByNative("Buffer")
sakal836f60c2017-07-28 07:12:23 -070059 Buffer cropAndScale(
60 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -070061 }
62
63 /**
64 * Interface for I420 buffers.
65 */
66 public interface I420Buffer extends Buffer {
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020067 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020068 * Returns a direct ByteBuffer containing Y-plane data. The buffer capacity is at least
69 * getStrideY() * getHeight() bytes. The position of the returned buffer is ignored and must
70 * be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
71 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020072 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010073 @CalledByNative("I420Buffer") ByteBuffer getDataY();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020074 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020075 * Returns a direct ByteBuffer containing U-plane data. The buffer capacity is at least
76 * getStrideU() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
77 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
78 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020079 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010080 @CalledByNative("I420Buffer") ByteBuffer getDataU();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020081 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020082 * Returns a direct ByteBuffer containing V-plane data. The buffer capacity is at least
83 * getStrideV() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
84 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
85 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020086 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010087 @CalledByNative("I420Buffer") ByteBuffer getDataV();
magjed55220212017-06-02 02:45:56 -070088
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010089 @CalledByNative("I420Buffer") int getStrideY();
90 @CalledByNative("I420Buffer") int getStrideU();
91 @CalledByNative("I420Buffer") int getStrideV();
magjed55220212017-06-02 02:45:56 -070092 }
93
94 /**
95 * Interface for buffers that are stored as a single texture, either in OES or RGB format.
96 */
97 public interface TextureBuffer extends Buffer {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020098 enum Type {
99 OES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES),
100 RGB(GLES20.GL_TEXTURE_2D);
101
102 private final int glTarget;
103
104 private Type(final int glTarget) {
105 this.glTarget = glTarget;
106 }
107
108 public int getGlTarget() {
109 return glTarget;
110 }
111 }
magjed55220212017-06-02 02:45:56 -0700112
113 Type getType();
114 int getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700115
116 /**
117 * Retrieve the transform matrix associated with the frame. This transform matrix maps 2D
118 * homogeneous coordinates of the form (s, t, 1) with s and t in the inclusive range [0, 1] to
119 * the coordinate that should be used to sample that location from the buffer.
120 */
121 public Matrix getTransformMatrix();
magjed55220212017-06-02 02:45:56 -0700122 }
123
124 private final Buffer buffer;
125 private final int rotation;
126 private final long timestampNs;
magjed55220212017-06-02 02:45:56 -0700127
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200128 /**
129 * Constructs a new VideoFrame backed by the given {@code buffer}.
130 *
131 * @note Ownership of the buffer object is tranferred to the new VideoFrame.
132 */
Magnus Jedvert1f2a3e72017-11-23 16:56:44 +0100133 @CalledByNative
sakal836f60c2017-07-28 07:12:23 -0700134 public VideoFrame(Buffer buffer, int rotation, long timestampNs) {
magjed55220212017-06-02 02:45:56 -0700135 if (buffer == null) {
136 throw new IllegalArgumentException("buffer not allowed to be null");
137 }
sakal6bdcefc2017-08-15 01:56:02 -0700138 if (rotation % 90 != 0) {
139 throw new IllegalArgumentException("rotation must be a multiple of 90");
140 }
magjed55220212017-06-02 02:45:56 -0700141 this.buffer = buffer;
142 this.rotation = rotation;
143 this.timestampNs = timestampNs;
magjed55220212017-06-02 02:45:56 -0700144 }
145
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100146 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700147 public Buffer getBuffer() {
148 return buffer;
149 }
150
151 /**
152 * Rotation of the frame in degrees.
153 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100154 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700155 public int getRotation() {
156 return rotation;
157 }
158
159 /**
160 * Timestamp of the frame in nano seconds.
161 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100162 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700163 public long getTimestampNs() {
164 return timestampNs;
165 }
166
sakal6bdcefc2017-08-15 01:56:02 -0700167 public int getRotatedWidth() {
168 if (rotation % 180 == 0) {
169 return buffer.getWidth();
170 }
171 return buffer.getHeight();
172 }
173
174 public int getRotatedHeight() {
175 if (rotation % 180 == 0) {
176 return buffer.getHeight();
177 }
178 return buffer.getWidth();
179 }
180
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200181 @Override
magjed55220212017-06-02 02:45:56 -0700182 public void retain() {
183 buffer.retain();
184 }
185
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200186 @Override
Sami Kalliomäki2bde8502018-02-15 13:58:15 +0100187 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700188 public void release() {
189 buffer.release();
190 }
sakal836f60c2017-07-28 07:12:23 -0700191
Sami Kalliomäki1641ca32018-04-04 15:59:31 +0200192 // TODO(sakal): This file should be strictly an interface. This method should be moved somewhere
193 // else.
sakal836f60c2017-07-28 07:12:23 -0700194 public static VideoFrame.Buffer cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY,
195 int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
196 if (cropWidth == scaleWidth && cropHeight == scaleHeight) {
197 // No scaling.
198 ByteBuffer dataY = buffer.getDataY();
199 ByteBuffer dataU = buffer.getDataU();
200 ByteBuffer dataV = buffer.getDataV();
201
202 dataY.position(cropX + cropY * buffer.getStrideY());
203 dataU.position(cropX / 2 + cropY / 2 * buffer.getStrideU());
204 dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV());
205
206 buffer.retain();
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200207 return JavaI420Buffer.wrap(buffer.getWidth(), buffer.getHeight(), dataY.slice(),
sakal836f60c2017-07-28 07:12:23 -0700208 buffer.getStrideY(), dataU.slice(), buffer.getStrideU(), dataV.slice(),
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200209 buffer.getStrideV(), buffer::release);
sakal836f60c2017-07-28 07:12:23 -0700210 }
211
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200212 JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight);
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100213 nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(),
sakal836f60c2017-07-28 07:12:23 -0700214 buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth,
215 cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(),
216 newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV(), scaleWidth,
217 scaleHeight);
218 return newBuffer;
219 }
220
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100221 private static native void nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY,
sakal836f60c2017-07-28 07:12:23 -0700222 ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY,
223 int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU,
224 int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -0700225}