blob: da9542b3af7c426d199a32172182d3c4cdd2937a [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;
17
18/**
19 * Java version of webrtc::VideoFrame and webrtc::VideoFrameBuffer. A difference from the C++
20 * version is that no explicit tag is used, and clients are expected to use 'instanceof' to find the
21 * right subclass of the buffer. This allows clients to create custom VideoFrame.Buffer in
22 * arbitrary format in their custom VideoSources, and then cast it back to the correct subclass in
23 * their custom VideoSinks. All implementations must also implement the toI420() function,
24 * converting from the underlying representation if necessary. I420 is the most widely accepted
25 * format and serves as a fallback for video sinks that can only handle I420, e.g. the internal
26 * WebRTC software encoders.
27 */
28public class VideoFrame {
29 public interface Buffer {
30 /**
31 * Resolution of the buffer in pixels.
32 */
33 int getWidth();
34 int getHeight();
35
36 /**
37 * Returns a memory-backed frame in I420 format. If the pixel data is in another format, a
38 * conversion will take place. All implementations must provide a fallback to I420 for
39 * compatibility with e.g. the internal WebRTC software encoders.
40 */
41 I420Buffer toI420();
42
43 /**
44 * Reference counting is needed since a video buffer can be shared between multiple VideoSinks,
45 * and the buffer needs to be returned to the VideoSource as soon as all references are gone.
46 */
47 void retain();
48 void release();
sakal836f60c2017-07-28 07:12:23 -070049
50 /**
51 * Crops a region defined by |cropx|, |cropY|, |cropWidth| and |cropHeight|. Scales it to size
52 * |scaleWidth| x |scaleHeight|.
53 */
54 Buffer cropAndScale(
55 int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -070056 }
57
58 /**
59 * Interface for I420 buffers.
60 */
61 public interface I420Buffer extends Buffer {
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020062 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020063 * Returns a direct ByteBuffer containing Y-plane data. The buffer capacity is at least
64 * getStrideY() * getHeight() bytes. The position of the returned buffer is ignored and must
65 * be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
66 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020067 */
magjed55220212017-06-02 02:45:56 -070068 ByteBuffer getDataY();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020069 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020070 * Returns a direct ByteBuffer containing U-plane data. The buffer capacity is at least
71 * getStrideU() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
72 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
73 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020074 */
magjed55220212017-06-02 02:45:56 -070075 ByteBuffer getDataU();
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020076 /**
Sami Kalliomäkie3044fe2017-10-02 09:41:55 +020077 * Returns a direct ByteBuffer containing V-plane data. The buffer capacity is at least
78 * getStrideV() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
79 * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
80 * implementations must return a new ByteBuffer or slice for each call.
Sami Kalliomäkibc7a1a92017-09-27 12:50:47 +020081 */
magjed55220212017-06-02 02:45:56 -070082 ByteBuffer getDataV();
83
84 int getStrideY();
85 int getStrideU();
86 int getStrideV();
87 }
88
89 /**
90 * Interface for buffers that are stored as a single texture, either in OES or RGB format.
91 */
92 public interface TextureBuffer extends Buffer {
Sami Kalliomäkicb98b112017-10-16 11:20:26 +020093 enum Type {
94 OES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES),
95 RGB(GLES20.GL_TEXTURE_2D);
96
97 private final int glTarget;
98
99 private Type(final int glTarget) {
100 this.glTarget = glTarget;
101 }
102
103 public int getGlTarget() {
104 return glTarget;
105 }
106 }
magjed55220212017-06-02 02:45:56 -0700107
108 Type getType();
109 int getTextureId();
sakal836f60c2017-07-28 07:12:23 -0700110
111 /**
112 * Retrieve the transform matrix associated with the frame. This transform matrix maps 2D
113 * homogeneous coordinates of the form (s, t, 1) with s and t in the inclusive range [0, 1] to
114 * the coordinate that should be used to sample that location from the buffer.
115 */
116 public Matrix getTransformMatrix();
magjed55220212017-06-02 02:45:56 -0700117 }
118
119 private final Buffer buffer;
120 private final int rotation;
121 private final long timestampNs;
magjed55220212017-06-02 02:45:56 -0700122
sakal836f60c2017-07-28 07:12:23 -0700123 public VideoFrame(Buffer buffer, int rotation, long timestampNs) {
magjed55220212017-06-02 02:45:56 -0700124 if (buffer == null) {
125 throw new IllegalArgumentException("buffer not allowed to be null");
126 }
sakal6bdcefc2017-08-15 01:56:02 -0700127 if (rotation % 90 != 0) {
128 throw new IllegalArgumentException("rotation must be a multiple of 90");
129 }
magjed55220212017-06-02 02:45:56 -0700130 this.buffer = buffer;
131 this.rotation = rotation;
132 this.timestampNs = timestampNs;
magjed55220212017-06-02 02:45:56 -0700133 }
134
135 public Buffer getBuffer() {
136 return buffer;
137 }
138
139 /**
140 * Rotation of the frame in degrees.
141 */
142 public int getRotation() {
143 return rotation;
144 }
145
146 /**
147 * Timestamp of the frame in nano seconds.
148 */
149 public long getTimestampNs() {
150 return timestampNs;
151 }
152
sakal6bdcefc2017-08-15 01:56:02 -0700153 public int getRotatedWidth() {
154 if (rotation % 180 == 0) {
155 return buffer.getWidth();
156 }
157 return buffer.getHeight();
158 }
159
160 public int getRotatedHeight() {
161 if (rotation % 180 == 0) {
162 return buffer.getHeight();
163 }
164 return buffer.getWidth();
165 }
166
magjed55220212017-06-02 02:45:56 -0700167 /**
magjed55220212017-06-02 02:45:56 -0700168 * Reference counting of the underlying buffer.
169 */
170 public void retain() {
171 buffer.retain();
172 }
173
174 public void release() {
175 buffer.release();
176 }
sakal836f60c2017-07-28 07:12:23 -0700177
178 public static VideoFrame.Buffer cropAndScaleI420(final I420Buffer buffer, int cropX, int cropY,
179 int cropWidth, int cropHeight, int scaleWidth, int scaleHeight) {
180 if (cropWidth == scaleWidth && cropHeight == scaleHeight) {
181 // No scaling.
182 ByteBuffer dataY = buffer.getDataY();
183 ByteBuffer dataU = buffer.getDataU();
184 ByteBuffer dataV = buffer.getDataV();
185
186 dataY.position(cropX + cropY * buffer.getStrideY());
187 dataU.position(cropX / 2 + cropY / 2 * buffer.getStrideU());
188 dataV.position(cropX / 2 + cropY / 2 * buffer.getStrideV());
189
190 buffer.retain();
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200191 return JavaI420Buffer.wrap(buffer.getWidth(), buffer.getHeight(), dataY.slice(),
sakal836f60c2017-07-28 07:12:23 -0700192 buffer.getStrideY(), dataU.slice(), buffer.getStrideU(), dataV.slice(),
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200193 buffer.getStrideV(), buffer::release);
sakal836f60c2017-07-28 07:12:23 -0700194 }
195
Sami Kalliomäki48b3c022017-10-04 17:01:00 +0200196 JavaI420Buffer newBuffer = JavaI420Buffer.allocate(scaleWidth, scaleHeight);
sakal836f60c2017-07-28 07:12:23 -0700197 nativeCropAndScaleI420(buffer.getDataY(), buffer.getStrideY(), buffer.getDataU(),
198 buffer.getStrideU(), buffer.getDataV(), buffer.getStrideV(), cropX, cropY, cropWidth,
199 cropHeight, newBuffer.getDataY(), newBuffer.getStrideY(), newBuffer.getDataU(),
200 newBuffer.getStrideU(), newBuffer.getDataV(), newBuffer.getStrideV(), scaleWidth,
201 scaleHeight);
202 return newBuffer;
203 }
204
205 private static native void nativeCropAndScaleI420(ByteBuffer srcY, int srcStrideY,
206 ByteBuffer srcU, int srcStrideU, ByteBuffer srcV, int srcStrideV, int cropX, int cropY,
207 int cropWidth, int cropHeight, ByteBuffer dstY, int dstStrideY, ByteBuffer dstU,
208 int dstStrideU, ByteBuffer dstV, int dstStrideV, int scaleWidth, int scaleHeight);
magjed55220212017-06-02 02:45:56 -0700209}