├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── android │ │ └── guoheng │ │ └── decodeencodemp4 │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── android │ │ │ └── guoheng │ │ │ └── decodeencodemp4 │ │ │ ├── CodecOutputSurface.java │ │ │ ├── EncodeDecodeSurface.java │ │ │ ├── GlUtil.java │ │ │ ├── MainActivity.java │ │ │ ├── STextureRender.java │ │ │ ├── SurfaceDecoder.java │ │ │ └── SurfaceEncoder.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── android │ └── guoheng │ └── decodeencodemp4 │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | DecodeEncodeMP4 -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DecodeEncodeMP4 2 | This repository is created for android hardware decode encode test 3 | 4 | #Request 5 | - you need put an input mp4 file named in 1.mp4 in root dir 6 | - The output mp4 file will be named as mytest.1920*1080.mp4, also located in root dir 7 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion "24.0.0" 6 | 7 | defaultConfig { 8 | applicationId "com.android.guoheng.decodeencodemp4" 9 | minSdkVersion 19 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:24.0.0' 26 | } 27 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\AndroidSDK/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/android/guoheng/decodeencodemp4/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/CodecOutputSurface.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.graphics.SurfaceTexture; 4 | import android.opengl.EGL14; 5 | import android.opengl.EGLConfig; 6 | import android.opengl.EGLContext; 7 | import android.opengl.EGLDisplay; 8 | import android.opengl.EGLExt; 9 | import android.opengl.EGLSurface; 10 | import android.util.Log; 11 | import android.view.Surface; 12 | 13 | import java.nio.ByteBuffer; 14 | import java.nio.ByteOrder; 15 | 16 | /** 17 | * Created by guoheng-iri on 2016/9/1. 18 | */ 19 | public class CodecOutputSurface 20 | implements SurfaceTexture.OnFrameAvailableListener { 21 | 22 | private static final String TAG = "EncodeDecodeSurface"; 23 | private static final boolean VERBOSE = false; // lots of logging 24 | 25 | private STextureRender mTextureRender; 26 | private SurfaceTexture mSurfaceTexture; 27 | private Surface mSurface; 28 | public Surface EncodeSurface; 29 | 30 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 31 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 32 | private EGLContext mEGLContextEncoder = EGL14.EGL_NO_CONTEXT; 33 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 34 | private EGLSurface mEGLSurfaceEncoder = EGL14.EGL_NO_SURFACE; 35 | int mWidth; 36 | int mHeight; 37 | 38 | private Object mFrameSyncObject = new Object(); // guards mFrameAvailable 39 | private boolean mFrameAvailable; 40 | 41 | private ByteBuffer mPixelBuf; // used by saveFrame() 42 | 43 | /** 44 | * Creates a CodecOutputSurface backed by a pbuffer with the specified dimensions. The 45 | * new EGL context and surface will be made current. Creates a Surface that can be passed 46 | * to MediaCodec.configure(). 47 | */ 48 | public CodecOutputSurface(int width, int height,Surface surface) { 49 | if (width <= 0 || height <= 0) { 50 | throw new IllegalArgumentException(); 51 | } 52 | mWidth = width; 53 | mHeight = height; 54 | 55 | eglSetup(surface); 56 | makeCurrent(); 57 | setup(); 58 | } 59 | 60 | /** 61 | * Creates interconnected instances of TextureRender, SurfaceTexture, and Surface. 62 | */ 63 | private void setup() { 64 | mTextureRender = new STextureRender(); 65 | mTextureRender.surfaceCreated(); 66 | 67 | if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId()); 68 | mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); 69 | 70 | mSurfaceTexture.setOnFrameAvailableListener(this); 71 | 72 | mSurface = new Surface(mSurfaceTexture); 73 | 74 | mPixelBuf = ByteBuffer.allocateDirect(mWidth * mHeight * 4); 75 | mPixelBuf.order(ByteOrder.LITTLE_ENDIAN); 76 | } 77 | 78 | /** 79 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer. 80 | */ 81 | private void eglSetup(Surface surface) { 82 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 83 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 84 | throw new RuntimeException("unable to get EGL14 display"); 85 | } 86 | int[] version = new int[2]; 87 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 88 | mEGLDisplay = null; 89 | throw new RuntimeException("unable to initialize EGL14"); 90 | } 91 | 92 | // Configure EGL for pbuffer and OpenGL ES 2.0, 24-bit RGB. 93 | int[] attribList = { 94 | EGL14.EGL_RED_SIZE, 8, 95 | EGL14.EGL_GREEN_SIZE, 8, 96 | EGL14.EGL_BLUE_SIZE, 8, 97 | EGL14.EGL_ALPHA_SIZE, 8, 98 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, 99 | EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT, 100 | EGL14.EGL_NONE 101 | }; 102 | EGLConfig[] configs = new EGLConfig[1]; 103 | int[] numConfigs = new int[1]; 104 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 105 | numConfigs, 0)) { 106 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); 107 | } 108 | 109 | EGLConfig configEncoder = getConfig(2); 110 | 111 | // Configure context for OpenGL ES 2.0. 112 | int[] attrib_list = { 113 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 114 | EGL14.EGL_NONE 115 | }; 116 | mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, 117 | attrib_list, 0); 118 | checkEglError("eglCreateContext"); 119 | if (mEGLContext == null) { 120 | throw new RuntimeException("null context"); 121 | } 122 | 123 | mEGLContextEncoder = EGL14.eglCreateContext(mEGLDisplay, configEncoder, mEGLContext, 124 | attrib_list, 0); 125 | checkEglError("eglCreateContext"); 126 | if (mEGLContextEncoder == null) { 127 | throw new RuntimeException("null context2"); 128 | } 129 | 130 | // Create a pbuffer surface. 131 | int[] surfaceAttribs = { 132 | EGL14.EGL_WIDTH, mWidth, 133 | EGL14.EGL_HEIGHT, mHeight, 134 | EGL14.EGL_NONE 135 | }; 136 | mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0); 137 | 138 | 139 | checkEglError("eglCreatePbufferSurface"); 140 | if (mEGLSurface == null) { 141 | throw new RuntimeException("surface was null"); 142 | } 143 | 144 | 145 | int[] surfaceAttribs2 = { 146 | EGL14.EGL_NONE 147 | }; 148 | mEGLSurfaceEncoder = EGL14.eglCreateWindowSurface(mEGLDisplay, configEncoder, surface, 149 | surfaceAttribs2, 0); //creates an EGL window surface and returns its handle 150 | checkEglError("eglCreateWindowSurface"); 151 | if (mEGLSurfaceEncoder == null) { 152 | throw new RuntimeException("surface was null"); 153 | } 154 | EncodeSurface=surface; 155 | 156 | } 157 | 158 | /** 159 | * Discard all resources held by this class, notably the EGL context. 160 | */ 161 | public void release() { 162 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 163 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); 164 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 165 | EGL14.eglReleaseThread(); 166 | EGL14.eglTerminate(mEGLDisplay); 167 | } 168 | mEGLDisplay = EGL14.EGL_NO_DISPLAY; 169 | mEGLContext = EGL14.EGL_NO_CONTEXT; 170 | mEGLSurface = EGL14.EGL_NO_SURFACE; 171 | 172 | mSurface.release(); 173 | 174 | mTextureRender = null; 175 | mSurface = null; 176 | EncodeSurface=null; 177 | mSurfaceTexture = null; 178 | } 179 | 180 | public void setPresentationTime(long nsecs) { 181 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurfaceEncoder, nsecs); 182 | checkEglError("eglPresentationTimeANDROID"); 183 | } 184 | 185 | 186 | public boolean swapBuffers() { 187 | boolean result = EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurfaceEncoder); 188 | checkEglError("eglSwapBuffers"); 189 | return result; 190 | } 191 | 192 | private EGLConfig getConfig(int version) { 193 | int renderableType = EGL14.EGL_OPENGL_ES2_BIT; 194 | if (version >= 3) { 195 | renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; 196 | } 197 | 198 | // The actual surface is generally RGBA or RGBX, so situationally omitting alpha 199 | // doesn't really help. It can also lead to a huge performance hit on glReadPixels() 200 | // when reading into a GL_RGBA buffer. 201 | int[] attribList = { 202 | EGL14.EGL_RED_SIZE, 8, 203 | EGL14.EGL_GREEN_SIZE, 8, 204 | EGL14.EGL_BLUE_SIZE, 8, 205 | EGL14.EGL_ALPHA_SIZE, 8, 206 | EGL14.EGL_RENDERABLE_TYPE, renderableType, 207 | EGL14.EGL_NONE, 0, // placeholder for recordable [@-3] 208 | EGL14.EGL_NONE 209 | }; 210 | 211 | EGLConfig[] configs = new EGLConfig[1]; 212 | int[] numConfigs = new int[1]; 213 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 214 | numConfigs, 0)) { 215 | Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); 216 | return null; 217 | } 218 | return configs[0]; 219 | } 220 | 221 | 222 | /** 223 | * Makes our EGL context and surface current. 224 | */ 225 | public void makeCurrent() { 226 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 227 | throw new RuntimeException("eglMakeCurrent failed"); 228 | } 229 | } 230 | 231 | 232 | public void makeCurrent(int index) { 233 | 234 | if (index==0) 235 | { 236 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 237 | throw new RuntimeException("eglMakeCurrent failed"); 238 | } 239 | }else 240 | { 241 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurfaceEncoder, mEGLSurfaceEncoder, mEGLContextEncoder)) { 242 | throw new RuntimeException("eglMakeCurrent failed"); 243 | } 244 | } 245 | 246 | } 247 | 248 | /** 249 | * Returns the Surface. 250 | */ 251 | public Surface getSurface() { 252 | return mSurface; 253 | } 254 | 255 | 256 | public void awaitNewImage() { 257 | final int TIMEOUT_MS = 2500; 258 | 259 | synchronized (mFrameSyncObject) { 260 | while (!mFrameAvailable) { 261 | try { 262 | // Wait for onFrameAvailable() to signal us. Use a timeout to avoid 263 | // stalling the test if it doesn't arrive. 264 | mFrameSyncObject.wait(TIMEOUT_MS); 265 | if (!mFrameAvailable) { 266 | // TODO: if "spurious wakeup", continue while loop 267 | throw new RuntimeException("frame wait timed out"); 268 | } 269 | } catch (InterruptedException ie) { 270 | // shouldn't happen 271 | throw new RuntimeException(ie); 272 | } 273 | } 274 | mFrameAvailable = false; 275 | } 276 | 277 | // Latch the data. 278 | mTextureRender.checkGlError("before updateTexImage"); 279 | mSurfaceTexture.updateTexImage(); 280 | } 281 | 282 | /** 283 | * Draws the data from SurfaceTexture onto the current EGL surface. 284 | * 285 | * @param invert if set, render the image with Y inverted (0,0 in top left) 286 | */ 287 | public void drawImage(boolean invert) { 288 | mTextureRender.drawFrame(mSurfaceTexture, invert); 289 | } 290 | 291 | // SurfaceTexture callback 292 | @Override 293 | public void onFrameAvailable(SurfaceTexture st) { 294 | if (VERBOSE) Log.d(TAG, "new frame available"); 295 | synchronized (mFrameSyncObject) { 296 | if (mFrameAvailable) { 297 | throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); 298 | } 299 | mFrameAvailable = true; 300 | mFrameSyncObject.notifyAll(); 301 | } 302 | } 303 | 304 | 305 | private void checkEglError(String msg) { 306 | int error; 307 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 308 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/EncodeDecodeSurface.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaFormat; 5 | import android.util.Log; 6 | 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | 10 | /** 11 | * Created by guoheng on 2016/8/31. 12 | */ 13 | public class EncodeDecodeSurface { 14 | 15 | private static final String TAG = "EncodeDecodeSurface"; 16 | private static final boolean VERBOSE = false; // lots of logging 17 | 18 | private static final int MAX_FRAMES = 400; // stop extracting after this many 19 | 20 | SurfaceDecoder SDecoder=new SurfaceDecoder(); 21 | SurfaceEncoder SEncoder=new SurfaceEncoder(); 22 | 23 | /** test entry point */ 24 | public void testEncodeDecodeSurface() throws Throwable { 25 | EncodeDecodeSurfaceWrapper.runTest(this); 26 | } 27 | 28 | private static class EncodeDecodeSurfaceWrapper implements Runnable { 29 | private Throwable mThrowable; 30 | private EncodeDecodeSurface mTest; 31 | 32 | private EncodeDecodeSurfaceWrapper(EncodeDecodeSurface test) { 33 | mTest = test; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | try { 39 | mTest.Prepare(); 40 | } catch (Throwable th) { 41 | mThrowable = th; 42 | } 43 | } 44 | 45 | /** Entry point. */ 46 | public static void runTest(EncodeDecodeSurface obj) throws Throwable { 47 | EncodeDecodeSurfaceWrapper wrapper = new EncodeDecodeSurfaceWrapper(obj); 48 | Thread th = new Thread(wrapper, "codec test"); 49 | th.start(); 50 | //th.join(); 51 | if (wrapper.mThrowable != null) { 52 | throw wrapper.mThrowable; 53 | } 54 | } 55 | } 56 | 57 | private void Prepare() throws IOException { 58 | try { 59 | 60 | SEncoder.VideoEncodePrepare(); 61 | SDecoder.SurfaceDecoderPrePare(SEncoder.getEncoderSurface()); 62 | doExtract(); 63 | } finally { 64 | SDecoder.release(); 65 | SEncoder.release(); 66 | } 67 | } 68 | 69 | void doExtract() throws IOException { 70 | final int TIMEOUT_USEC = 10000; 71 | ByteBuffer[] decoderInputBuffers = SDecoder.decoder.getInputBuffers(); 72 | MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 73 | int inputChunk = 0; 74 | int decodeCount = 0; 75 | long frameSaveTime = 0; 76 | 77 | boolean outputDone = false; 78 | boolean inputDone = false; 79 | while (!outputDone) { 80 | if (VERBOSE) Log.d(TAG, "loop"); 81 | 82 | // Feed more data to the decoder. 83 | if (!inputDone) { 84 | int inputBufIndex = SDecoder.decoder.dequeueInputBuffer(TIMEOUT_USEC); 85 | if (inputBufIndex >= 0) { 86 | ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex]; 87 | int chunkSize = SDecoder.extractor.readSampleData(inputBuf, 0); 88 | if (chunkSize < 0) { 89 | SDecoder.decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, 90 | MediaCodec.BUFFER_FLAG_END_OF_STREAM); 91 | inputDone = true; 92 | if (VERBOSE) Log.d(TAG, "sent input EOS"); 93 | } else { 94 | if (SDecoder.extractor.getSampleTrackIndex() != SDecoder.DecodetrackIndex) { 95 | Log.w(TAG, "WEIRD: got sample from track " + 96 | SDecoder.extractor.getSampleTrackIndex() + ", expected " + SDecoder.DecodetrackIndex); 97 | } 98 | long presentationTimeUs = SDecoder.extractor.getSampleTime(); 99 | SDecoder.decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, 100 | presentationTimeUs, 0 /*flags*/); 101 | if (VERBOSE) { 102 | Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" + 103 | chunkSize); 104 | } 105 | inputChunk++; 106 | SDecoder.extractor.advance(); 107 | } 108 | } else { 109 | if (VERBOSE) Log.d(TAG, "input buffer not available"); 110 | } 111 | } 112 | 113 | if (!outputDone) { 114 | int decoderStatus = SDecoder.decoder.dequeueOutputBuffer(info, TIMEOUT_USEC); 115 | if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 116 | // no output available yet 117 | if (VERBOSE) Log.d(TAG, "no output from decoder available"); 118 | } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 119 | // not important for us, since we're using Surface 120 | if (VERBOSE) Log.d(TAG, "decoder output buffers changed"); 121 | } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 122 | MediaFormat newFormat = SDecoder.decoder.getOutputFormat(); 123 | if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat); 124 | } else if (decoderStatus < 0) { 125 | 126 | } else { // decoderStatus >= 0 127 | if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus + 128 | " (size=" + info.size + ")"); 129 | if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 130 | if (VERBOSE) Log.d(TAG, "output EOS"); 131 | outputDone = true; 132 | } 133 | 134 | boolean doRender = (info.size != 0); 135 | 136 | SDecoder.decoder.releaseOutputBuffer(decoderStatus, doRender); 137 | if (doRender) { 138 | if (VERBOSE) Log.d(TAG, "awaiting decode of frame " + decodeCount); 139 | 140 | if (decodeCount < MAX_FRAMES) { 141 | SDecoder.outputSurface.makeCurrent(1); 142 | SDecoder.outputSurface.awaitNewImage(); 143 | SDecoder.outputSurface.drawImage(true); 144 | 145 | SEncoder.drainEncoder(false); 146 | SDecoder.outputSurface.setPresentationTime(computePresentationTimeNsec(decodeCount)); 147 | SDecoder.outputSurface.swapBuffers(); 148 | 149 | } 150 | decodeCount++; 151 | } 152 | 153 | } 154 | } 155 | } 156 | 157 | SEncoder.drainEncoder(true); 158 | int numSaved = (MAX_FRAMES < decodeCount) ? MAX_FRAMES : decodeCount; 159 | Log.d(TAG, "Saving " + numSaved + " frames took " + 160 | (frameSaveTime / numSaved / 1000) + " us per frame"); 161 | } 162 | 163 | 164 | private static long computePresentationTimeNsec(int frameIndex) { 165 | final long ONE_BILLION = 1000000000; 166 | return frameIndex * ONE_BILLION / 30; 167 | } 168 | 169 | 170 | } 171 | 172 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/GlUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 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.android.guoheng.decodeencodemp4; 18 | 19 | import android.opengl.GLES20; 20 | import android.opengl.GLES30; 21 | import android.opengl.Matrix; 22 | import android.util.Log; 23 | 24 | import java.nio.ByteBuffer; 25 | import java.nio.ByteOrder; 26 | import java.nio.FloatBuffer; 27 | 28 | /** 29 | * Some OpenGL utility functions. 30 | */ 31 | public class GlUtil { 32 | public static final String TAG = "EncodeDecodeSurface"; 33 | 34 | /** Identity matrix for general use. Don't modify or life will get weird. */ 35 | public static final float[] IDENTITY_MATRIX; 36 | static { 37 | 38 | IDENTITY_MATRIX = new float[16]; 39 | Matrix.setIdentityM(IDENTITY_MATRIX, 0); 40 | //Matrix.scaleM(IDENTITY_MATRIX,0,0.5f,0.5f,1); 41 | } 42 | 43 | private static final int SIZEOF_FLOAT = 4; 44 | 45 | 46 | private GlUtil() {} // do not instantiate 47 | 48 | /** 49 | * Creates a new program from the supplied vertex and fragment shaders. 50 | * 51 | * @return A handle to the program, or 0 on failure. 52 | */ 53 | public static int createProgram(String vertexSource, String fragmentSource) { 54 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 55 | if (vertexShader == 0) { 56 | return 0; 57 | } 58 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 59 | if (pixelShader == 0) { 60 | return 0; 61 | } 62 | 63 | int program = GLES20.glCreateProgram(); 64 | checkGlError("glCreateProgram"); 65 | if (program == 0) { 66 | Log.e(TAG, "Could not create program"); 67 | } 68 | GLES20.glAttachShader(program, vertexShader); 69 | checkGlError("glAttachShader"); 70 | GLES20.glAttachShader(program, pixelShader); 71 | checkGlError("glAttachShader"); 72 | GLES20.glLinkProgram(program); 73 | int[] linkStatus = new int[1]; 74 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 75 | if (linkStatus[0] != GLES20.GL_TRUE) { 76 | Log.e(TAG, "Could not link program: "); 77 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 78 | GLES20.glDeleteProgram(program); 79 | program = 0; 80 | } 81 | return program; 82 | } 83 | 84 | /** 85 | * Compiles the provided shader source. 86 | * 87 | * @return A handle to the shader, or 0 on failure. 88 | */ 89 | public static int loadShader(int shaderType, String source) { 90 | int shader = GLES20.glCreateShader(shaderType); 91 | checkGlError("glCreateShader type=" + shaderType); 92 | GLES20.glShaderSource(shader, source); 93 | GLES20.glCompileShader(shader); 94 | int[] compiled = new int[1]; 95 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 96 | if (compiled[0] == 0) { 97 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 98 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); 99 | GLES20.glDeleteShader(shader); 100 | shader = 0; 101 | } 102 | return shader; 103 | } 104 | 105 | /** 106 | * Checks to see if a GLES error has been raised. 107 | */ 108 | public static void checkGlError(String op) { 109 | int error = GLES20.glGetError(); 110 | if (error != GLES20.GL_NO_ERROR) { 111 | String msg = op + ": glError 0x" + Integer.toHexString(error); 112 | Log.e(TAG, msg); 113 | throw new RuntimeException(msg); 114 | } 115 | } 116 | 117 | /** 118 | * Checks to see if the location we obtained is valid. GLES returns -1 if a label 119 | * could not be found, but does not set the GL error. 120 | *

