1 /* 2 * Copyright (C) 2011 The Android Open Source Project 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.mediadump; 18 19 import java.io.IOException; 20 import java.io.BufferedOutputStream; 21 import java.io.BufferedWriter; 22 import java.io.File; 23 import java.io.FileWriter; 24 import java.io.FilenameFilter; 25 import java.io.FileOutputStream; 26 import java.io.File; 27 28 import java.lang.Integer; 29 import java.lang.Math; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.nio.FloatBuffer; 33 import java.nio.channels.FileChannel; 34 import java.nio.IntBuffer; 35 import java.util.Properties; 36 37 import javax.microedition.khronos.egl.EGLConfig; 38 import javax.microedition.khronos.opengles.GL10; 39 40 import android.app.Activity; 41 import android.content.Context; 42 import android.content.pm.ActivityInfo; 43 import android.graphics.SurfaceTexture; 44 import android.media.MediaPlayer; 45 import android.opengl.GLES20; 46 import android.opengl.GLSurfaceView; 47 import android.opengl.GLUtils; 48 import android.opengl.Matrix; 49 import android.os.Bundle; 50 import android.util.Log; 51 import android.view.MotionEvent; 52 import android.view.Surface; 53 import android.view.SurfaceHolder; 54 import android.view.View; 55 import android.widget.MediaController; 56 import android.widget.MediaController.MediaPlayerControl; 57 58 /** 59 * A view to play a video, specified by VideoDumpConfig.VIDEO_URI, and dump the screen 60 * into raw RGB files. 61 * It uses a renderer to display each video frame over a surface texture, read pixels, 62 * and writes the pixels into a rgb file on sdcard. 63 * Those raw rgb files will be used to compare the quality distortion against 64 * the original video. They can be viewed with the RgbPlayer app for debugging. 65 */ 66 class VideoDumpView extends GLSurfaceView implements MediaPlayerControl { 67 private static final String TAG = "VideoDumpView"; 68 VideoDumpRenderer mRenderer; 69 private MediaController mMediaController; 70 private boolean mMediaControllerAttached = false; 71 private MediaPlayer mMediaPlayer = null; 72 private BufferedWriter mImageListWriter = null; 73 74 // A serials of configuration constants. 75 class VideoDumpConfig { 76 // Currently we are running with a local copy of the video. 77 // It should work with a "http://" sort of streaming url as well. 78 public static final String VIDEO_URI = "/sdcard/mediadump/sample.mp4"; 79 public static final String ROOT_DIR = "/sdcard/mediadump/"; 80 public static final String IMAGES_LIST = "images.lst"; 81 public static final String IMAGE_PREFIX = "img"; 82 public static final String IMAGE_SUFFIX = ".rgb"; 83 public static final String PROPERTY_FILE = "prop.xml"; 84 85 // So far, glReadPixels only supports two (format, type) combinations 86 // GL_RGB GL_UNSIGNED_SHORT_5_6_5 16 bits per pixel (default) 87 // GL_RGBA GL_UNSIGNED_BYTE 32 bits per pixel 88 public static final int PIXEL_FORMAT = GLES20.GL_RGB; 89 public static final int PIXEL_TYPE = PIXEL_FORMAT == GLES20.GL_RGBA 90 ? GLES20.GL_UNSIGNED_BYTE : GLES20.GL_UNSIGNED_SHORT_5_6_5; 91 public static final int BYTES_PER_PIXEL = 92 PIXEL_FORMAT == GLES20.GL_RGBA ? 4 : 2; 93 public static final boolean SET_CHOOSER 94 = PIXEL_FORMAT == GLES20.GL_RGBA ? true : false; 95 96 // On Motorola Xoom, it takes 100ms to read pixels and 180ms to write to a file 97 // to dump a complete 720p(1280*720) video frame. It's much slower than the frame 98 // playback interval (40ms). So we only dump a center block and it should be able 99 // to catch all the e2e distortion. A reasonable size of the block is 256x256, 100 // which takes 4ms to read pixels and 25 ms to write to a file. 101 public static final int MAX_DUMP_WIDTH = 256; 102 public static final int MAX_DUMP_HEIGHT = 256; 103 104 // TODO: MediaPlayer doesn't give back the video frame rate and we'll need to 105 // figure it by dividing the total number of frames by the duration. 106 public static final int FRAME_RATE = 25; 107 } 108 109 public VideoDumpView(Context context) { 110 super(context); 111 setEGLContextClientVersion(2); 112 // GLSurfaceView uses RGB_5_6_5 by default. 113 if (VideoDumpConfig.SET_CHOOSER) { 114 setEGLConfigChooser(8, 8, 8, 8, 8, 8); 115 } 116 mRenderer = new VideoDumpRenderer(context); 117 setRenderer(mRenderer); 118 } 119 120 @Override 121 public void onPause() { 122 stopPlayback(); 123 super.onPause(); 124 } 125 126 @Override 127 public void onResume() { 128 Log.d(TAG, "onResume"); 129 130 mMediaPlayer = new MediaPlayer(); 131 try { 132 mMediaPlayer.setDataSource(VideoDumpConfig.VIDEO_URI); 133 134 class RGBFilter implements FilenameFilter { 135 public boolean accept(File dir, String name) { 136 return (name.endsWith(VideoDumpConfig.IMAGE_SUFFIX)); 137 } 138 } 139 File dump_dir = new File(VideoDumpConfig.ROOT_DIR); 140 File[] dump_files = dump_dir.listFiles(new RGBFilter()); 141 for (File dump_file :dump_files) { 142 dump_file.delete(); 143 } 144 145 File image_list = new File(VideoDumpConfig.ROOT_DIR 146 + VideoDumpConfig.IMAGES_LIST); 147 image_list.delete(); 148 mImageListWriter = new BufferedWriter(new FileWriter(image_list)); 149 } catch (java.io.IOException e) { 150 Log.e(TAG, e.getMessage(), e); 151 } 152 153 queueEvent(new Runnable(){ 154 public void run() { 155 mRenderer.setMediaPlayer(mMediaPlayer); 156 mRenderer.setImageListWriter(mImageListWriter); 157 }}); 158 159 super.onResume(); 160 } 161 162 public void start() { 163 mMediaPlayer.start(); 164 } 165 166 public void pause() { 167 mMediaPlayer.pause(); 168 try { 169 mImageListWriter.flush(); 170 } catch (java.io.IOException e) { 171 Log.e(TAG, e.getMessage(), e); 172 } 173 } 174 175 public void stopPlayback() { 176 Log.d(TAG, "stopPlayback"); 177 178 if (mMediaPlayer != null) { 179 mMediaPlayer.stop(); 180 mMediaPlayer.release(); 181 mMediaPlayer = null; 182 } 183 if (mImageListWriter != null) { 184 try { 185 mImageListWriter.flush(); 186 mImageListWriter.close(); 187 } catch (java.io.IOException e) { 188 Log.e(TAG, e.getMessage(), e); 189 } 190 } else { 191 Log.d(TAG, "image list file was not written successfully."); 192 } 193 } 194 195 public void setMediaController(MediaController controller) { 196 if (mMediaController != null) { 197 mMediaController.hide(); 198 } 199 mMediaController = controller; 200 } 201 202 private void attachMediaController() { 203 if (mMediaPlayer != null && mMediaController != null) { 204 if (!mMediaControllerAttached) { 205 mMediaController.setMediaPlayer(this); 206 View anchorView = this.getParent() instanceof View ? 207 (View)this.getParent() : this; 208 mMediaController.setAnchorView(anchorView); 209 mMediaController.setEnabled(true); 210 mMediaControllerAttached = true; 211 } 212 mMediaController.show(); 213 } 214 } 215 216 private boolean isInPlaybackState() { 217 return (mMediaPlayer != null && mMediaPlayer.isPlaying()); 218 } 219 220 public boolean canPause () { 221 return true; 222 } 223 224 public boolean canSeekBackward () { 225 return true; 226 } 227 228 public boolean canSeekForward () { 229 return true; 230 } 231 232 public int getBufferPercentage () { 233 return 1; 234 } 235 236 public int getCurrentPosition () { 237 if (isInPlaybackState()) { 238 return mMediaPlayer.getCurrentPosition(); 239 } 240 return 0; 241 } 242 243 public int getDuration () { 244 return mMediaPlayer.getDuration(); 245 } 246 247 public boolean isPlaying () { 248 return isInPlaybackState() && mMediaPlayer.isPlaying(); 249 } 250 251 public void seekTo (int pos) { 252 mMediaPlayer.seekTo(pos); 253 } 254 255 @Override 256 public int getAudioSessionId() { 257 return 0; 258 } 259 260 @Override 261 public boolean onTouchEvent(MotionEvent ev) { 262 attachMediaController(); 263 return true; 264 } 265 266 /** 267 * A renderer to read each video frame from a media player, draw it over a surface 268 * texture, dump the on-screen pixels into a buffer, and writes the pixels into 269 * a rgb file on sdcard. 270 */ 271 private static class VideoDumpRenderer 272 implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener { 273 private static String TAG = "VideoDumpRenderer"; 274 275 /* All GL related fields from 276 * http://developer.android.com/resources/samples/ApiDemos/src/com/example 277 * /android/apis/graphics/GLES20TriangleRenderer.html 278 */ 279 private static final int FLOAT_SIZE_BYTES = 4; 280 private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; 281 private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; 282 private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; 283 private final float[] mTriangleVerticesData = { 284 // X, Y, Z, U, V 285 -1.0f, -1.0f, 0, 0.f, 0.f, 286 1.0f, -1.0f, 0, 1.f, 0.f, 287 -1.0f, 1.0f, 0, 0.f, 1.f, 288 1.0f, 1.0f, 0, 1.f, 1.f, 289 }; 290 291 private FloatBuffer mTriangleVertices; 292 293 private final String mVertexShader = 294 "uniform mat4 uMVPMatrix;\n" + 295 "uniform mat4 uSTMatrix;\n" + 296 "attribute vec4 aPosition;\n" + 297 "attribute vec4 aTextureCoord;\n" + 298 "varying vec2 vTextureCoord;\n" + 299 "void main() {\n" + 300 " gl_Position = uMVPMatrix * aPosition;\n" + 301 " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + 302 "}\n"; 303 304 private final String mFragmentShader = 305 "#extension GL_OES_EGL_image_external : require\n" + 306 "precision mediump float;\n" + 307 "varying vec2 vTextureCoord;\n" + 308 "uniform samplerExternalOES sTexture;\n" + 309 "void main() {\n" + 310 " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + 311 "}\n"; 312 313 private float[] mMVPMatrix = new float[16]; 314 private float[] mSTMatrix = new float[16]; 315 316 private int mProgram; 317 private int mTextureID; 318 private int muMVPMatrixHandle; 319 private int muSTMatrixHandle; 320 private int maPositionHandle; 321 private int maTextureHandle; 322 323 private SurfaceTexture mSurface; 324 private boolean updateSurface = false; 325 326 // Magic key 327 private static int GL_TEXTURE_EXTERNAL_OES = 0x8D65; 328 329 330 /** 331 * Fields that reads video source and dumps to file. 332 */ 333 // The media player that loads and decodes the video. 334 // Not owned by this class. 335 private MediaPlayer mMediaPlayer; 336 // The frame number from media player. 337 private int mFrameNumber = 0; 338 // The frame number that is drawing on screen. 339 private int mDrawNumber = 0; 340 // The width and height of dumping block. 341 private int mWidth = 0; 342 private int mHeight = 0; 343 // The offset of the dumping block. 344 private int mStartX = 0; 345 private int mStartY = 0; 346 // A buffer to hold the dumping pixels. 347 private ByteBuffer mBuffer = null; 348 // A file writer to write the filenames of images. 349 private BufferedWriter mImageListWriter; 350 351 public VideoDumpRenderer(Context context) { 352 mTriangleVertices = ByteBuffer.allocateDirect( 353 mTriangleVerticesData.length * FLOAT_SIZE_BYTES) 354 .order(ByteOrder.nativeOrder()).asFloatBuffer(); 355 mTriangleVertices.put(mTriangleVerticesData).position(0); 356 357 Matrix.setIdentityM(mSTMatrix, 0); 358 } 359 360 public void setMediaPlayer(MediaPlayer player) { 361 mMediaPlayer = player; 362 } 363 364 public void setImageListWriter(BufferedWriter imageListWriter) { 365 mImageListWriter = imageListWriter; 366 } 367 368 /** 369 * Called to draw the current frame. 370 * This method is responsible for drawing the current frame. 371 */ 372 public void onDrawFrame(GL10 glUnused) { 373 boolean isNewFrame = false; 374 int frameNumber = 0; 375 376 synchronized(this) { 377 if (updateSurface) { 378 isNewFrame = true; 379 frameNumber = mFrameNumber; 380 mSurface.updateTexImage(); 381 mSurface.getTransformMatrix(mSTMatrix); 382 updateSurface = false; 383 } 384 } 385 386 // Initial clear. 387 GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); 388 GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); 389 390 // Load the program, which is the basics rules to draw the vertexes and textures. 391 GLES20.glUseProgram(mProgram); 392 checkGlError("glUseProgram"); 393 394 // Activate the texture. 395 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 396 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID); 397 398 // Load the vertexes coordinates. Simple here since it only draw a rectangle 399 // that fits the whole screen. 400 mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); 401 GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 402 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 403 checkGlError("glVertexAttribPointer maPosition"); 404 GLES20.glEnableVertexAttribArray(maPositionHandle); 405 checkGlError("glEnableVertexAttribArray maPositionHandle"); 406 407 // Load the texture coordinates, which is essentially a rectangle that fits 408 // the whole video frame. 409 mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); 410 GLES20.glVertexAttribPointer(maTextureHandle, 3, GLES20.GL_FLOAT, false, 411 TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); 412 checkGlError("glVertexAttribPointer maTextureHandle"); 413 GLES20.glEnableVertexAttribArray(maTextureHandle); 414 checkGlError("glEnableVertexAttribArray maTextureHandle"); 415 416 // Set up the GL matrices. 417 Matrix.setIdentityM(mMVPMatrix, 0); 418 GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); 419 GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); 420 421 // Draw a rectangle and render the video frame as a texture on it. 422 GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); 423 checkGlError("glDrawArrays"); 424 GLES20.glFinish(); 425 426 if (isNewFrame) { // avoid duplicates. 427 Log.d(TAG, mDrawNumber + "/" + frameNumber + " before dumping " 428 + System.currentTimeMillis()); 429 DumpToFile(frameNumber); 430 Log.d(TAG, mDrawNumber + "/" + frameNumber + " after dumping " 431 + System.currentTimeMillis()); 432 433 mDrawNumber++; 434 } 435 } 436 437 // Call the GL function that dumps the screen into a buffer, then write to a file. 438 private void DumpToFile(int frameNumber) { 439 GLES20.glReadPixels(mStartX, mStartY, mWidth, mHeight, 440 VideoDumpConfig.PIXEL_FORMAT, 441 VideoDumpConfig.PIXEL_TYPE, 442 mBuffer); 443 checkGlError("glReadPixels"); 444 445 Log.d(TAG, mDrawNumber + "/" + frameNumber + " after glReadPixels " 446 + System.currentTimeMillis()); 447 448 String filename = VideoDumpConfig.ROOT_DIR + VideoDumpConfig.IMAGE_PREFIX 449 + frameNumber + VideoDumpConfig.IMAGE_SUFFIX; 450 try { 451 mImageListWriter.write(filename); 452 mImageListWriter.newLine(); 453 FileOutputStream fos = new FileOutputStream(filename); 454 fos.write(mBuffer.array()); 455 fos.close(); 456 } catch (java.io.IOException e) { 457 Log.e(TAG, e.getMessage(), e); 458 } 459 } 460 461 /** 462 * Called when the surface changed size. 463 * Called after the surface is created and whenever the OpenGL surface size changes. 464 */ 465 public void onSurfaceChanged(GL10 glUnused, int width, int height) { 466 Log.d(TAG, "Surface size: " + width + "x" + height); 467 468 int video_width = mMediaPlayer.getVideoWidth(); 469 int video_height = mMediaPlayer.getVideoHeight(); 470 Log.d(TAG, "Video size: " + video_width 471 + "x" + video_height); 472 473 // TODO: adjust video_width and video_height with the surface size. 474 GLES20.glViewport(0, 0, video_width, video_height); 475 476 mWidth = Math.min(VideoDumpConfig.MAX_DUMP_WIDTH, video_width); 477 mHeight = Math.min(VideoDumpConfig.MAX_DUMP_HEIGHT, video_height); 478 mStartX = video_width / mWidth / 2 * mWidth; 479 mStartY = video_height / mHeight / 2 * mHeight; 480 481 Log.d(TAG, "dumping block start at (" + mStartX + "," + mStartY + ") " 482 + "size " + mWidth + "x" + mHeight); 483 484 int image_size = mWidth * mHeight * VideoDumpConfig.BYTES_PER_PIXEL; 485 mBuffer = ByteBuffer.allocate(image_size); 486 487 int bpp[] = new int[3]; 488 GLES20.glGetIntegerv(GLES20.GL_RED_BITS, bpp, 0); 489 GLES20.glGetIntegerv(GLES20.GL_GREEN_BITS, bpp, 1); 490 GLES20.glGetIntegerv(GLES20.GL_BLUE_BITS, bpp, 2); 491 Log.d(TAG, "rgb bits: " + bpp[0] + "-" + bpp[1] + "-" + bpp[2]); 492 493 // Save the properties into a xml file 494 // so the RgbPlayer can understand the output format. 495 Properties prop = new Properties(); 496 prop.setProperty("width", Integer.toString(mWidth)); 497 prop.setProperty("height", Integer.toString(mHeight)); 498 prop.setProperty("startX", Integer.toString(mStartX)); 499 prop.setProperty("startY", Integer.toString(mStartY)); 500 prop.setProperty("bytesPerPixel", 501 Integer.toString(VideoDumpConfig.BYTES_PER_PIXEL)); 502 prop.setProperty("frameRate", Integer.toString(VideoDumpConfig.FRAME_RATE)); 503 try { 504 prop.storeToXML(new FileOutputStream(VideoDumpConfig.ROOT_DIR 505 + VideoDumpConfig.PROPERTY_FILE), ""); 506 } catch (java.io.IOException e) { 507 Log.e(TAG, e.getMessage(), e); 508 } 509 } 510 511 /** 512 * Called when the surface is created or recreated. 513 * Called when the rendering thread starts and whenever the EGL context is lost. 514 * A place to put code to create resources that need to be created when the rendering 515 * starts, and that need to be recreated when the EGL context is lost e.g. texture. 516 * Note that when the EGL context is lost, all OpenGL resources associated with 517 * that context will be automatically deleted. 518 */ 519 public void onSurfaceCreated(GL10 glUnused, EGLConfig config) { 520 Log.d(TAG, "onSurfaceCreated"); 521 522 /* Set up shaders and handles to their variables */ 523 mProgram = createProgram(mVertexShader, mFragmentShader); 524 if (mProgram == 0) { 525 return; 526 } 527 maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); 528 checkGlError("glGetAttribLocation aPosition"); 529 if (maPositionHandle == -1) { 530 throw new RuntimeException("Could not get attrib location for aPosition"); 531 } 532 maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); 533 checkGlError("glGetAttribLocation aTextureCoord"); 534 if (maTextureHandle == -1) { 535 throw new RuntimeException("Could not get attrib location for aTextureCoord"); 536 } 537 538 muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); 539 checkGlError("glGetUniformLocation uMVPMatrix"); 540 if (muMVPMatrixHandle == -1) { 541 throw new RuntimeException("Could not get attrib location for uMVPMatrix"); 542 } 543 544 muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); 545 checkGlError("glGetUniformLocation uSTMatrix"); 546 if (muSTMatrixHandle == -1) { 547 throw new RuntimeException("Could not get attrib location for uSTMatrix"); 548 } 549 550 551 // Create our texture. This has to be done each time the surface is created. 552 int[] textures = new int[1]; 553 GLES20.glGenTextures(1, textures, 0); 554 555 mTextureID = textures[0]; 556 GLES20.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID); 557 checkGlError("glBindTexture mTextureID"); 558 559 // Can't do mipmapping with mediaplayer source 560 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, 561 GLES20.GL_NEAREST); 562 GLES20.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, 563 GLES20.GL_LINEAR); 564 // Clamp to edge is the only option 565 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, 566 GLES20.GL_CLAMP_TO_EDGE); 567 GLES20.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, 568 GLES20.GL_CLAMP_TO_EDGE); 569 checkGlError("glTexParameteri mTextureID"); 570 571 /* 572 * Create the SurfaceTexture that will feed this textureID, 573 * and pass it to the MediaPlayer 574 */ 575 mSurface = new SurfaceTexture(mTextureID); 576 mSurface.setOnFrameAvailableListener(this); 577 578 Surface surface = new Surface(mSurface); 579 mMediaPlayer.setSurface(surface); 580 surface.release(); 581 582 try { 583 mMediaPlayer.prepare(); 584 } catch (IOException t) { 585 Log.e(TAG, "media player prepare failed"); 586 } 587 588 synchronized(this) { 589 updateSurface = false; 590 } 591 } 592 593 synchronized public void onFrameAvailable(SurfaceTexture surface) { 594 /* For simplicity, SurfaceTexture calls here when it has new 595 * data available. Call may come in from some random thread, 596 * so let's be safe and use synchronize. No OpenGL calls can be done here. 597 */ 598 mFrameNumber++; 599 updateSurface = true; 600 } 601 602 private int loadShader(int shaderType, String source) { 603 int shader = GLES20.glCreateShader(shaderType); 604 if (shader != 0) { 605 GLES20.glShaderSource(shader, source); 606 GLES20.glCompileShader(shader); 607 int[] compiled = new int[1]; 608 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); 609 if (compiled[0] == 0) { 610 Log.e(TAG, "Could not compile shader " + shaderType + ":"); 611 Log.e(TAG, GLES20.glGetShaderInfoLog(shader)); 612 GLES20.glDeleteShader(shader); 613 shader = 0; 614 } 615 } 616 return shader; 617 } 618 619 private int createProgram(String vertexSource, String fragmentSource) { 620 int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); 621 if (vertexShader == 0) { 622 return 0; 623 } 624 int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); 625 if (pixelShader == 0) { 626 return 0; 627 } 628 629 int program = GLES20.glCreateProgram(); 630 if (program != 0) { 631 GLES20.glAttachShader(program, vertexShader); 632 checkGlError("glAttachShader"); 633 GLES20.glAttachShader(program, pixelShader); 634 checkGlError("glAttachShader"); 635 GLES20.glLinkProgram(program); 636 int[] linkStatus = new int[1]; 637 GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); 638 if (linkStatus[0] != GLES20.GL_TRUE) { 639 Log.e(TAG, "Could not link program: "); 640 Log.e(TAG, GLES20.glGetProgramInfoLog(program)); 641 GLES20.glDeleteProgram(program); 642 program = 0; 643 } 644 } 645 return program; 646 } 647 648 private void checkGlError(String op) { 649 int error; 650 while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 651 Log.e(TAG, op + ": glError " + error); 652 throw new RuntimeException(op + ": glError " + error); 653 } 654 } 655 656 } // End of class VideoDumpRender. 657 658 } // End of class VideoDumpView. 659