├── FrameGrabber
├── .gitignore
├── res
│ ├── values
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── values-v11
│ │ └── styles.xml
│ └── values-v14
│ │ └── styles.xml
├── libs
│ └── android-support-v4.jar
├── .classpath
├── AndroidManifest.xml
├── src
│ └── com
│ │ └── tam
│ │ ├── gl
│ │ ├── GLUtil.java
│ │ ├── GLHelper.java
│ │ └── TextureRender.java
│ │ ├── utils
│ │ └── BitmapUtil.java
│ │ └── media
│ │ ├── FrameGrabber.java
│ │ └── VideoDecoder.java
├── project.properties
├── proguard-project.txt
└── .project
├── FrameGrabberTest
├── .gitignore
├── ic_launcher-web.png
├── assets
│ └── frameCount.mp4
├── libs
│ └── android-support-v4.jar
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── values-sw600dp
│ │ └── dimens.xml
│ ├── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ ├── menu
│ │ └── main.xml
│ ├── values-sw720dp-land
│ │ └── dimens.xml
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ └── layout
│ │ └── activity_main.xml
├── .classpath
├── project.properties
├── proguard-project.txt
├── .project
├── AndroidManifest.xml
└── src
│ └── com
│ └── tam
│ └── gltest
│ └── MainActivity.java
├── .gitignore
└── README.md
/FrameGrabber/.gitignore:
--------------------------------------------------------------------------------
1 | gen/*
2 | bin/*
3 |
--------------------------------------------------------------------------------
/FrameGrabberTest/.gitignore:
--------------------------------------------------------------------------------
1 | gen/*
2 | bin/*
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | FrameGrabber/gen/*
2 | FrameGrabber/bin/*
3 |
--------------------------------------------------------------------------------
/FrameGrabberTest/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/ic_launcher-web.png
--------------------------------------------------------------------------------
/FrameGrabber/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameGrabber
4 |
5 |
6 |
--------------------------------------------------------------------------------
/FrameGrabberTest/assets/frameCount.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/assets/frameCount.mp4
--------------------------------------------------------------------------------
/FrameGrabber/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabber/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/FrameGrabberTest/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/FrameGrabber/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabber/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabber/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabber/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabber/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabber/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabberTest/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabberTest/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabberTest/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabberTest/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kswlee/FrameGrabber/HEAD/FrameGrabberTest/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values-sw600dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | FrameGrabberTest
5 | Settings
6 | Hello world!
7 |
8 |
9 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values-sw720dp-land/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | 128dp
8 |
9 |
10 |
--------------------------------------------------------------------------------
/FrameGrabber/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | FrameGrabber
2 | ============
3 |
4 | An alternative solution to replace the getFrameAt method of Android MediaMetadataRetriever. FrameGrabber uses MediaCodec to decode video frame and use OpenGL to convert the video frame as RGB Bitmap. As Android MediaMetadataRetriever does not guarantee to return result when calling getFrameAtTime, this FrameGrabber can be used to extract video frame with frame accuracy.
5 |
--------------------------------------------------------------------------------
/FrameGrabber/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/FrameGrabber/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/FrameGrabberTest/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/FrameGrabber/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/gl/GLUtil.java:
--------------------------------------------------------------------------------
1 | package com.tam.gl;
2 |
3 | import android.opengl.EGL14;
4 | import android.util.Log;
5 |
6 | public class GLUtil {
7 | /**
8 | * Checks for EGL errors.
9 | */
10 | public static void checkEglError(String msg) {
11 | boolean failed = false;
12 | int error;
13 | while ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
14 | Log.e("TAG", msg + ": EGL error: 0x" + Integer.toHexString(error));
15 | failed = true;
16 | }
17 | if (failed) {
18 | throw new RuntimeException("EGL error encountered (see log)");
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FrameGrabberTest/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library.reference.1=../FrameGrabber
16 |
--------------------------------------------------------------------------------
/FrameGrabber/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 | android.library=true
16 | android.library.reference.1=../../../../../android_projects/VH2/VH/videoengine/HtcMediaLibrary
17 |
--------------------------------------------------------------------------------
/FrameGrabber/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
14 |
15 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/FrameGrabber/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/FrameGrabberTest/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/utils/BitmapUtil.java:
--------------------------------------------------------------------------------
1 | package com.tam.utils;
2 |
3 | import java.io.FileNotFoundException;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 |
7 | import android.graphics.Bitmap;
8 | import android.graphics.Bitmap.CompressFormat;
9 | import android.graphics.Matrix;
10 |
11 | public class BitmapUtil {
12 | public static void saveBitmap(Bitmap bmp, String path) {
13 | try {
14 | FileOutputStream fos = new FileOutputStream(path);
15 | bmp.compress(CompressFormat.JPEG, 100, fos);
16 | fos.close();
17 | } catch (FileNotFoundException e) {
18 | e.printStackTrace();
19 | } catch (IOException e) {
20 | e.printStackTrace();
21 | }
22 | }
23 |
24 | public static Bitmap flip(Bitmap src) {
25 | Matrix matrix = new Matrix();
26 | matrix.preScale(1.0f, -1.0f);
27 | return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/FrameGrabber/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameGrabber
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/FrameGrabberTest/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | FrameGrabberTest
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/FrameGrabberTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/FrameGrabberTest/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
15 |
16 |
21 |
22 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/media/FrameGrabber.java:
--------------------------------------------------------------------------------
1 | package com.tam.media;
2 |
3 | import com.tam.gl.GLHelper;
4 |
5 | import android.annotation.SuppressLint;
6 | import android.graphics.Bitmap;
7 | import android.graphics.SurfaceTexture;
8 | import android.graphics.SurfaceTexture.OnFrameAvailableListener;
9 | import android.os.Handler;
10 | import android.os.HandlerThread;
11 | import android.util.Log;
12 | import android.view.Surface;
13 |
14 | public class FrameGrabber {
15 | final static String TAG = "FrameGrabber";
16 |
17 | private HandlerThread mGLThread = null;
18 | private Handler mGLHandler = null;
19 | private GLHelper mGLHelper = null;
20 |
21 | private int mDefaultTextureID = 10001;
22 |
23 | private int mWidth = 1920;
24 | private int mHeight = 1080;
25 |
26 | private String mPath = null;
27 |
28 | public FrameGrabber() {
29 | mGLHelper = new GLHelper();
30 | mGLThread = new HandlerThread("FrameGrabber");
31 |
32 | mGLThread.start();
33 | mGLHandler = new Handler(mGLThread.getLooper());
34 | }
35 |
36 | public void setDataSource(String path) {
37 | mPath = path;
38 | }
39 |
40 | public void setTargetSize(int width, int height) {
41 | mWidth = width;
42 | mHeight = height;
43 | }
44 |
45 | public void init() {
46 | mGLHandler.post(new Runnable() {
47 | @Override
48 | public void run() {
49 | SurfaceTexture st = new SurfaceTexture(mDefaultTextureID);
50 | st.setDefaultBufferSize(mWidth, mHeight);
51 | mGLHelper.init(st);
52 | }
53 | });
54 | }
55 |
56 | public void release() {
57 | mGLHandler.post(new Runnable() {
58 | @Override
59 | public void run() {
60 | mGLHelper.release();
61 | mGLThread.quit();
62 | }
63 | });
64 | }
65 |
66 | private Object mWaitBitmap = new Object();
67 | private Bitmap mBitmap = null;
68 | public Bitmap getFrameAtTime(final long frameTime) {
69 | if (null == mPath || mPath.isEmpty()) {
70 | throw new RuntimeException("Illegal State");
71 | }
72 |
73 | mGLHandler.post(new Runnable() {
74 | @Override
75 | public void run() {
76 | getFrameAtTimeImpl(frameTime);
77 | }
78 | });
79 |
80 | synchronized (mWaitBitmap) {
81 | try {
82 | mWaitBitmap.wait();
83 | } catch (InterruptedException e) {
84 | e.printStackTrace();
85 | }
86 | }
87 |
88 | return mBitmap;
89 | }
90 |
91 | @SuppressLint("SdCardPath")
92 | public void getFrameAtTimeImpl(long frameTime) {
93 | final int textureID = mGLHelper.createOESTexture();
94 | final SurfaceTexture st = new SurfaceTexture(textureID);
95 | final Surface surface = new Surface(st);
96 | final VideoDecoder vd = new VideoDecoder(mPath, surface);
97 | st.setOnFrameAvailableListener(new OnFrameAvailableListener() {
98 | @Override
99 | public void onFrameAvailable(SurfaceTexture surfaceTexture) {
100 | Log.i(TAG, "onFrameAvailable");
101 | mGLHelper.drawFrame(st, textureID);
102 | mBitmap = mGLHelper.readPixels(mWidth, mHeight);
103 | synchronized (mWaitBitmap) {
104 | mWaitBitmap.notify();
105 | }
106 |
107 | vd.release();
108 | st.release();
109 | surface.release();
110 | }
111 | });
112 |
113 | if (!vd.prepare(frameTime)) {
114 | mBitmap = null;
115 | synchronized (mWaitBitmap) {
116 | mWaitBitmap.notify();
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/FrameGrabberTest/src/com/tam/gltest/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tam.gltest;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.OutputStream;
8 |
9 | import com.tam.media.FrameGrabber;
10 | import com.tam.utils.BitmapUtil;
11 |
12 | import android.media.MediaMetadataRetriever;
13 | import android.os.Bundle;
14 | import android.annotation.SuppressLint;
15 | import android.app.Activity;
16 | import android.content.res.AssetManager;
17 | import android.graphics.Bitmap;
18 | import android.util.Log;
19 | import android.view.View;
20 | import android.view.View.OnClickListener;
21 | import android.widget.Button;
22 | import android.widget.ImageView;
23 |
24 | public class MainActivity extends Activity {
25 | private final static String TAG = "MainActivity";
26 | @SuppressLint("SdCardPath")
27 | private final static String VIDEO_CONTENT = "/sdcard/frameCount.mp4";
28 |
29 | private FrameGrabber mFrameGrabber = null;
30 | private Button mButton = null;
31 | private ImageView mImageView = null;
32 |
33 | @Override
34 | protected void onCreate(Bundle savedInstanceState) {
35 | Log.i(TAG, "onCreate");
36 | super.onCreate(savedInstanceState);
37 | setContentView(R.layout.activity_main);
38 |
39 | // UI Setup
40 | initUIControls();
41 | }
42 |
43 | private void initUIControls() {
44 | mButton = (Button) findViewById(R.id.button);
45 | mButton.setOnClickListener(new OnClickListener() {
46 | @Override
47 | public void onClick(View v) {
48 | testFrameGrabber();
49 | }
50 | });
51 |
52 | mImageView = (ImageView) findViewById(R.id.imageView);
53 | }
54 |
55 | @SuppressLint("SdCardPath")
56 | private void testFrameGrabber() {
57 | File videoFile = new File(VIDEO_CONTENT);
58 | if (false == videoFile.exists()) {
59 | copyAssets();
60 | }
61 |
62 | boolean useMMDR = false;
63 |
64 | String video = VIDEO_CONTENT;
65 | int captureFrame = 5;
66 | long captureBegin = System.currentTimeMillis();
67 | Bitmap bmp = useMMDR? getFrameAtTimeByMMDR(video, 33333 * captureFrame) : getFrameAtTimeByFrameGrabber(video, 33333 * captureFrame);
68 | long captureSpends = System.currentTimeMillis() - captureBegin;
69 |
70 | Log.i(TAG, "grab frame spends " + captureSpends + "ms");
71 |
72 | if (null != bmp && null != mImageView) {
73 | mImageView.setImageBitmap(bmp);
74 | } else if (null != bmp)
75 | BitmapUtil.saveBitmap(bmp, String.format("/sdcard/read_%d.jpg", captureFrame));
76 |
77 | if (null != mFrameGrabber)
78 | mFrameGrabber.release();
79 | }
80 |
81 | private Bitmap getFrameAtTimeByMMDR(String path, long time) {
82 | MediaMetadataRetriever mmr = new MediaMetadataRetriever();
83 | mmr.setDataSource(path);
84 | Bitmap bmp = mmr.getFrameAtTime(time, MediaMetadataRetriever.OPTION_CLOSEST);
85 | mmr.release();
86 | return bmp;
87 | }
88 |
89 | private Bitmap getFrameAtTimeByFrameGrabber(String path, long time) {
90 | mFrameGrabber = new FrameGrabber();
91 | mFrameGrabber.setDataSource(path);
92 | mFrameGrabber.setTargetSize(1280, 720);
93 | mFrameGrabber.init();
94 | return mFrameGrabber.getFrameAtTime(time);
95 | }
96 |
97 | private void copyAssets() {
98 | AssetManager assetManager = getAssets();
99 | String[] files = null;
100 | try {
101 | files = assetManager.list("");
102 | } catch (IOException e) {
103 | Log.e(TAG, "Failed to get asset file list.", e);
104 | }
105 | for(String filename : files) {
106 | InputStream in = null;
107 | OutputStream out = null;
108 | try {
109 | in = assetManager.open(filename);
110 | File outFile = new File(VIDEO_CONTENT);
111 | out = new FileOutputStream(outFile);
112 | copyFile(in, out);
113 | in.close();
114 | in = null;
115 | out.flush();
116 | out.close();
117 | out = null;
118 | } catch(IOException e) {
119 | Log.e(TAG, "Failed to copy asset file: " + filename, e);
120 | }
121 | }
122 | }
123 |
124 | private void copyFile(InputStream in, OutputStream out) throws IOException {
125 | byte[] buffer = new byte[1024];
126 | int read;
127 | while((read = in.read(buffer)) != -1){
128 | out.write(buffer, 0, read);
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/gl/GLHelper.java:
--------------------------------------------------------------------------------
1 | package com.tam.gl;
2 |
3 | import java.nio.ByteBuffer;
4 |
5 | import android.graphics.Bitmap;
6 | import android.graphics.SurfaceTexture;
7 | import android.opengl.EGL14;
8 | import android.opengl.EGLConfig;
9 | import android.opengl.EGLContext;
10 | import android.opengl.EGLDisplay;
11 | import android.opengl.EGLSurface;
12 | import android.opengl.GLES11Ext;
13 | import android.opengl.GLES20;
14 | import android.opengl.GLUtils;
15 | import android.view.Surface;
16 |
17 | public class GLHelper {
18 | private static final int EGL_RECORDABLE_ANDROID = 0x3142;
19 | private static final int EGL_OPENGL_ES2_BIT = 4;
20 |
21 | private SurfaceTexture mSurfaceTexture;
22 | private TextureRender mTextureRender;
23 |
24 | private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY;
25 | private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT;
26 | private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE;
27 |
28 | public void init(SurfaceTexture st) {
29 | mSurfaceTexture = st;
30 | initGL();
31 |
32 | makeCurrent();
33 | mTextureRender = new TextureRender();
34 | }
35 |
36 | private void initGL() {
37 | mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
38 | if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
39 | throw new RuntimeException("eglGetdisplay failed : " +
40 | GLUtils.getEGLErrorString(EGL14.eglGetError()));
41 | }
42 |
43 | int[] version = new int[2];
44 | if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
45 | mEglDisplay = null;
46 | throw new RuntimeException("unable to initialize EGL14");
47 | }
48 |
49 | // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
50 | // to be able to tell if the frame is reasonable.
51 | int[] attribList = {
52 | EGL14.EGL_RED_SIZE, 8,
53 | EGL14.EGL_GREEN_SIZE, 8,
54 | EGL14.EGL_BLUE_SIZE, 8,
55 | EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
56 | EGL_RECORDABLE_ANDROID, 1,
57 | EGL14.EGL_NONE
58 | };
59 |
60 | EGLConfig[] configs = new EGLConfig[1];
61 | int[] numConfigs = new int[1];
62 | if (!EGL14.eglChooseConfig(mEglDisplay, attribList, 0, configs, 0, configs.length,
63 | numConfigs, 0)) {
64 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
65 | }
66 |
67 | // Configure context for OpenGL ES 2.0.
68 | int[] attrib_list = {
69 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
70 | EGL14.EGL_NONE
71 | };
72 | mEglContext = EGL14.eglCreateContext(mEglDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
73 | attrib_list, 0);
74 | GLUtil.checkEglError("eglCreateContext");
75 | if (mEglContext == null) {
76 | throw new RuntimeException("null context");
77 | }
78 |
79 | // Create a window surface, and attach it to the Surface we received.
80 | int[] surfaceAttribs = {
81 | EGL14.EGL_NONE
82 | };
83 | mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, configs[0], new Surface(mSurfaceTexture),
84 | surfaceAttribs, 0);
85 | GLUtil.checkEglError("eglCreateWindowSurface");
86 | if (mEglSurface == null) {
87 | throw new RuntimeException("surface was null");
88 | }
89 | }
90 |
91 | public void release() {
92 | if (null != mSurfaceTexture)
93 | mSurfaceTexture.release();
94 | }
95 |
96 | public void makeCurrent() {
97 | if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
98 | throw new RuntimeException("eglMakeCurrent failed");
99 | }
100 | }
101 |
102 | public int createOESTexture() {
103 | int[] textures = new int[1];
104 | GLES20.glGenTextures(1, textures, 0);
105 |
106 | int textureID = textures[0];
107 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
108 | GLUtil.checkEglError("glBindTexture textureID");
109 |
110 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
111 | GLES20.GL_NEAREST);
112 | GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
113 | GLES20.GL_LINEAR);
114 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
115 | GLES20.GL_CLAMP_TO_EDGE);
116 | GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
117 | GLES20.GL_CLAMP_TO_EDGE);
118 | GLUtil.checkEglError("glTexParameter");
119 |
120 | return textureID;
121 | }
122 |
123 | public void drawFrame(SurfaceTexture st, int textureID) {
124 | st.updateTexImage();
125 | if (null != mTextureRender)
126 | mTextureRender.drawFrame(st, textureID);
127 | }
128 |
129 | public Bitmap readPixels(int width, int height) {
130 | ByteBuffer PixelBuffer = ByteBuffer.allocateDirect(4 * width * height);
131 | PixelBuffer.position(0);
132 | GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, PixelBuffer);
133 |
134 | Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
135 | PixelBuffer.position(0);
136 | bmp.copyPixelsFromBuffer(PixelBuffer);
137 |
138 | return bmp;
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/media/VideoDecoder.java:
--------------------------------------------------------------------------------
1 | package com.tam.media;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import android.media.MediaCodec;
7 | import android.media.MediaCodec.BufferInfo;
8 | import android.media.MediaExtractor;
9 | import android.media.MediaFormat;
10 | import android.util.Log;
11 | import android.view.Surface;
12 |
13 | public class VideoDecoder {
14 | final static String TAG = "VideoDecoder";
15 | final static String VIDEO_MIME_PREFIX = "video/";
16 |
17 | private MediaExtractor mMediaExtractor = null;
18 | private MediaCodec mMediaCodec = null;
19 |
20 | private Surface mSurface = null;
21 | private String mPath = null;
22 | private int mVideoTrackIndex = -1;
23 |
24 | public VideoDecoder(String path, Surface surface) {
25 | mPath = path;
26 | mSurface = surface;
27 |
28 | initCodec();
29 | }
30 |
31 | public boolean prepare(long time) {
32 | return decodeFrameAt(time);
33 | }
34 |
35 | public void startDecode() {
36 | }
37 |
38 | public void release() {
39 | if (null != mMediaCodec) {
40 | mMediaCodec.stop();
41 | mMediaCodec.release();
42 | }
43 |
44 | if (null != mMediaExtractor) {
45 | mMediaExtractor.release();
46 | }
47 | }
48 |
49 | private boolean initCodec() {
50 | Log.i(TAG, "initCodec");
51 | mMediaExtractor = new MediaExtractor();
52 | try {
53 | mMediaExtractor.setDataSource(mPath);
54 | } catch (IOException e) {
55 | e.printStackTrace();
56 | return false;
57 | }
58 |
59 | int trackCount = mMediaExtractor.getTrackCount();
60 | for (int i = 0; i < trackCount; ++i) {
61 | MediaFormat mf = mMediaExtractor.getTrackFormat(i);
62 | String mime = mf.getString(MediaFormat.KEY_MIME);
63 | if (mime.startsWith(VIDEO_MIME_PREFIX)) {
64 | mVideoTrackIndex = i;
65 | break;
66 | }
67 | }
68 | if (mVideoTrackIndex < 0)
69 | return false;
70 |
71 | mMediaExtractor.selectTrack(mVideoTrackIndex);
72 | MediaFormat mf = mMediaExtractor.getTrackFormat(mVideoTrackIndex);
73 | String mime = mf.getString(MediaFormat.KEY_MIME);
74 | mMediaCodec = MediaCodec.createDecoderByType(mime);
75 |
76 | mMediaCodec.configure(mf, mSurface, null, 0);
77 | mMediaCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
78 | mMediaCodec.start();
79 | Log.i(TAG, "initCodec end");
80 |
81 | return true;
82 | }
83 |
84 | private boolean mIsInputEOS = false;
85 | private boolean decodeFrameAt(long timeUs) {
86 | Log.i(TAG, "decodeFrameAt " + timeUs);
87 | mMediaExtractor.seekTo(timeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
88 |
89 | mIsInputEOS = false;
90 | CodecState inputState = new CodecState();
91 | CodecState outState = new CodecState();
92 | boolean reachTarget = false;
93 | for (;;) {
94 | if (!inputState.EOS)
95 | handleCodecInput(inputState);
96 |
97 | if (inputState.outIndex < 0) {
98 | handleCodecOutput(outState);
99 | reachTarget = processOutputState(outState, timeUs);
100 | } else {
101 | reachTarget = processOutputState(inputState, timeUs);
102 | }
103 |
104 | if (true == reachTarget || outState.EOS) {
105 | Log.i(TAG, "decodeFrameAt " + timeUs + " reach target or EOS");
106 | break;
107 | }
108 |
109 | inputState.outIndex = -1;
110 | outState.outIndex = -1;
111 | }
112 |
113 | return reachTarget;
114 | }
115 |
116 | private boolean processOutputState(CodecState state, long timeUs) {
117 | if (state.outIndex < 0)
118 | return false;
119 |
120 | if (state.outIndex >= 0 && state.info.presentationTimeUs < timeUs) {
121 | Log.i(TAG, "processOutputState presentationTimeUs " + state.info.presentationTimeUs);
122 | mMediaCodec.releaseOutputBuffer(state.outIndex, false);
123 | return false;
124 | }
125 |
126 | if (state.outIndex >= 0) {
127 | Log.i(TAG, "processOutputState presentationTimeUs " + state.info.presentationTimeUs);
128 | mMediaCodec.releaseOutputBuffer(state.outIndex, true);
129 | return true;
130 | }
131 |
132 | return false;
133 | }
134 |
135 | private class CodecState {
136 | int outIndex = -1;
137 | BufferInfo info = new BufferInfo();
138 | boolean EOS = false;
139 | }
140 |
141 | private void handleCodecInput(CodecState state) {
142 | ByteBuffer [] inputBuffer = mMediaCodec.getInputBuffers();
143 |
144 | for (;!mIsInputEOS;) {
145 | int inputBufferIndex = mMediaCodec.dequeueInputBuffer(10000);
146 | if (inputBufferIndex < 0)
147 | continue;
148 |
149 | ByteBuffer in = inputBuffer[inputBufferIndex];
150 | int readSize = mMediaExtractor.readSampleData(in, 0);
151 | long presentationTimeUs = mMediaExtractor.getSampleTime();
152 | int flags = mMediaExtractor.getSampleFlags();
153 |
154 | boolean EOS = !mMediaExtractor.advance();
155 | EOS |= (readSize <= 0);
156 | EOS |= ((flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) > 0);
157 |
158 | Log.i(TAG, "input presentationTimeUs " + presentationTimeUs + " isEOS " + EOS);
159 |
160 | if (EOS && readSize < 0)
161 | readSize = 0;
162 |
163 | if (readSize > 0 || EOS)
164 | mMediaCodec.queueInputBuffer(inputBufferIndex, 0, readSize, presentationTimeUs, flags | (EOS? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0));
165 |
166 | if (EOS) {
167 | state.EOS = true;
168 | mIsInputEOS = true;
169 | break;
170 | }
171 |
172 | state.outIndex = mMediaCodec.dequeueOutputBuffer(state.info, 10000);
173 | if (state.outIndex >= 0)
174 | break;
175 | }
176 | }
177 |
178 | private void handleCodecOutput(CodecState state) {
179 | state.outIndex = mMediaCodec.dequeueOutputBuffer(state.info, 10000);
180 | if (state.outIndex < 0) {
181 | return;
182 | }
183 |
184 | if ((state.info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
185 | state.EOS = true;
186 | Log.i(TAG, "reach output EOS " + state.info.presentationTimeUs);
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/FrameGrabber/src/com/tam/gl/TextureRender.java:
--------------------------------------------------------------------------------
1 | package com.tam.gl;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.nio.ByteOrder;
5 | import java.nio.FloatBuffer;
6 |
7 | import android.graphics.SurfaceTexture;
8 | import android.opengl.GLES11Ext;
9 | import android.opengl.GLES20;
10 | import android.opengl.Matrix;
11 | import android.util.Log;
12 |
13 |
14 | /**
15 | * Code for rendering a texture onto a surface using OpenGL ES 2.0.
16 | */
17 | class TextureRender {
18 | private static final String TAG = "TextureRender";
19 |
20 | private static final int FLOAT_SIZE_BYTES = 4;
21 | private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
22 | private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
23 | private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
24 | private final float[] mTriangleVerticesData = {
25 | // X, Y, Z, U, V
26 | -1.0f, -1.0f, 0, 0.f, 1.f,
27 | 1.0f, -1.0f, 0, 1.f, 1.f,
28 | -1.0f, 1.0f, 0, 0.f, 0.f,
29 | 1.0f, 1.0f, 0, 1.f, 0.f,
30 | };
31 |
32 | private FloatBuffer mTriangleVertices;
33 |
34 | private static final String VERTEX_SHADER =
35 | "uniform mat4 uMVPMatrix;\n" +
36 | "uniform mat4 uSTMatrix;\n" +
37 | "attribute vec4 aPosition;\n" +
38 | "attribute vec4 aTextureCoord;\n" +
39 | "varying vec2 vTextureCoord;\n" +
40 | "void main() {\n" +
41 | " gl_Position = uMVPMatrix * aPosition;\n" +
42 | " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
43 | "}\n";
44 |
45 | private static final String FRAGMENT_SHADER =
46 | "#extension GL_OES_EGL_image_external : require\n" +
47 | "precision mediump float;\n" + // highp here doesn't seem to matter
48 | "varying vec2 vTextureCoord;\n" +
49 | "uniform samplerExternalOES sTexture;\n" +
50 | "void main() {\n" +
51 | " vec2 texcoord = vTextureCoord;\n" +
52 | " vec3 normalColor = texture2D(sTexture, texcoord).rgb;\n" +
53 | " normalColor = vec3(normalColor.r, normalColor.g, normalColor.b);\n" +
54 | " gl_FragColor = vec4(normalColor.r, normalColor.g, normalColor.b, 1); \n"+
55 | "}\n";
56 |
57 | private float[] mMVPMatrix = new float[16];
58 | private float[] mSTMatrix = new float[16];
59 |
60 | private int mProgram;
61 | private int muMVPMatrixHandle;
62 | private int muSTMatrixHandle;
63 | private int maPositionHandle;
64 | private int maTextureHandle;
65 |
66 | public TextureRender() {
67 | mTriangleVertices = ByteBuffer.allocateDirect(
68 | mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
69 | .order(ByteOrder.nativeOrder()).asFloatBuffer();
70 | mTriangleVertices.put(mTriangleVerticesData).position(0);
71 |
72 | Matrix.setIdentityM(mSTMatrix, 0);
73 | init();
74 | }
75 |
76 | public void drawFrame(SurfaceTexture st, int textureID) {
77 | GLUtil.checkEglError("onDrawFrame start");
78 | st.getTransformMatrix(mSTMatrix);
79 |
80 | GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
81 | GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
82 |
83 | if (GLES20.glIsProgram( mProgram ) != true){
84 | reCreateProgram();
85 | }
86 |
87 | GLES20.glUseProgram(mProgram);
88 | GLUtil.checkEglError("glUseProgram");
89 |
90 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
91 | GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
92 |
93 | mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
94 | GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
95 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
96 | GLUtil.checkEglError("glVertexAttribPointer maPosition");
97 | GLES20.glEnableVertexAttribArray(maPositionHandle);
98 | GLUtil.checkEglError("glEnableVertexAttribArray maPositionHandle");
99 |
100 | mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
101 | GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false,
102 | TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
103 | GLUtil.checkEglError("glVertexAttribPointer maTextureHandle");
104 | GLES20.glEnableVertexAttribArray(maTextureHandle);
105 | GLUtil.checkEglError("glEnableVertexAttribArray maTextureHandle");
106 |
107 | Matrix.setIdentityM(mMVPMatrix, 0);
108 | GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
109 | GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
110 |
111 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
112 | GLUtil.checkEglError("glDrawArrays");
113 | GLES20.glFinish();
114 | }
115 |
116 | /**
117 | * Initializes GL state. Call this after the EGL surface has been created and made current.
118 | */
119 | public void init() {
120 | mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
121 | if (mProgram == 0) {
122 | throw new RuntimeException("failed creating program");
123 | }
124 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
125 | GLUtil.checkEglError("glGetAttribLocation aPosition");
126 | if (maPositionHandle == -1) {
127 | throw new RuntimeException("Could not get attrib location for aPosition");
128 | }
129 | maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
130 | GLUtil.checkEglError("glGetAttribLocation aTextureCoord");
131 | if (maTextureHandle == -1) {
132 | throw new RuntimeException("Could not get attrib location for aTextureCoord");
133 | }
134 |
135 | muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
136 | GLUtil.checkEglError("glGetUniformLocation uMVPMatrix");
137 | if (muMVPMatrixHandle == -1) {
138 | throw new RuntimeException("Could not get attrib location for uMVPMatrix");
139 | }
140 |
141 | muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
142 | GLUtil.checkEglError("glGetUniformLocation uSTMatrix");
143 | if (muSTMatrixHandle == -1) {
144 | throw new RuntimeException("Could not get attrib location for uSTMatrix");
145 | }
146 | }
147 |
148 | private void reCreateProgram() {
149 | mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
150 | if (mProgram == 0) {
151 | throw new RuntimeException("failed creating program");
152 | }
153 | maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
154 | GLUtil.checkEglError("glGetAttribLocation aPosition");
155 | if (maPositionHandle == -1) {
156 | throw new RuntimeException("Could not get attrib location for aPosition");
157 | }
158 | maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
159 | GLUtil.checkEglError("glGetAttribLocation aTextureCoord");
160 | if (maTextureHandle == -1) {
161 | throw new RuntimeException("Could not get attrib location for aTextureCoord");
162 | }
163 |
164 | muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
165 | GLUtil.checkEglError("glGetUniformLocation uMVPMatrix");
166 | if (muMVPMatrixHandle == -1) {
167 | throw new RuntimeException("Could not get attrib location for uMVPMatrix");
168 | }
169 |
170 | muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
171 | GLUtil.checkEglError("glGetUniformLocation uSTMatrix");
172 | if (muSTMatrixHandle == -1) {
173 | throw new RuntimeException("Could not get attrib location for uSTMatrix");
174 | }
175 | }
176 |
177 | private int loadShader(int shaderType, String source) {
178 | int shader = GLES20.glCreateShader(shaderType);
179 | GLUtil.checkEglError("glCreateShader type=" + shaderType);
180 | GLES20.glShaderSource(shader, source);
181 | GLES20.glCompileShader(shader);
182 | int[] compiled = new int[1];
183 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
184 | if (compiled[0] == 0) {
185 | Log.e(TAG, "Could not compile shader " + shaderType + ":");
186 | Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
187 | GLES20.glDeleteShader(shader);
188 | shader = 0;
189 | }
190 | return shader;
191 | }
192 |
193 | private int createProgram(String vertexSource, String fragmentSource) {
194 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
195 | if (vertexShader == 0) {
196 | return 0;
197 | }
198 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
199 | if (pixelShader == 0) {
200 | return 0;
201 | }
202 |
203 | int program = GLES20.glCreateProgram();
204 | GLUtil.checkEglError("glCreateProgram");
205 | if (program == 0) {
206 | Log.e(TAG, "Could not create program");
207 | }
208 | GLES20.glAttachShader(program, vertexShader);
209 | GLUtil.checkEglError("glAttachShader");
210 | GLES20.glAttachShader(program, pixelShader);
211 | GLUtil.checkEglError("glAttachShader");
212 | GLES20.glLinkProgram(program);
213 | int[] linkStatus = new int[1];
214 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
215 | if (linkStatus[0] != GLES20.GL_TRUE) {
216 | Log.e(TAG, "Could not link program: ");
217 | Log.e(TAG, GLES20.glGetProgramInfoLog(program));
218 | GLES20.glDeleteProgram(program);
219 | program = 0;
220 | }
221 | return program;
222 | }
223 | }
--------------------------------------------------------------------------------