121 | * Throws a RuntimeException if the location is invalid. 122 | */ 123 | public static void checkLocation(int location, String label) { 124 | if (location < 0) { 125 | throw new RuntimeException("Unable to locate '" + label + "' in program"); 126 | } 127 | } 128 | 129 | /** 130 | * Creates a texture from raw data. 131 | * 132 | * @param data Image data, in a "direct" ByteBuffer. 133 | * @param width Texture width, in pixels (not bytes). 134 | * @param height Texture height, in pixels. 135 | * @param format Image data format (use constant appropriate for glTexImage2D(), e.g. GL_RGBA). 136 | * @return Handle to texture. 137 | */ 138 | public static int createImageTexture(ByteBuffer data, int width, int height, int format) { 139 | int[] textureHandles = new int[1]; 140 | int textureHandle; 141 | 142 | GLES20.glGenTextures(1, textureHandles, 0); 143 | textureHandle = textureHandles[0]; 144 | GlUtil.checkGlError("glGenTextures"); 145 | 146 | // Bind the texture handle to the 2D texture target. 147 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle); 148 | 149 | // Configure min/mag filtering, i.e. what scaling method do we use if what we're rendering 150 | // is smaller or larger than the source image. 151 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, 152 | GLES20.GL_LINEAR); 153 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, 154 | GLES20.GL_LINEAR); 155 | GlUtil.checkGlError("loadImageTexture"); 156 | 157 | // Load the data from the buffer into the texture handle. 158 | GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, /*level*/ 0, format, 159 | width, height, /*border*/ 0, format, GLES20.GL_UNSIGNED_BYTE, data); 160 | GlUtil.checkGlError("loadImageTexture"); 161 | 162 | return textureHandle; 163 | } 164 | 165 | /** 166 | * Allocates a direct float buffer, and populates it with the float array data. 167 | */ 168 | public static FloatBuffer createFloatBuffer(float[] coords) { 169 | // Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it. 170 | ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT); 171 | bb.order(ByteOrder.nativeOrder()); 172 | FloatBuffer fb = bb.asFloatBuffer(); 173 | fb.put(coords); 174 | fb.position(0); 175 | return fb; 176 | } 177 | 178 | /** 179 | * Writes GL version info to the log. 180 | */ 181 | public static void logVersionInfo() { 182 | Log.i(TAG, "vendor : " + GLES20.glGetString(GLES20.GL_VENDOR)); 183 | Log.i(TAG, "renderer: " + GLES20.glGetString(GLES20.GL_RENDERER)); 184 | Log.i(TAG, "version : " + GLES20.glGetString(GLES20.GL_VERSION)); 185 | 186 | if (false) { 187 | int[] values = new int[1]; 188 | GLES30.glGetIntegerv(GLES30.GL_MAJOR_VERSION, values, 0); 189 | int majorVersion = values[0]; 190 | GLES30.glGetIntegerv(GLES30.GL_MINOR_VERSION, values, 0); 191 | int minorVersion = values[0]; 192 | if (GLES30.glGetError() == GLES30.GL_NO_ERROR) { 193 | Log.i(TAG, "iversion: " + majorVersion + "." + minorVersion); 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.support.v7.app.AppCompatActivity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | 13 | EncodeDecodeSurface test=new EncodeDecodeSurface(); 14 | try { 15 | test.testEncodeDecodeSurface(); 16 | }catch (Throwable a){ 17 | a.printStackTrace(); 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/STextureRender.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.graphics.SurfaceTexture; 4 | import android.opengl.GLES11Ext; 5 | import android.opengl.GLES20; 6 | import android.opengl.Matrix; 7 | import android.util.Log; 8 | 9 | import java.nio.FloatBuffer; 10 | 11 | /** 12 | * Created by guoheng on 2016/8/31. 13 | */ 14 | public class STextureRender { 15 | private static final int FLOAT_SIZE_BYTES = 4; 16 | private static final String TAG = "STextureRendering"; 17 | 18 | private static final float TRANSFORM_RECTANGLE_COORDS[] = { 19 | -0.914337f, -0.949318f,1.0f, 20 | 0.494437f, -0.683502f,1.0f, 21 | -0.895833f, 0.62963f,1.0f, 22 | 0.76524f, 0.689287f,1.0f 23 | 24 | }; 25 | 26 | 27 | private static final float TRANSFORM_RECTANGLE_TEX_COORDS[] = { 28 | 0f, 0.822368f, 0.822368f,1.0f, 29 | 0.710227f, 0.710227f, 0.710227f,1.0f, 30 | 0f, 0f, 1f,1.0f, 31 | 0.838926f, 0f, 0.838926f,1.0f 32 | 33 | }; 34 | 35 | private static final float FULL_RECTANGLE_COORDS[] = { 36 | -1.0f, -1.0f,1.0f, // 0 bottom left 37 | 1.0f, -1.0f,1.0f, // 1 bottom right 38 | -1.0f, 1.0f,1.0f, // 2 top left 39 | 1.0f, 1.0f,1.0f // 3 top right 40 | }; 41 | 42 | private static final float FULL_RECTANGLE_TEX_COORDS[] = { 43 | 0.0f, 1.0f, 1f,1.0f, // 0 bottom left 44 | 1.0f, 1.0f,1f,1.0f, // 1 bottom right 45 | 0.0f, 0.0f, 1f,1.0f, // 2 top left 46 | 1.0f, 0.0f ,1f,1.0f // 3 top right 47 | }; 48 | 49 | private static final FloatBuffer FULL_RECTANGLE_BUF = 50 | GlUtil.createFloatBuffer(FULL_RECTANGLE_COORDS); 51 | private static final FloatBuffer FULL_RECTANGLE_TEX_BUF = 52 | GlUtil.createFloatBuffer(FULL_RECTANGLE_TEX_COORDS); 53 | 54 | 55 | 56 | private static final FloatBuffer TRANSFORM_RECTANGLE_BUF = 57 | GlUtil.createFloatBuffer(TRANSFORM_RECTANGLE_COORDS); 58 | private static final FloatBuffer TRANSFORM_RECTANGLE_TEX_BUF = 59 | GlUtil.createFloatBuffer(TRANSFORM_RECTANGLE_TEX_COORDS); 60 | 61 | 62 | 63 | private FloatBuffer mTriangleVertices; 64 | 65 | private static final String VERTEX_SHADER = 66 | "uniform mat4 uMVPMatrix;\n" + 67 | "uniform mat4 uSTMatrix;\n" + 68 | "attribute vec4 aPosition;\n" + 69 | "attribute vec4 aTextureCoord;\n" + 70 | "varying vec4 vTextureCoord;\n" + 71 | "void main() {\n" + 72 | " gl_Position = uMVPMatrix * aPosition;\n" + 73 | " vTextureCoord = uSTMatrix * aTextureCoord;\n" + 74 | "}\n"; 75 | 76 | private static final String FRAGMENT_SHADER = 77 | "#extension GL_OES_EGL_image_external : require\n" + 78 | "precision mediump float;\n" + // highp here doesn't seem to matter 79 | "varying vec4 vTextureCoord;\n" + 80 | "uniform samplerExternalOES sTexture;\n" + 81 | "void main() {\n" + 82 | " gl_FragColor = texture2D(sTexture, vTextureCoord.xy/vTextureCoord.z);" + 83 | "}\n"; 84 | 85 | 86 | 87 | 88 | private float[] mMVPMatrix = new float[16]; 89 | private float[] mSTMatrix = new float[16]; 90 | 91 | private int mProgram; 92 | private int mTextureID = -12345; 93 | private int muMVPMatrixHandle; 94 | private int muSTMatrixHandle; 95 | private int maPositionHandle; 96 | private int maTextureHandle; 97 | 98 | public STextureRender() { 99 | Matrix.setIdentityM(mSTMatrix, 0); 100 | } 101 | 102 | public int getTextureId() { 103 | return mTextureID; 104 | } 105 | 106 | /** 107 | * Draws the external texture in SurfaceTexture onto the current EGL surface. 108 | */ 109 | public void drawFrame(SurfaceTexture st, boolean invert) { 110 | checkGlError("onDrawFrame start"); 111 | st.getTransformMatrix(mSTMatrix); 112 | if (invert) { 113 | mSTMatrix[5] = -mSTMatrix[5]; 114 | mSTMatrix[13] = 1.0f - mSTMatrix[13]; 115 | } 116 | 117 | // (optional) clear to green so we can see if we're failing to set pixels 118 | GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); 119 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 120 | 121 | GLES20.glUseProgram(mProgram); 122 | checkGlError("glUseProgram"); 123 | 124 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 125 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); 126 | 127 | 128 | 129 | // Enable the "aPosition" vertex attribute. 130 | GLES20.glEnableVertexAttribArray(maPositionHandle); 131 | GlUtil.checkGlError("glEnableVertexAttribArray"); 132 | 133 | // Connect vertexBuffer to "aPosition". 134 | GLES20.glVertexAttribPointer(maPositionHandle, 3, 135 | GLES20.GL_FLOAT, false, 3*FLOAT_SIZE_BYTES, TRANSFORM_RECTANGLE_BUF); 136 | GlUtil.checkGlError("glVertexAttribPointer"); 137 | 138 | // Enable the "aTextureCoord" vertex attribute. 139 | GLES20.glEnableVertexAttribArray(maTextureHandle); 140 | GlUtil.checkGlError("glEnableVertexAttribArray"); 141 | 142 | // Connect texBuffer to "aTextureCoord". 143 | GLES20.glVertexAttribPointer(maTextureHandle, 4, 144 | GLES20.GL_FLOAT, false, 4*FLOAT_SIZE_BYTES, TRANSFORM_RECTANGLE_TEX_BUF); 145 | GlUtil.checkGlError("glVertexAttribPointer"); 146 | 147 | Matrix.setIdentityM(mMVPMatrix, 0); 148 | GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); 149 | GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); 150 | 151 | 152 | // Draw the rect. 153 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 154 | GlUtil.checkGlError("glDrawArrays"); 155 | 156 | 157 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 158 | checkGlError("glDrawArrays"); 159 | 160 | 161 | // Done -- disable vertex array, texture, and program. 162 | GLES20.glDisableVertexAttribArray(maPositionHandle); 163 | GLES20.glDisableVertexAttribArray(maTextureHandle); 164 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); 165 | GLES20.glUseProgram(0); 166 | 167 | } 168 | 169 | 170 | /** 171 | * Initializes GL state. Call this after the EGL surface has been created and made current. 172 | */ 173 | public void surfaceCreated() { 174 | mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); 175 | if (mProgram == 0) { 176 | throw new RuntimeException("failed creating program"); 177 | } 178 | 179 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 180 | checkLocation(maPositionHandle, "aPosition"); 181 | maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); 182 | checkLocation(maTextureHandle, "aTextureCoord"); 183 | 184 | muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 185 | checkLocation(muMVPMatrixHandle, "uMVPMatrix"); 186 | muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); 187 | checkLocation(muSTMatrixHandle, "uSTMatrix"); 188 | 189 | int[] textures = new int[1]; 190 | GLES20.glGenTextures(1, textures, 0); 191 | 192 | mTextureID = textures[0]; 193 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); 194 | checkGlError("glBindTexture mTextureID"); 195 | 196 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 197 | GLES20.GL_NEAREST); 198 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 199 | GLES20.GL_LINEAR); 200 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, 201 | GLES20.GL_CLAMP_TO_EDGE); 202 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, 203 | GLES20.GL_CLAMP_TO_EDGE); 204 | checkGlError("glTexParameter"); 205 | } 206 | 207 | /** 208 | * Replaces the fragment shader. Pass in null to reset to default. 209 | */ 210 | public void changeFragmentShader(String fragmentShader) { 211 | if (fragmentShader == null) { 212 | fragmentShader = FRAGMENT_SHADER; 213 | } 214 | GLES20.glDeleteProgram(mProgram); 215 | mProgram = createProgram(VERTEX_SHADER, fragmentShader); 216 | if (mProgram == 0) { 217 | throw new RuntimeException("failed creating program"); 218 | } 219 | } 220 | 221 | private int loadShader(int shaderType, String source) { 222 | int shader = GLES20.glCreateShader(shaderType); 223 | checkGlError("glCreateShader type=" + shaderType); 224 | GLES20.glShaderSource(shader, source); 225 | GLES20.glCompileShader(shader); 226 | int[] compiled = new int[1]; 227 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 228 | if (compiled[0] == 0) { 229 | Log.e(TAG, "Could not compile shader " + shaderType + ":"); 230 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader)); 231 | GLES20.glDeleteShader(shader); 232 | shader = 0; 233 | } 234 | return shader; 235 | } 236 | 237 | private int createProgram(String vertexSource, String fragmentSource) { 238 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 239 | if (vertexShader == 0) { 240 | return 0; 241 | } 242 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 243 | if (pixelShader == 0) { 244 | return 0; 245 | } 246 | 247 | int program = GLES20.glCreateProgram(); 248 | if (program == 0) { 249 | Log.e(TAG, "Could not create program"); 250 | } 251 | GLES20.glAttachShader(program, vertexShader); 252 | checkGlError("glAttachShader"); 253 | GLES20.glAttachShader(program, pixelShader); 254 | checkGlError("glAttachShader"); 255 | GLES20.glLinkProgram(program); 256 | int[] linkStatus = new int[1]; 257 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 258 | if (linkStatus[0] != GLES20.GL_TRUE) { 259 | Log.e(TAG, "Could not link program: "); 260 | Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 261 | GLES20.glDeleteProgram(program); 262 | program = 0; 263 | } 264 | return program; 265 | } 266 | 267 | public void checkGlError(String op) { 268 | int error; 269 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 270 | Log.e(TAG, op + ": glError " + error); 271 | throw new RuntimeException(op + ": glError " + error); 272 | } 273 | } 274 | 275 | public static void checkLocation(int location, String label) { 276 | if (location < 0) { 277 | throw new RuntimeException("Unable to locate '" + label + "' in program"); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/SurfaceDecoder.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaExtractor; 5 | import android.media.MediaFormat; 6 | import android.os.Environment; 7 | import android.util.Log; 8 | import android.view.Surface; 9 | 10 | import java.io.File; 11 | import java.io.FileNotFoundException; 12 | import java.io.IOException; 13 | 14 | /** 15 | * Created by guoheng on 2016/9/1. 16 | */ 17 | public class SurfaceDecoder { 18 | 19 | private static final String TAG = "EncodeDecodeSurface"; 20 | private static final boolean VERBOSE = false; // lots of logging 21 | 22 | int saveWidth = 1920; 23 | int saveHeight = 1080; 24 | 25 | MediaCodec decoder = null; 26 | 27 | CodecOutputSurface outputSurface = null; 28 | 29 | MediaExtractor extractor = null; 30 | 31 | public int DecodetrackIndex; 32 | 33 | // where to find files (note: requires WRITE_EXTERNAL_STORAGE permission) 34 | private static final File FILES_DIR = Environment.getExternalStorageDirectory(); 35 | private static final String INPUT_FILE = "1.mp4"; 36 | 37 | 38 | void SurfaceDecoderPrePare(Surface encodersurface) 39 | { 40 | try { 41 | File inputFile = new File(FILES_DIR, INPUT_FILE); // must be an absolute path 42 | 43 | if (!inputFile.canRead()) { 44 | throw new FileNotFoundException("Unable to read " + inputFile); 45 | } 46 | extractor = new MediaExtractor(); 47 | extractor.setDataSource(inputFile.toString()); 48 | DecodetrackIndex = selectTrack(extractor); 49 | if (DecodetrackIndex < 0) { 50 | throw new RuntimeException("No video track found in " + inputFile); 51 | } 52 | extractor.selectTrack(DecodetrackIndex); 53 | 54 | MediaFormat format = extractor.getTrackFormat(DecodetrackIndex); 55 | if (VERBOSE) { 56 | Log.d(TAG, "Video size is " + format.getInteger(MediaFormat.KEY_WIDTH) + "x" + 57 | format.getInteger(MediaFormat.KEY_HEIGHT)); 58 | } 59 | 60 | outputSurface = new CodecOutputSurface(saveWidth, saveHeight,encodersurface); 61 | 62 | String mime = format.getString(MediaFormat.KEY_MIME); 63 | decoder = MediaCodec.createDecoderByType(mime); 64 | decoder.configure(format, outputSurface.getSurface(), null, 0); 65 | decoder.start(); 66 | }catch (IOException e) 67 | { 68 | e.printStackTrace(); 69 | } 70 | 71 | 72 | } 73 | 74 | 75 | private int selectTrack(MediaExtractor extractor) { 76 | // Select the first video track we find, ignore the rest. 77 | int numTracks = extractor.getTrackCount(); 78 | for (int i = 0; i < numTracks; i++) { 79 | MediaFormat format = extractor.getTrackFormat(i); 80 | String mime = format.getString(MediaFormat.KEY_MIME); 81 | if (mime.startsWith("video/")) { 82 | if (VERBOSE) { 83 | Log.d(TAG, "Extractor selected track " + i + " (" + mime + "): " + format); 84 | } 85 | return i; 86 | } 87 | } 88 | 89 | return -1; 90 | } 91 | 92 | 93 | void release() 94 | { 95 | if (decoder != null) { 96 | decoder.stop(); 97 | decoder.release(); 98 | decoder = null; 99 | } 100 | if (extractor != null) { 101 | extractor.release(); 102 | extractor = null; 103 | } 104 | if (outputSurface != null) { 105 | outputSurface.release(); 106 | outputSurface = null; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/android/guoheng/decodeencodemp4/SurfaceEncoder.java: -------------------------------------------------------------------------------- 1 | package com.android.guoheng.decodeencodemp4; 2 | 3 | import android.media.MediaCodec; 4 | import android.media.MediaCodecInfo; 5 | import android.media.MediaFormat; 6 | import android.media.MediaMuxer; 7 | import android.os.Environment; 8 | import android.util.Log; 9 | import android.view.Surface; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.nio.ByteBuffer; 14 | 15 | /** 16 | * Created by guoheng on 2016/9/1. 17 | */ 18 | public class SurfaceEncoder { 19 | 20 | private static final String TAG = "EncodeDecodeSurface"; 21 | private static final boolean VERBOSE = false; // lots of logging 22 | private static final String MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding 23 | private static final int WIDTH = 1920; 24 | private static final int HEIGHT = 1080; 25 | private static final int BIT_RATE = 1920*1080*10; // 2Mbps 26 | public static final int FRAME_RATE = 30; // 30fps 27 | private static final int IFRAME_INTERVAL = 30; // 10 seconds between I-frames 28 | 29 | MediaCodec encoder = null; 30 | Surface encodesurface; 31 | private MediaCodec.BufferInfo mBufferInfo; 32 | public MediaMuxer mMuxer; 33 | 34 | public int mTrackIndex; 35 | public boolean mMuxerStarted; 36 | 37 | 38 | public void VideoEncodePrepare() 39 | { 40 | String outputPath = new File(Environment.getExternalStorageDirectory(), 41 | "mytest." + WIDTH + "x" + HEIGHT + ".mp4").toString(); 42 | 43 | mBufferInfo = new MediaCodec.BufferInfo(); 44 | 45 | MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT); 46 | 47 | // Set some properties. Failing to specify some of these can cause the MediaCodec 48 | // configure() call to throw an unhelpful exception. 49 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 50 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); 51 | format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE); 52 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); 53 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); 54 | 55 | 56 | encoder = null; 57 | 58 | try { 59 | encoder = MediaCodec.createEncoderByType(MIME_TYPE); 60 | 61 | encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 62 | encodesurface=encoder.createInputSurface(); 63 | encoder.start(); 64 | mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 65 | 66 | }catch (IOException ioe) { 67 | throw new RuntimeException("failed init encoder", ioe); 68 | } 69 | 70 | mTrackIndex = -1; 71 | mMuxerStarted = false; 72 | 73 | } 74 | 75 | 76 | 77 | public void drainEncoder(boolean endOfStream) { 78 | final int TIMEOUT_USEC = 10000; 79 | if (VERBOSE) Log.d(TAG, "drainEncoder(" + endOfStream + ")"); 80 | 81 | if (endOfStream) { 82 | if (VERBOSE) Log.d(TAG, "sending EOS to encoder"); 83 | encoder.signalEndOfInputStream(); 84 | } 85 | 86 | ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); 87 | while (true) { 88 | int encoderStatus = encoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC); 89 | if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 90 | // no output available yet 91 | if (!endOfStream) { 92 | break; // out of while 93 | } else { 94 | if (VERBOSE) Log.d(TAG, "no output available, spinning to await EOS"); 95 | } 96 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 97 | // not expected for an encoder 98 | encoderOutputBuffers = encoder.getOutputBuffers(); 99 | } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 100 | // should happen before receiving buffers, and should only happen once 101 | if (mMuxerStarted) { 102 | throw new RuntimeException("format changed twice"); 103 | } 104 | MediaFormat newFormat = encoder.getOutputFormat(); 105 | Log.d(TAG, "encoder output format changed: " + newFormat); 106 | 107 | // now that we have the Magic Goodies, start the muxer 108 | mTrackIndex = mMuxer.addTrack(newFormat); 109 | mMuxer.start(); 110 | mMuxerStarted = true; 111 | } else if (encoderStatus < 0) { 112 | Log.w(TAG, "unexpected result from encoder.dequeueOutputBuffer: " + 113 | encoderStatus); 114 | // let's ignore it 115 | } else { 116 | ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; 117 | if (encodedData == null) { 118 | throw new RuntimeException("encoderOutputBuffer " + encoderStatus + 119 | " was null"); 120 | } 121 | 122 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { 123 | // The codec config data was pulled out and fed to the muxer when we got 124 | // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. 125 | if (VERBOSE) Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); 126 | 127 | MediaFormat format = 128 | MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT); 129 | format.setByteBuffer("csd-0", encodedData); 130 | 131 | mBufferInfo.size = 0; 132 | } 133 | 134 | if (mBufferInfo.size != 0) { 135 | if (!mMuxerStarted) { 136 | throw new RuntimeException("muxer hasn't started"); 137 | } 138 | 139 | // adjust the ByteBuffer values to match BufferInfo (not needed?) 140 | encodedData.position(mBufferInfo.offset); 141 | encodedData.limit(mBufferInfo.offset + mBufferInfo.size); 142 | 143 | mMuxer.writeSampleData(mTrackIndex, encodedData, mBufferInfo); 144 | if (VERBOSE) Log.d(TAG, "sent " + mBufferInfo.size + " bytes to muxer"); 145 | } 146 | 147 | encoder.releaseOutputBuffer(encoderStatus, false); 148 | 149 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 150 | if (!endOfStream) { 151 | Log.w(TAG, "reached end of stream unexpectedly"); 152 | } else { 153 | if (VERBOSE) Log.d(TAG, "end of stream reached"); 154 | } 155 | break; // out of while 156 | } 157 | } 158 | } 159 | } 160 | 161 | 162 | void release() 163 | { 164 | if (encoder!=null) 165 | { 166 | encoder.stop(); 167 | encoder.release(); 168 | } 169 | if (mMuxer != null) { 170 | mMuxer.stop(); 171 | mMuxer.release(); 172 | mMuxer = null; 173 | } 174 | } 175 | 176 | 177 | 178 | 179 | Surface getEncoderSurface() 180 | { 181 | return encodesurface; 182 | } 183 | 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 |