1 /*
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 
11 package org.webrtc;
12 
13 import android.graphics.Matrix;
14 import android.opengl.GLES11Ext;
15 import android.opengl.GLES20;
16 import 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  */
28 public class VideoFrame implements RefCounted {
29   /**
30    * Implements image storage medium. Might be for example an OpenGL texture or a memory region
31    * containing I420-data.
32    *
33    * <p>Reference counting is needed since a video buffer can be shared between multiple VideoSinks,
34    * and the buffer needs to be returned to the VideoSource as soon as all references are gone.
35    */
36   public interface Buffer extends RefCounted {
37     /**
38      * Resolution of the buffer in pixels.
39      */
getWidth()40     @CalledByNative("Buffer") int getWidth();
getHeight()41     @CalledByNative("Buffer") int getHeight();
42 
43     /**
44      * Returns a memory-backed frame in I420 format. If the pixel data is in another format, a
45      * conversion will take place. All implementations must provide a fallback to I420 for
46      * compatibility with e.g. the internal WebRTC software encoders.
47      */
toI420()48     @CalledByNative("Buffer") I420Buffer toI420();
49 
retain()50     @Override @CalledByNative("Buffer") void retain();
release()51     @Override @CalledByNative("Buffer") void release();
52 
53     /**
54      * Crops a region defined by |cropx|, |cropY|, |cropWidth| and |cropHeight|. Scales it to size
55      * |scaleWidth| x |scaleHeight|.
56      */
57     @CalledByNative("Buffer")
cropAndScale( int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight)58     Buffer cropAndScale(
59         int cropX, int cropY, int cropWidth, int cropHeight, int scaleWidth, int scaleHeight);
60   }
61 
62   /**
63    * Interface for I420 buffers.
64    */
65   public interface I420Buffer extends Buffer {
66     /**
67      * Returns a direct ByteBuffer containing Y-plane data. The buffer capacity is at least
68      * getStrideY() * getHeight() bytes. The position of the returned buffer is ignored and must
69      * be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
70      * implementations must return a new ByteBuffer or slice for each call.
71      */
getDataY()72     @CalledByNative("I420Buffer") ByteBuffer getDataY();
73     /**
74      * Returns a direct ByteBuffer containing U-plane data. The buffer capacity is at least
75      * getStrideU() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
76      * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
77      * implementations must return a new ByteBuffer or slice for each call.
78      */
getDataU()79     @CalledByNative("I420Buffer") ByteBuffer getDataU();
80     /**
81      * Returns a direct ByteBuffer containing V-plane data. The buffer capacity is at least
82      * getStrideV() * ((getHeight() + 1) / 2) bytes. The position of the returned buffer is ignored
83      * and must be 0. Callers may mutate the ByteBuffer (eg. through relative-read operations), so
84      * implementations must return a new ByteBuffer or slice for each call.
85      */
getDataV()86     @CalledByNative("I420Buffer") ByteBuffer getDataV();
87 
getStrideY()88     @CalledByNative("I420Buffer") int getStrideY();
getStrideU()89     @CalledByNative("I420Buffer") int getStrideU();
getStrideV()90     @CalledByNative("I420Buffer") int getStrideV();
91   }
92 
93   /**
94    * Interface for buffers that are stored as a single texture, either in OES or RGB format.
95    */
96   public interface TextureBuffer extends Buffer {
97     enum Type {
98       OES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES),
99       RGB(GLES20.GL_TEXTURE_2D);
100 
101       private final int glTarget;
102 
Type(final int glTarget)103       private Type(final int glTarget) {
104         this.glTarget = glTarget;
105       }
106 
getGlTarget()107       public int getGlTarget() {
108         return glTarget;
109       }
110     }
111 
getType()112     Type getType();
getTextureId()113     int getTextureId();
114 
115     /**
116      * Retrieve the transform matrix associated with the frame. This transform matrix maps 2D
117      * homogeneous coordinates of the form (s, t, 1) with s and t in the inclusive range [0, 1] to
118      * the coordinate that should be used to sample that location from the buffer.
119      */
getTransformMatrix()120     Matrix getTransformMatrix();
121   }
122 
123   private final Buffer buffer;
124   private final int rotation;
125   private final long timestampNs;
126 
127   /**
128    * Constructs a new VideoFrame backed by the given {@code buffer}.
129    *
130    * @note Ownership of the buffer object is tranferred to the new VideoFrame.
131    */
132   @CalledByNative
VideoFrame(Buffer buffer, int rotation, long timestampNs)133   public VideoFrame(Buffer buffer, int rotation, long timestampNs) {
134     if (buffer == null) {
135       throw new IllegalArgumentException("buffer not allowed to be null");
136     }
137     if (rotation % 90 != 0) {
138       throw new IllegalArgumentException("rotation must be a multiple of 90");
139     }
140     this.buffer = buffer;
141     this.rotation = rotation;
142     this.timestampNs = timestampNs;
143   }
144 
145   @CalledByNative
getBuffer()146   public Buffer getBuffer() {
147     return buffer;
148   }
149 
150   /**
151    * Rotation of the frame in degrees.
152    */
153   @CalledByNative
getRotation()154   public int getRotation() {
155     return rotation;
156   }
157 
158   /**
159    * Timestamp of the frame in nano seconds.
160    */
161   @CalledByNative
getTimestampNs()162   public long getTimestampNs() {
163     return timestampNs;
164   }
165 
getRotatedWidth()166   public int getRotatedWidth() {
167     if (rotation % 180 == 0) {
168       return buffer.getWidth();
169     }
170     return buffer.getHeight();
171   }
172 
getRotatedHeight()173   public int getRotatedHeight() {
174     if (rotation % 180 == 0) {
175       return buffer.getHeight();
176     }
177     return buffer.getWidth();
178   }
179 
180   @Override
retain()181   public void retain() {
182     buffer.retain();
183   }
184 
185   @Override
186   @CalledByNative
release()187   public void release() {
188     buffer.release();
189   }
190 }
191