30 | * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent to the video encoder.
31 | */
32 | class InputSurface {
33 | private static final String TAG = "InputSurface";
34 | private static final boolean VERBOSE = false;
35 | private static final int EGL_RECORDABLE_ANDROID = 0x3142;
36 | private static final int EGL_OPENGL_ES2_BIT = 4;
37 | private EGLDisplay mEGLDisplay;
38 | private EGLContext mEGLContext;
39 | private EGLSurface mEGLSurface;
40 | private Surface mSurface;
41 |
42 | /**
43 | * Creates an InputSurface from a Surface.
44 | */
45 | public InputSurface( Surface surface ) {
46 | if ( surface == null ) {
47 | throw new NullPointerException();
48 | }
49 | mSurface = surface;
50 | eglSetup();
51 | }
52 |
53 | /**
54 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording.
55 | */
56 | private void eglSetup() {
57 | mEGLDisplay = EGL14.eglGetDisplay( EGL14.EGL_DEFAULT_DISPLAY );
58 | if ( mEGLDisplay == EGL14.EGL_NO_DISPLAY ) {
59 | throw new RuntimeException( "unable to get EGL14 display" );
60 | }
61 | int[] version = new int[2];
62 | if ( !EGL14.eglInitialize( mEGLDisplay, version, 0, version, 1 ) ) {
63 | mEGLDisplay = null;
64 | throw new RuntimeException( "unable to initialize EGL14" );
65 | }
66 | // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
67 | // to be able to tell if the frame is reasonable.
68 | int[] attribList = { EGL14.EGL_RED_SIZE, 8, EGL14.EGL_GREEN_SIZE, 8, EGL14.EGL_BLUE_SIZE, 8, EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RECORDABLE_ANDROID, 1, EGL14.EGL_NONE };
69 | EGLConfig[] configs = new EGLConfig[1];
70 | int[] numConfigs = new int[1];
71 | if ( !EGL14.eglChooseConfig( mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0 ) ) {
72 | throw new RuntimeException( "unable to find RGB888+recordable ES2 EGL config" );
73 | }
74 | // Configure context for OpenGL ES 2.0.
75 | int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
76 | mEGLContext = EGL14.eglCreateContext( mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0 );
77 | checkEglError( "eglCreateContext" );
78 | if ( mEGLContext == null ) {
79 | throw new RuntimeException( "null context" );
80 | }
81 | // Create a window surface, and attach it to the Surface we received.
82 | int[] surfaceAttribs = { EGL14.EGL_NONE };
83 | mEGLSurface = EGL14.eglCreateWindowSurface( mEGLDisplay, configs[0], mSurface, surfaceAttribs, 0 );
84 | checkEglError( "eglCreateWindowSurface" );
85 | if ( mEGLSurface == null ) {
86 | throw new RuntimeException( "surface was null" );
87 | }
88 | }
89 |
90 | /**
91 | * Discard all resources held by this class, notably the EGL context. Also releases the Surface that was passed to our constructor.
92 | */
93 | public void release() {
94 | if ( EGL14.eglGetCurrentContext().equals( mEGLContext ) ) {
95 | // Clear the current context and surface to ensure they are discarded immediately.
96 | EGL14.eglMakeCurrent( mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT );
97 | }
98 | EGL14.eglDestroySurface( mEGLDisplay, mEGLSurface );
99 | EGL14.eglDestroyContext( mEGLDisplay, mEGLContext );
100 | // EGL14.eglTerminate(mEGLDisplay);
101 | mSurface.release();
102 | // null everything out so future attempts to use this object will cause an NPE
103 | mEGLDisplay = null;
104 | mEGLContext = null;
105 | mEGLSurface = null;
106 | mSurface = null;
107 | }
108 |
109 | /**
110 | * Makes our EGL context and surface current.
111 | */
112 | public void makeCurrent() {
113 | if ( !EGL14.eglMakeCurrent( mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext ) ) {
114 | throw new RuntimeException( "eglMakeCurrent failed" );
115 | }
116 | }
117 |
118 | /**
119 | * Calls eglSwapBuffers. Use this to "publish" the current frame.
120 | */
121 | public boolean swapBuffers() {
122 | return EGL14.eglSwapBuffers( mEGLDisplay, mEGLSurface );
123 | }
124 |
125 | /**
126 | * Returns the Surface that the MediaCodec receives buffers from.
127 | */
128 | public Surface getSurface() {
129 | return mSurface;
130 | }
131 |
132 | /**
133 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds.
134 | */
135 | public void setPresentationTime( long nsecs ) {
136 | EGLExt.eglPresentationTimeANDROID( mEGLDisplay, mEGLSurface, nsecs );
137 | }
138 |
139 | /**
140 | * Checks for EGL errors.
141 | */
142 | private void checkEglError( String msg ) {
143 | boolean failed = false;
144 | int error;
145 | while ( ( error = EGL14.eglGetError() ) != EGL14.EGL_SUCCESS ) {
146 | Log.e( TAG, msg + ": EGL error: 0x" + Integer.toHexString( error ) );
147 | failed = true;
148 | }
149 | if ( failed ) {
150 | throw new RuntimeException( "EGL error encountered (see log)" );
151 | }
152 | }
153 | }
--------------------------------------------------------------------------------
/CommonVideoLibrary/src/com/roryhool/commonvideolibrary/MediaHelper.java:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (c) 2014 Rory Hool
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | **/
16 |
17 | package com.roryhool.commonvideolibrary;
18 |
19 | import java.io.FileOutputStream;
20 | import java.io.IOException;
21 | import java.util.List;
22 | import java.util.Locale;
23 |
24 | import android.annotation.TargetApi;
25 | import android.graphics.Bitmap;
26 | import android.media.MediaExtractor;
27 | import android.media.MediaFormat;
28 | import android.media.MediaMetadataRetriever;
29 | import android.net.Uri;
30 | import android.os.Build;
31 |
32 | import com.coremedia.iso.IsoFile;
33 | import com.coremedia.iso.boxes.Box;
34 | import com.coremedia.iso.boxes.HandlerBox;
35 | import com.coremedia.iso.boxes.TrackBox;
36 | import com.coremedia.iso.boxes.TrackHeaderBox;
37 | import com.googlecode.mp4parser.util.Matrix;
38 |
39 | public class MediaHelper {
40 |
41 | public static final String MIME_TYPE_AVC = "video/avc";
42 |
43 | public static Bitmap GetThumbnailFromVideo( Uri uri, long timeMs ) {
44 | MediaMetadataRetriever retriever = new MediaMetadataRetriever();
45 | retriever.setDataSource( uri.toString() );
46 | return retriever.getFrameAtTime( timeMs * 1000 );
47 | }
48 |
49 | public static int GetDuration( Uri uri ) {
50 | return GetMediaMetadataRetrieverPropertyInteger( uri, MediaMetadataRetriever.METADATA_KEY_DURATION, 0 );
51 | }
52 |
53 | public static int GetWidth( Uri uri ) {
54 | return GetMediaMetadataRetrieverPropertyInteger( uri, MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH, 0 );
55 | }
56 |
57 | public static int GetHeight( Uri uri ) {
58 | return GetMediaMetadataRetrieverPropertyInteger( uri, MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT, 0 );
59 | }
60 |
61 | public static int GetBitRate( Uri uri ) {
62 | return GetMediaMetadataRetrieverPropertyInteger( uri, MediaMetadataRetriever.METADATA_KEY_BITRATE, 0 );
63 | }
64 |
65 | @TargetApi( Build.VERSION_CODES.JELLY_BEAN_MR1 )
66 | public static int GetRotation( Uri uri ) {
67 | return GetMediaMetadataRetrieverPropertyInteger( uri, MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION, 0 );
68 | }
69 |
70 | public static int GetMediaMetadataRetrieverPropertyInteger( Uri uri, int key, int defaultValue ) {
71 | MediaMetadataRetriever retriever = new MediaMetadataRetriever();
72 | retriever.setDataSource( uri.toString() );
73 | String value = retriever.extractMetadata( key );
74 |
75 | if ( value == null ) {
76 | return defaultValue;
77 | }
78 | return Integer.parseInt( value );
79 |
80 | }
81 |
82 | @TargetApi( Build.VERSION_CODES.JELLY_BEAN )
83 | public static int GetIFrameInterval( Uri uri ) {
84 |
85 | return GetMediaFormatPropertyInteger( uri, MediaFormat.KEY_I_FRAME_INTERVAL, -1 );
86 | }
87 |
88 | @TargetApi( Build.VERSION_CODES.JELLY_BEAN )
89 | public static int GetFrameRate( Uri uri ) {
90 |
91 | return GetMediaFormatPropertyInteger( uri, MediaFormat.KEY_FRAME_RATE, -1 );
92 | }
93 |
94 | @TargetApi( Build.VERSION_CODES.JELLY_BEAN )
95 | public static int GetMediaFormatPropertyInteger( Uri uri, String key, int defaultValue ) {
96 | int value = defaultValue;
97 |
98 | MediaExtractor extractor = new MediaExtractor();
99 | try {
100 | extractor.setDataSource( uri.toString() );
101 | } catch ( IOException e ) {
102 | e.printStackTrace();
103 | return value;
104 | }
105 |
106 | MediaFormat format = GetTrackFormat( extractor, MIME_TYPE_AVC );
107 | extractor.release();
108 |
109 | if ( format.containsKey( key ) ) {
110 | value = format.getInteger( key );
111 | }
112 |
113 | return value;
114 | }
115 |
116 | @TargetApi( Build.VERSION_CODES.JELLY_BEAN )
117 | public static MediaFormat GetTrackFormat( MediaExtractor extractor, String mimeType ) {
118 | for ( int i = 0; i < extractor.getTrackCount(); i++ ) {
119 | MediaFormat format = extractor.getTrackFormat( i );
120 | String trackMimeType = format.getString( MediaFormat.KEY_MIME );
121 | if ( mimeType.equals( trackMimeType ) ) {
122 | return format;
123 | }
124 | }
125 |
126 | return null;
127 | }
128 |
129 | public static Uri RotateVideo( Uri uri, int rotation ) {
130 |
131 | Uri rotatedVideoUri = null;
132 |
133 | try {
134 | IsoFile file = new IsoFile( uri.toString() );
135 |
136 | List
32 | * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, and then create a Surface for that SurfaceTexture. The Surface can be passed to MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the texture with updateTexImage, then render the texture with GL to a pbuffer.
33 | *
34 | * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives we just draw it on whatever surface is current.
35 | *
36 | * By default, the Surface will be using a BufferQueue in asynchronous mode, so we can potentially drop frames.
37 | */
38 | class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
39 | private static final String TAG = "OutputSurface";
40 | private static final boolean VERBOSE = false;
41 | private static final int EGL_OPENGL_ES2_BIT = 4;
42 | private EGL10 mEGL;
43 | private EGLDisplay mEGLDisplay;
44 | private EGLContext mEGLContext;
45 | private EGLSurface mEGLSurface;
46 | private SurfaceTexture mSurfaceTexture;
47 | private Surface mSurface;
48 | private Object mFrameSyncObject = new Object(); // guards mFrameAvailable
49 | private boolean mFrameAvailable;
50 | private TextureRender mTextureRender;
51 |
52 | /**
53 | * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new EGL context and surface will be made current. Creates a Surface that can be passed to MediaCodec.configure().
54 | */
55 | public OutputSurface( int width, int height ) {
56 | if ( width <= 0 || height <= 0 ) {
57 | throw new IllegalArgumentException();
58 | }
59 | eglSetup( width, height );
60 | makeCurrent();
61 | setup();
62 | }
63 |
64 | /**
65 | * Creates an OutputSurface using the current EGL context. Creates a Surface that can be passed to MediaCodec.configure().
66 | */
67 | public OutputSurface() {
68 | setup();
69 | }
70 |
71 | /**
72 | * Creates instances of TextureRender and SurfaceTexture, and a Surface associated with the SurfaceTexture.
73 | */
74 | private void setup() {
75 | mTextureRender = new TextureRender();
76 | mTextureRender.surfaceCreated();
77 | // Even if we don't access the SurfaceTexture after the constructor returns, we
78 | // still need to keep a reference to it. The Surface doesn't retain a reference
79 | // at the Java level, so if we don't either then the object can get GCed, which
80 | // causes the native finalizer to run.
81 | if ( VERBOSE )
82 | Log.d( TAG, "textureID=" + mTextureRender.getTextureId() );
83 | mSurfaceTexture = new SurfaceTexture( mTextureRender.getTextureId() );
84 | // This doesn't work if OutputSurface is created on the thread that CTS started for
85 | // these test cases.
86 | //
87 | // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
88 | // create a Handler that uses it. The "frame available" message is delivered
89 | // there, but since we're not a Looper-based thread we'll never see it. For
90 | // this to do anything useful, OutputSurface must be created on a thread without
91 | // a Looper, so that SurfaceTexture uses the main application Looper instead.
92 | //
93 | // Java language note: passing "this" out of a constructor is generally unwise,
94 | // but we should be able to get away with it here.
95 | mSurfaceTexture.setOnFrameAvailableListener( this );
96 | mSurface = new Surface( mSurfaceTexture );
97 | }
98 |
99 | /**
100 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer.
101 | */
102 | private void eglSetup( int width, int height ) {
103 | mEGL = (EGL10) EGLContext.getEGL();
104 | mEGLDisplay = mEGL.eglGetDisplay( EGL10.EGL_DEFAULT_DISPLAY );
105 | if ( !mEGL.eglInitialize( mEGLDisplay, null ) ) {
106 | throw new RuntimeException( "unable to initialize EGL10" );
107 | }
108 | // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
109 | // to be able to tell if the frame is reasonable.
110 | int[] attribList = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE };
111 | EGLConfig[] configs = new EGLConfig[1];
112 | int[] numConfigs = new int[1];
113 | if ( !mEGL.eglChooseConfig( mEGLDisplay, attribList, configs, 1, numConfigs ) ) {
114 | throw new RuntimeException( "unable to find RGB888+pbuffer EGL config" );
115 | }
116 | // Configure context for OpenGL ES 2.0.
117 | int[] attrib_list = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
118 | mEGLContext = mEGL.eglCreateContext( mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list );
119 | checkEglError( "eglCreateContext" );
120 | if ( mEGLContext == null ) {
121 | throw new RuntimeException( "null context" );
122 | }
123 | // Create a pbuffer surface. By using this for output, we can use glReadPixels
124 | // to test values in the output.
125 | int[] surfaceAttribs = { EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE };
126 | mEGLSurface = mEGL.eglCreatePbufferSurface( mEGLDisplay, configs[0], surfaceAttribs );
127 | checkEglError( "eglCreatePbufferSurface" );
128 | if ( mEGLSurface == null ) {
129 | throw new RuntimeException( "surface was null" );
130 | }
131 | }
132 |
133 | /**
134 | * Discard all resources held by this class, notably the EGL context.
135 | */
136 | public void release() {
137 | if ( mEGL != null ) {
138 | if ( mEGL.eglGetCurrentContext().equals( mEGLContext ) ) {
139 | // Clear the current context and surface to ensure they are discarded immediately.
140 | mEGL.eglMakeCurrent( mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT );
141 | }
142 | mEGL.eglDestroySurface( mEGLDisplay, mEGLSurface );
143 | mEGL.eglDestroyContext( mEGLDisplay, mEGLContext );
144 | // mEGL.eglTerminate(mEGLDisplay);
145 | }
146 | mSurface.release();
147 | // this causes a bunch of warnings that appear harmless but might confuse someone:
148 | // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
149 | // mSurfaceTexture.release();
150 | // null everything out so future attempts to use this object will cause an NPE
151 | mEGLDisplay = null;
152 | mEGLContext = null;
153 | mEGLSurface = null;
154 | mEGL = null;
155 | mTextureRender = null;
156 | mSurface = null;
157 | mSurfaceTexture = null;
158 | }
159 |
160 | /**
161 | * Makes our EGL context and surface current.
162 | */
163 | public void makeCurrent() {
164 | if ( mEGL == null ) {
165 | throw new RuntimeException( "not configured for makeCurrent" );
166 | }
167 | checkEglError( "before makeCurrent" );
168 | if ( !mEGL.eglMakeCurrent( mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext ) ) {
169 | throw new RuntimeException( "eglMakeCurrent failed" );
170 | }
171 | }
172 |
173 | /**
174 | * Returns the Surface that we draw onto.
175 | */
176 | public Surface getSurface() {
177 | return mSurface;
178 | }
179 |
180 | /**
181 | * Replaces the fragment shader.
182 | */
183 | public void changeFragmentShader( String fragmentShader ) {
184 | mTextureRender.changeFragmentShader( fragmentShader );
185 | }
186 |
187 | /**
188 | * Latches the next buffer into the texture. Must be called from the thread that created the OutputSurface object, after the onFrameAvailable callback has signaled that new data is available.
189 | */
190 | public void awaitNewImage() {
191 | final int TIMEOUT_MS = 500;
192 | synchronized ( mFrameSyncObject ) {
193 | while ( !mFrameAvailable ) {
194 | try {
195 | // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
196 | // stalling the test if it doesn't arrive.
197 | mFrameSyncObject.wait( TIMEOUT_MS );
198 | if ( !mFrameAvailable ) {
199 | // TODO: if "spurious wakeup", continue while loop
200 | throw new RuntimeException( "Surface frame wait timed out" );
201 | }
202 | } catch ( InterruptedException ie ) {
203 | // shouldn't happen
204 | throw new RuntimeException( ie );
205 | }
206 | }
207 | mFrameAvailable = false;
208 | }
209 | // Latch the data.
210 | mTextureRender.checkGlError( "before updateTexImage" );
211 | mSurfaceTexture.updateTexImage();
212 | }
213 |
214 | /**
215 | * Draws the data from SurfaceTexture onto the current EGL surface.
216 | */
217 | public void drawImage() {
218 | mTextureRender.drawFrame( mSurfaceTexture );
219 | }
220 |
221 | @Override
222 | public void onFrameAvailable( SurfaceTexture st ) {
223 | if ( VERBOSE )
224 | Log.d( TAG, "new frame available" );
225 | synchronized ( mFrameSyncObject ) {
226 | if ( mFrameAvailable ) {
227 | throw new RuntimeException( "mFrameAvailable already set, frame could be dropped" );
228 | }
229 | mFrameAvailable = true;
230 | mFrameSyncObject.notifyAll();
231 | }
232 | }
233 |
234 | /**
235 | * Checks for EGL errors.
236 | */
237 | private void checkEglError( String msg ) {
238 | boolean failed = false;
239 | int error;
240 | while ( ( error = mEGL.eglGetError() ) != EGL10.EGL_SUCCESS ) {
241 | Log.e( TAG, msg + ": EGL error: 0x" + Integer.toHexString( error ) );
242 | failed = true;
243 | }
244 | if ( failed ) {
245 | throw new RuntimeException( "EGL error encountered (see log)" );
246 | }
247 | }
248 | }
--------------------------------------------------------------------------------
/VideoCreation/src/com/roryhool/videocreation/SurfaceEncoder.java:
--------------------------------------------------------------------------------
1 | /**
2 | Copyright (c) 2014 Rory Hool
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 |
10 | Unless required by applicable law or agreed to in writing, software
11 | distributed under the License is distributed on an "AS IS" BASIS,
12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | See the License for the specific language governing permissions and
14 | limitations under the License.
15 | **/
16 |
17 | package com.roryhool.videocreation;
18 |
19 | import java.io.IOException;
20 | import java.nio.ByteBuffer;
21 | import java.util.ArrayList;
22 |
23 | import android.graphics.Canvas;
24 | import android.media.MediaCodec;
25 | import android.media.MediaCodecInfo;
26 | import android.media.MediaFormat;
27 | import android.media.MediaMuxer;
28 | import android.net.Uri;
29 | import android.os.Handler;
30 | import android.os.Message;
31 | import android.util.Log;
32 | import android.view.Surface;
33 | import android.view.Surface.OutOfResourcesException;
34 |
35 | public class SurfaceEncoder {
36 |
37 | private static final int TIMEOUT_USEC = 10000;
38 |
39 | public static final int ENCODER_STATUS = 100;
40 | public static final int ENCODER_SUCCEEDED = 101;
41 | public static final int ENCODER_FAILED = 102;
42 |
43 | public interface EncoderSource {
44 | public void renderFrame( Canvas canvas, long time, long interval );
45 | public int getWidth();
46 | public int getHeight();
47 | public long getDuration();
48 | }
49 |
50 | public interface EncoderListener {
51 | public void encoderSucceeded();
52 | public void encoderFailed();
53 | }
54 |
55 | private static final String TAG = "Encoder";
56 |
57 | private static final String MIME_TYPE = "video/avc";
58 | private static final int FRAME_RATE = 30;
59 | private static final int IFRAME_INTERVAL = 1;
60 |
61 | Uri mUri;
62 |
63 | int mWidth;
64 | int mHeight;
65 |
66 | int mBitRate = 2000000;
67 |
68 | private MediaCodec mEncoder;
69 | private Surface mSurface;
70 | private MediaMuxer mMuxer;
71 | private int mTrackIndex;
72 | private boolean mMuxerStarted;
73 |
74 | private MediaCodec.BufferInfo mBufferInfo;
75 |
76 | EncoderSource mSource;
77 |
78 | EncoderThread mThread;
79 |
80 | ArrayList