blob: 91c596c01c32ccea3eca35068641e3c307562456 [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 */
Magnus Jedvert84d8ae52017-12-20 15:12:10 +010029@JNINamespace("webrtc::jni")
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020030public class VideoFrame implements RefCounted {
31 /**
32 * Implements image storage medium. Might be for example an OpenGL texture or a memory region
33 * containing I420-data.
34 *
35 * <p>Reference counting is needed since a video buffer can be shared between multiple VideoSinks,
36 * and the buffer needs to be returned to the VideoSource as soon as all references are gone.
37 */
38 public interface Buffer extends RefCounted {
magjed55220212017-06-02 02:45:56 -070039 /**
40 * Resolution of the buffer in pixels.
41 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010042 @CalledByNative("Buffer") int getWidth();
43 @CalledByNative("Buffer") int getHeight();
magjed55220212017-06-02 02:45:56 -070044
45 /**
46 * Returns a memory-backed frame in I420 format. If the pixel data is in another format, a
47 * conversion will take place. All implementations must provide a fallback to I420 for
48 * compatibility with e.g. the internal WebRTC software encoders.
49 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010050 @CalledByNative("Buffer") I420Buffer toI420();
magjed55220212017-06-02 02:45:56 -070051
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +020052 @Override @CalledByNative("Buffer") void retain();
53 @Override @CalledByNative("Buffer") void release();
sakal836f60c2017-07-28 07:12:23 -070054
55 /**
56 * Crops a region defined by |cropx|, |cropY|, |cropWidth| and |cropHeight|. Scales it to size
57 * |scaleWidth| x |scaleHeight|.
58 */
Magnus Jedvert202be392017-11-18 16:09:17 +010059 @CalledByNative("Buffer")
sakal836f60c2017-07-28 07:12:23 -070060 Buffer cropAndScale(
61 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -070062 }
63
64 /**
65 * Interface for I420 buffers.
66 */
67 public interface I420Buffer extends Buffer {
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020068 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020069 * Returns a direct ByteBuffer containing Y-plane data. The buffer capacity is at least
70 * getStrideY() * getHeight() bytes. The position of the returned buffer is ignored and must
71 * be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
72 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020073 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010074 @CalledByNative("I420Buffer") ByteBuffer getDataY();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020075 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020076 * Returns a direct ByteBuffer containing U-plane data. The buffer capacity is at least
77 * getStrideU() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
78 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
79 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020080 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010081 @CalledByNative("I420Buffer") ByteBuffer getDataU();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020082 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020083 * Returns a direct ByteBuffer containing V-plane data. The buffer capacity is at least
84 * getStrideV() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
85 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
86 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020087 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010088 @CalledByNative("I420Buffer") ByteBuffer getDataV();
magjed55220212017-06-02 02:45:56 -070089
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +010090 @CalledByNative("I420Buffer") int getStrideY();
91 @CalledByNative("I420Buffer") int getStrideU();
92 @CalledByNative("I420Buffer") int getStrideV();
magjed55220212017-06-02 02:45:56 -070093 }
94
95 /**
96 * Interface for buffers that are stored as a single texture, either in OES or RGB format.
97 */
98 public interface TextureBuffer extends Buffer {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020099 enum Type {
100 OES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES),
101 RGB(GLES20.GL_TEXTURE_2D);
102
103 private final int glTarget;
104
105 private Type(final int glTarget) {
106 this.glTarget = glTarget;
107 }
108
109 public int getGlTarget() {
110 return glTarget;
111 }
112 }
magjed55220212017-06-02 02:45:56 -0700113
114 Type getType();
115 int getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700116
117 /**
118 * Retrieve the transform matrix associated with the frame. This transform matrix maps 2D
119 * homogeneous coordinates of the form (s, t, 1) with s and t in the inclusive range [0, 1] to
120 * the coordinate that should be used to sample that location from the buffer.
121 */
122 public Matrix getTransformMatrix();
magjed55220212017-06-02 02:45:56 -0700123 }
124
125 private final Buffer buffer;
126 private final int rotation;
127 private final long timestampNs;
magjed55220212017-06-02 02:45:56 -0700128
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200129 /**
130 * Constructs a new VideoFrame backed by the given {@code buffer}.
131 *
132 * @note Ownership of the buffer object is tranferred to the new VideoFrame.
133 */
Magnus Jedvert1f2a3e72017-11-23 16:56:44 +0100134 @CalledByNative
sakal836f60c2017-07-28 07:12:23 -0700135 public VideoFrame(Buffer buffer, int rotation, long timestampNs) {
magjed55220212017-06-02 02:45:56 -0700136 if (buffer == null) {
137 throw new IllegalArgumentException("buffer not allowed to be null");
138 }
sakal6bdcefc2017-08-15 01:56:02 -0700139 if (rotation % 90 != 0) {
140 throw new IllegalArgumentException("rotation must be a multiple of 90");
141 }
magjed55220212017-06-02 02:45:56 -0700142 this.buffer = buffer;
143 this.rotation = rotation;
144 this.timestampNs = timestampNs;
magjed55220212017-06-02 02:45:56 -0700145 }
146
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100147 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700148 public Buffer getBuffer() {
149 return buffer;
150 }
151
152 /**
153 * Rotation of the frame in degrees.
154 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100155 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700156 public int getRotation() {
157 return rotation;
158 }
159
160 /**
161 * Timestamp of the frame in nano seconds.
162 */
Magnus Jedvertc2ac3c62017-11-14 17:08:59 +0100163 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700164 public long getTimestampNs() {
165 return timestampNs;
166 }
167
sakal6bdcefc2017-08-15 01:56:02 -0700168 public int getRotatedWidth() {
169 if (rotation % 180 == 0) {
170 return buffer.getWidth();
171 }
172 return buffer.getHeight();
173 }
174
175 public int getRotatedHeight() {
176 if (rotation % 180 == 0) {
177 return buffer.getHeight();
178 }
179 return buffer.getWidth();
180 }
181
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200182 @Override
magjed55220212017-06-02 02:45:56 -0700183 public void retain() {
184 buffer.retain();
185 }
186
Sami Kalliomäki61db3fd2018-04-09 17:51:19 +0200187 @Override
Sami Kalliomäki2bde8502018-02-15 13:58:15 +0100188 @CalledByNative
magjed55220212017-06-02 02:45:56 -0700189 public void release() {
190 buffer.release();
191 }
sakal836f60c2017-07-28 07:12:23 -0700192
Sami Kalliomäki1641ca32018-04-04 15:59:31 +0200193 // TODO(sakal): This file should be strictly an interface. This method should be moved somewhere
194 // else.
sakal836f60c2017-07-28 07:12:23 -0700195 public static VideoFrame.Buffer cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY,
196 int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
197 if (cropWidth == scaleWidth && cropHeight == scaleHeight) {
198 // No scaling.
199 ByteBuffer dataY = buffer.getDataY();
200 ByteBuffer dataU = buffer.getDataU();
201 ByteBuffer dataV = buffer.getDataV();
202
203 dataY.position(cropX + cropY * buffer.getStrideY());
204 dataU.position(cropX / 2 + cropY / 2 * buffer.getStrideU());
205 dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV());
206
207 buffer.retain();
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200208 return JavaI420Buffer.wrap(buffer.getWidth(), buffer.getHeight(), dataY.slice(),
sakal836f60c2017-07-28 07:12:23 -0700209 buffer.getStrideY(), dataU.slice(), buffer.getStrideU(), dataV.slice(),
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200210 buffer.getStrideV(), buffer::release);
sakal836f60c2017-07-28 07:12:23 -0700211 }
212
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200213 JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight);
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100214 nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(),
sakal836f60c2017-07-28 07:12:23 -0700215 buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth,
216 cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(),
217 newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV(), scaleWidth,
218 scaleHeight);
219 return newBuffer;
220 }
221
Magnus Jedvert84d8ae52017-12-20 15:12:10 +0100222 private static native void nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY,
sakal836f60c2017-07-28 07:12:23 -0700223 ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY,
224 int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU,
225 int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -0700226}