Android(安卓)Camera2 Opengles2.0 图像实时滤镜 显示 视频编码
16lz
2021-01-25
demo:
http://download.csdn.net/download/keen_zuxwang/10043183
在博文”Android Camera2 Opengles2.0 预览图像实时滤镜 视频编码”
http://blog.csdn.net/keen_zuxwang/article/details/78366598
的基础上添加FBO实时滤镜、回调显示—其中用到glReadPixels:
glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区,
则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓冲区的。因此,如果需要读取已经绘制好的像素,往往需要先交换前后缓冲
void glReadPixels( GLint x,
GLint y,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
GLvoid * data)
vertex
shader, fragment shader部分不变
增加, FBO 操作类:
public class EasyGlUtils { EasyGlUtils(){ } public static void defaultTexParameter(){ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE); } public static void useTexParameter(int gl_wrap_s, int gl_wrap_t, int gl_min_filter, int gl_mag_filter){ GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,gl_wrap_s); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,gl_wrap_t); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,gl_min_filter); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,gl_mag_filter); } //生产纹理、并设置纹理类型、尺寸等参数,调用GLES20.glDrawElements() GLES20.glDrawArrays()将片元绘制到该设置的纹理上 public static void genTexturesWithParameter(int size, int[] textures,int start, int gl_format,int width,int height){ GLES20.glGenTextures(size, textures, start); for (int i = 0; i < size; i++) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i]); GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, gl_format, width, height, 0, gl_format, GLES20.GL_UNSIGNED_BYTE, null); defaultTexParameter(); } GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,0); } public static void generateBindFrameTexture(int[] frameBufferId, int[] renderId, int[] textureId, int width, int height){ //生成fb GLES20.glGenFramebuffers(1, frameBufferId, 0); GLES20.glGenRenderbuffers(1, renderId, 0); genTexturesWithParameter(1, textureId, 0, GLES20.GL_RGBA, width, height); //绑定fb GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId[0]); GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, renderId[0]); GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER, GLES20.GL_DEPTH_COMPONENT16, width, height); //绑定纹理到fb GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId[0], 0); GLES20.glFramebufferRenderbuffer(GLES20.GL_FRAMEBUFFER, GLES20.GL_DEPTH_ATTACHMENT, GLES20.GL_RENDERBUFFER, renderId[0]); } //绑定Framebuffer Texture2D public static void bindFrameTexture(int frameBufferId, int textureId){ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBufferId); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0); } public static void unBindFrameBuffer(){ //GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, 0); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,0); }}
shader 操作类增加FBO离屏渲染、显示部分:
public class SurfaceRenderer implements Runnable, SurfaceTexture.OnFrameAvailableListener{ public static String LOG_TAG = SurfaceRenderer.class.getSimpleName(); private static float squareCoords[] = { -1.0f, 1.0f, // top left -1.0f, -1.0f, // bottom left 1.0f, -1.0f, // bottom right 1.0f, 1.0f // top right }; private static short drawOrder[] = { 0, 1, 2, 0, 2, 3}; // Texture to be shown in backgrund private float textureCoords[] = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f }; private int[] textures = new int[1]; private Context context; private int shaderProgram; private FloatBuffer vertexBuffer; private FloatBuffer textureBuffer; private ShortBuffer drawListBuffer; private SurfaceTexture videoTexture; private float[] videoTextureTransform; private boolean frameAvailable = false; int textureParamHandle; int textureCoordinateHandle; int positionHandle; int textureTranformHandle; protected Surface surface; protected int width; protected int height; private EGL10 egl; private EGLContext eglContext; private EGLDisplay eglDisplay; private EGLSurface eglSurface; TextureViewMediaActivity instance; public boolean running = false; private float[] frameMatrix=new float[16]; //用于绘制回调缩放的矩阵 private boolean isRecord=false; private boolean isShoot=false; private ByteBuffer[] outPutBuffer = new ByteBuffer[3]; //用于存储回调数据的buffer private OnFrameCallback onFrameCallback; //回调 private int frameCallbackWidth, frameCallbackHeight; //回调数据的宽高 private int indexOutput=0; public interface OnFrameCallback { void onFrame(byte[] bytes, long time); } public void setOnFrameCallback(int width, int height, OnFrameCallback onFrameCallback){ this.frameCallbackWidth = width; this.frameCallbackHeight = height; if (frameCallbackWidth > 0 && frameCallbackHeight > 0) { for(int i=0; i<3; i++) { outPutBuffer[i] = ByteBuffer.allocate(width*height*4); } setFrameCallbackMatrix(); /* IntBuffer imp_fmt = null; IntBuffer imp_type = null; GLES20.glGetIntegerv(GLES20.GL_IMPLEMENTATION_COLOR_READ_FORMAT, imp_fmt); GLES20.glGetIntegerv(GLES20.GL_IMPLEMENTATION_COLOR_READ_TYPE, imp_type); */ this.onFrameCallback = onFrameCallback; isRecord = true; } else { this.onFrameCallback = null; } } private void setFrameCallbackMatrix(){ if(frameCallbackHeight>0 && frameCallbackWidth>0 && width>0 && height>0){ //计算输出的变换矩阵 MatrixUtils.getMatrix(frameMatrix, MatrixUtils.TYPE_CENTERCROP, width, height, frameCallbackWidth,frameCallbackHeight); MatrixUtils.flip(frameMatrix, false, true); } } //需要回调,则缩放图片到指定大小,读取数据并回调 private void callbackIfNeeded() { if (onFrameCallback != null && (isRecord || isShoot)) { //设置绘制窗口,同一般直接绘制到屏幕的原理是一样的,这里只是离屏绘制到Framebuffer绑定纹理上 GLES20.glViewport(0, 0, frameCallbackWidth, frameCallbackHeight); //绑定纹理,纹理输出 EasyGlUtils.bindFrameTexture(fFrame[0], fTexture[0]); //调用GLES20.glDrawElements() GLES20.glDrawArrays()将片元绘制到该Framebuffer绑定的纹理上 drawTexture(2); //Y 镜像 //调用回调显示 frameCallback(); //解绑定 EasyGlUtils.unBindFrameBuffer(); } } //读取数据并回调 private void frameCallback(){ //OpenGL提供了简洁的函数来操作像素: //glReadPixels:读取一些像素。当前可以简单理解为“把已经绘制好的像素(它可能已经被保存到显卡的显存中)读取到内存”。 //glDrawPixels:绘制一些像素。当前可以简单理解为“把内存中一些数据作为像素数据,进行绘制”。 //glCopyPixels:复制一些像素。当前可以简单理解为“把已经绘制好的像素从一个位置复制到另一个位置”。 //虽然从功能上看,好象等价于先读取像素再绘制像素,但实际上它不需要把已经绘制的像素(它可能已经被保存到显卡的显存中)转换为内存数据,然后再由内存数据进行重新的绘制, //所以要比先读取后绘制快很多。 //这三个函数可以完成简单的像素读取、绘制和复制任务,但实际上也可以完成更复杂的任务 //glReadPixels实际上是从缓冲区中读取数据,如果使用了双缓冲区, //则默认是从正在显示的缓冲(即前缓冲)中读取,而绘制工作是默认绘制到后缓冲区的。因此,如果需要读取已经绘制好的像素,往往需要先交换前后缓冲 /* void glReadPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * data) type和format要匹配上: format: GL_RGBA,GL_RGB,GL_ALPHA,GL_LUMINANCE等格式 GL_UNSIGNED_BYTE,0-255 GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, or GL_UNSIGNED_SHORT_5_5_5_1, 这个每一个通道的范围在0-2n次方的范围内 查询匹配的format和type值方法: glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE,&eReadType); glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT,&eReadFormat); */ GLES20.glReadPixels(0, 0, frameCallbackWidth, frameCallbackHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, outPutBuffer[indexOutput]); onFrameCallback.onFrame(outPutBuffer[indexOutput].array(),0); } private int[] fFrame = new int[1]; private int[] fRender = new int[1]; private int[] fTexture = new int[1]; private void deleteFrameBuffer() { //GLES20.glDeleteRenderbuffers(1, fRender, 0); GLES20.glDeleteFramebuffers(1, fFrame, 0); GLES20.glDeleteTextures(1, fTexture, 0); } public SurfaceRenderer(Context context, Surface surface, int width, int height) { Log.e ("TAG", " SurfaceRenderer create "); this.surface = surface; this.width = width; this.height = height; this.running = true; this.context = context; instance = (TextureViewMediaActivity)context; videoTextureTransform = new float[16]; Thread thread = new Thread(this); // 渲染线程 thread.start(); } @Override public void run() { initEGL(); initGLComponents(); deleteFrameBuffer(); GLES20.glGenFramebuffers(1, fFrame, 0); //产生Framebuffers EasyGlUtils.genTexturesWithParameter(1, fTexture, 0, GLES20.GL_RGBA, width, height);//生成纹理 Log.d(LOG_TAG, "OpenGL init OK. start draw..."); while (running) { if (draw()) { //EGL交换缓存区,实现双缓存交换并刷新显示缓存(由底层的FramebufferNativeWindow输出--FramebufferNativeWindo是ANativeWindow的继承类,其内部实现了queuebuffer dequeuebuffer等操作) //双缓冲刷新 front buffer 和 back buffer //eglSwapBuffers会去触发queuebuffer,dequeuebuffer, //queuebuffer将画好的buffer(back->front)交给surfaceflinger处理, //dequeuebuffer新创建一个buffer用来画图 egl.eglSwapBuffers(eglDisplay, eglSurface); } } deinitGLComponents(); deinitEGL(); } private void initEGL() { egl = (EGL10)EGLContext.getEGL(); // eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); //version int version[] = new int[2]; egl.eglInitialize(eglDisplay, version); // 初始化显示设备、获取EGL版本号 EGLConfig eglConfig = chooseEglConfig(); //将Surface转换为本地窗口 eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, surface, null); // 创建EGLSurface,通过上层传入的Surface surface创建本地EGLSurface(ANativeWindow) eglContext = createContext(egl, eglDisplay, eglConfig); try { if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("GL error:" + GLUtils.getEGLErrorString(egl.eglGetError())); } //将EGLDisplay、EGLSurface和EGLContext进行绑定(渲染上下文绑定到渲染面,指定当前的环境为绘制环境 EGLContext->context) ///eglMakeCurrent后生成的surface就可以利用opengl画图了 if (!egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { // 绑定EGLSurface到本地EGLContext上下文,实现上层opengles的surface到底层egl的eglSurface的全局环境绑定 throw new RuntimeException("GL Make current Error"+ GLUtils.getEGLErrorString(egl.eglGetError())); } }catch (Exception e) { e.printStackTrace(); } } private void deinitEGL() { egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl.eglDestroySurface(eglDisplay, eglSurface); egl.eglDestroyContext(eglDisplay, eglContext); egl.eglTerminate(eglDisplay); Log.d(LOG_TAG, "OpenGL deinit OK."); } //创建EGL环境, EGLContext: OpenGL ES图形上下文,它代表了OpenGL状态机 private EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { //EGLContext 属性 int[] attrs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, // opengles 客户版本 2.0 EGL10.EGL_NONE }; return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrs); //根据EGLContext属性、EGLConfig配置,创建EGLContext(egl上下文) } // private EGLConfig chooseEglConfig() { int[] configsCount = new int[1]; EGLConfig[] configs = new EGLConfig[1]; int[] attributes = getAttributes(); int confSize = 1; if (!egl.eglChooseConfig(eglDisplay, attributes, configs, confSize, configsCount)) { throw new IllegalArgumentException("Failed to choose config:"+ GLUtils.getEGLErrorString(egl.eglGetError())); } else if (configsCount[0] > 0) { return configs[0]; } return null; } //EGLConfig 属性 private int[] getAttributes() { return new int[] { EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, //渲染类型 EGL_OPENGL_ES2 EGL10.EGL_RED_SIZE, 8, // 渲染rgba大小 EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 8, EGL10.EGL_DEPTH_SIZE, 0, // EGL_DEPTH_SIZE 深度、模板尺寸 EGL10.EGL_STENCIL_SIZE, 0, EGL10.EGL_NONE }; } public void onPause(){ running = false; } @Override protected void finalize() throws Throwable { super.finalize(); running = false; } public int mColorFlag=0; public int xyFlag=0; public int mRatio; public float ratio=0.5f; public int textureHandle; public int textureIdOne; private int gHWidth; private int gHHeight; private float[] matrix=new float[16]; private float[] matrix0=new float[16]; private float[] mModelMatrix=new float[16]; private float[] mModelMatrix0=new float[16]; //镜像 public float[] flip(float[] m,boolean x,boolean y){ if(x||y){ Matrix.scaleM(m,0,x?-1:1,y?-1:1,1); } return m; } public void setSize(){ Matrix.setIdentityM(mModelMatrix,0); Matrix.setIdentityM(mModelMatrix0,0); matrix = flip(mModelMatrix, true, false); matrix0 = flip(mModelMatrix0, false, true); } private void setupGraphics() { final String vertexShader = HelpUtils.readTextFileFromRawResource(context, R.raw.vetext_sharder); final String fragmentShader = HelpUtils.readTextFileFromRawResource(context, R.raw.fragment_sharder); final int vertexShaderHandle = HelpUtils.compileShader(GLES20.GL_VERTEX_SHADER, vertexShader); final int fragmentShaderHandle = HelpUtils.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader); shaderProgram = HelpUtils.createAndLinkProgram(vertexShaderHandle, fragmentShaderHandle, new String[]{"texture","vPosition","vTexCoordinate","textureTransform"}); GLES20.glUseProgram(shaderProgram); textureParamHandle = GLES20.glGetUniformLocation(shaderProgram, "texture"); // 摄像头图像外部扩展纹理 textureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "vTexCoordinate"); // 顶点纹理坐标 positionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition"); // 顶点坐标 textureTranformHandle = GLES20.glGetUniformLocation(shaderProgram, "textureTransform"); textureHandle = GLES20.glGetUniformLocation(shaderProgram, "texture0"); // 获得贴图对应的纹理采样器句柄(索引) mRatio = GLES20.glGetUniformLocation(shaderProgram, "mratio"); // 融合因子 gHWidth=GLES20.glGetUniformLocation(shaderProgram,"mWidth"); // 视窗宽、高 gHHeight=GLES20.glGetUniformLocation(shaderProgram,"mHeight"); GLES20.glUniform1i(gHWidth,width); GLES20.glUniform1i(gHHeight,height); setSize(); } private void setupVertexBuffer() { // Draw list buffer ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder. length * 2); dlb.order(ByteOrder.nativeOrder()); //转换成本地字节序 drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); // Initialize the texture holder ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); //转换成本地字节序 vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); } private void setupTexture() { ByteBuffer texturebb = ByteBuffer.allocateDirect(textureCoords.length * 4); texturebb.order(ByteOrder.nativeOrder()); // 转换成本地字节序 textureBuffer = texturebb.asFloatBuffer(); textureBuffer.put(textureCoords); textureBuffer.position(0); // Generate the actual texture GLES20.glActiveTexture(GLES20.GL_TEXTURE0); // 激活(使能)相应的纹理单元 GLES20.glGenTextures(1, textures, 0); // 产生纹理id checkGlError("Texture generate"); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]); //通过纹理id,绑定到相应的纹理单元,纹理单元内存放的类型可以很多种,比如GLES20.GL_TEXTURE_1D、GLES20.GL_TEXTURE_2D、GLES20.GL_TEXTURE_3D、GLES11Ext.GL_TEXTURE_EXTERNAL_OES等 checkGlError("Texture bind"); videoTexture = new SurfaceTexture(textures[0]); // 通过创建的纹理id,生成SurfaceTexture videoTexture.setOnFrameAvailableListener(this); } public int initTexture(int drawableId) { //生成纹理ID int[] textures = new int[1]; GLES20.glGenTextures ( 1, //产生的纹理id的数量 textures, //纹理id的数组 0 //偏移量 ); int textureId = textures[0]; Log.i(LOG_TAG, " initTexture textureId = " + textureId); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST); // 纹素放大、缩小设置GL_LINEAR对应线性滤波,GL_NEAREST对应最近邻滤波方式 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_CLAMP_TO_EDGE); // 纹理边界处理,当纹理坐标超出[0,1]的范围时该怎么处理,GL_CLAMP_TO_EDGE --- 纹理坐标会被截断到[0,1]之间。坐标值大的被截断到纹理的边缘部分,形成了一个拉伸的边缘 GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_CLAMP_TO_EDGE); //加载图片 InputStream is = context.getResources().openRawResource(drawableId); Bitmap bitmapTmp; try { bitmapTmp = BitmapFactory.decodeStream(is); } finally { try { is.close(); } catch(IOException e) { e.printStackTrace(); } } //载纹理 GLUtils.texImage2D ( GLES20.GL_TEXTURE_2D, //纹理类型,在OpenGL ES中必须为GL10.GL_TEXTURE_2D 0, //纹理的层次,0表示基本图像层,直接贴图 bitmapTmp, //纹理图像 0 //纹理边框尺寸 ); bitmapTmp.recycle(); //纹理加载成功后释放图片 return textureId; } protected boolean draw() { synchronized (this){ if (frameAvailable) { videoTexture.updateTexImage(); // 更新SurfaceTexture纹理图像信息,然后绑定的GLES11Ext.GL_TEXTURE_EXTERNAL_OES纹理才能渲染 videoTexture.getTransformMatrix(videoTextureTransform); // 获取SurfaceTexture纹理变换矩 frameAvailable = false; } else{ return false; } } GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //设置清除颜色 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); //GL_COLOR_BUFFER_BIT 设置窗口颜色 //GL_DEPTH_BUFFER_BIT 设置深度缓存--把所有像素的深度值设置为最大值(一般为远裁剪面) GLES20.glViewport(0, 0, width, height); drawTexture(); callbackIfNeeded(); //离屏渲染、回调显示 return true; } private void drawTexture() { // Draw texture int mHProjMatrix=GLES20.glGetUniformLocation(shaderProgram,"uProjMatrix"); GLES20.glUniformMatrix4fv(mHProjMatrix,1,false,matrix,0); int mHProjMatrix0=GLES20.glGetUniformLocation(shaderProgram,"uProjMatrix0"); GLES20.glUniformMatrix4fv(mHProjMatrix0,1,false,matrix0,0); int mXyFlag = GLES20.glGetUniformLocation(shaderProgram, "xyFlag"); //镜像类型: x镜像,y镜像---通过不同的变化矩阵与顶点位置向量进行左乘,如:uProjMatrix*vPosition; GLES20.glUniform1i(mXyFlag, xyFlag); int mColorFlagHandle = GLES20.glGetUniformLocation(shaderProgram, "colorFlag"); // 纹理操作类型(滤镜处理):饱和度/灰度/冷暖色/放大镜/模糊/美颜/纹理融合 GLES20.glUniform1i(mColorFlagHandle, mColorFlag); //顶点属性一般包括位置、颜色、法线、纹理坐标属性 GLES20.glEnableVertexAttribArray(positionHandle); // 使能相应的顶点位置属性的顶点属性数组 GLES20.glVertexAttribPointer(positionHandle, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer); // 指定(绑定)该相应的顶点位置属性的顶点属性数组 GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textures[0]); // 摄像头图像纹理 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glUniform1i(textureParamHandle, 0); GLES20.glActiveTexture(GLES20.GL_TEXTURE1); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIdOne); // 贴图的图像纹理 GLES20.glUniform1i(textureHandle, 1); GLES20.glEnableVertexAttribArray(textureCoordinateHandle); GLES20.glVertexAttribPointer(textureCoordinateHandle, 4, GLES20.GL_FLOAT, false, 0, textureBuffer); GLES20.glUniformMatrix4fv(textureTranformHandle, 1, false, videoTextureTransform, 0); // GL_TEXTURE_EXTERNAL_OES纹理的变化矩 GLES20.glUniform1f(mRatio, ratio); // 纹理融合因子 GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer); // 根据顶点位置索引进行绘制片元 GLES20.glDisableVertexAttribArray(positionHandle); GLES20.glDisableVertexAttribArray(textureCoordinateHandle); } protected void initGLComponents() { setupVertexBuffer(); setupTexture(); setupGraphics(); textureIdOne = initTexture(R.drawable.bg); Message message = new Message(); message.what = 1; instance.myHandler.sendMessage(message); } protected void deinitGLComponents() { GLES20.glDeleteTextures(1, textures, 0); GLES20.glDeleteProgram(shaderProgram); videoTexture.release(); videoTexture.setOnFrameAvailableListener(null); } public void checkGlError(String op) { int error; while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { Log.e("SurfaceTest", op + ": glError " + GLUtils.getEGLErrorString(error)); } } public SurfaceTexture getVideoTexture() { return videoTexture; } @Override public void onFrameAvailable(SurfaceTexture surfaceTexture) { synchronized (this){ frameAvailable = true; } }}
设置camera2 预览、mediacodec/medianuxer 视频编码设置
package com.vr.jarry.playvideo_texuture;import android.Manifest;import android.app.Activity;import android.content.Intent;import android.content.pm.PackageManager;import android.graphics.Bitmap;import android.graphics.SurfaceTexture;import android.hardware.camera2.CameraAccessException;import android.hardware.camera2.CameraCaptureSession;import android.hardware.camera2.CameraCharacteristics;import android.hardware.camera2.CameraDevice;import android.hardware.camera2.CameraManager;import android.hardware.camera2.CaptureRequest;import android.hardware.camera2.CaptureResult;import android.hardware.camera2.TotalCaptureResult;import android.media.MediaCodec;import android.media.MediaCodecInfo;import android.media.MediaFormat;import android.media.MediaMuxer;import android.media.MediaPlayer;import android.net.Uri;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.support.v4.app.ActivityCompat;import android.os.Bundle;import android.util.Log;import android.view.Surface;import android.view.SurfaceHolder;import android.view.SurfaceView;import android.view.TextureView;import android.view.View;import android.view.Window;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.Toast;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.util.Arrays;import com.vr.jarry.playvideo_texuture.SurfaceRenderer.OnFrameCallback;public class TextureViewMediaActivity extends Activity implements OnFrameCallback, TextureView.SurfaceTextureListener{ private static final String TAG = "GLViewMediaActivity"; private boolean clickFlag = false; public static final String videoPath = Environment.getExternalStorageDirectory()+"/live.mp4"; private SurfaceRenderer videoRenderer; private Button btn_shutter, btn_mirror, btn_color; ImageView imagView; Surface mEncoderSurface; BufferedOutputStream outputStream; private MediaCodec mCodec, mDecodec; boolean isEncode = false; private MediaMuxer mMuxer; TextureView mPreviewView; CameraCaptureSession mSession; CaptureRequest.Builder mPreviewBuilder; public CameraDevice mCameraDevice; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main_0); mPreviewView = (TextureView) findViewById(R.id.id_textureview); mPreviewView.setSurfaceTextureListener(this); imagView = (ImageView) findViewById(R.id.id_textureview0); SeekBar seekBar = (SeekBar) findViewById(R.id.id_seekBar); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub if(videoRenderer != null) { videoRenderer.ratio = progress/100.0f; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } }); btn_color = (Button) findViewById(R.id.btn_color); btn_shutter = (Button) findViewById(R.id.btn_shutter); btn_mirror = (Button) findViewById(R.id.btn_mirror); Button btn_play = (Button) findViewById(R.id.btn_play); btn_play.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { File f = new File(mOutputPath); if(f.exists() && mVideoTrack==-1){ Log.e(TAG, " play video "); Intent intent = new Intent(Intent.ACTION_VIEW); //intent.setDataAndType(Uri.parse(mOutputPath), "video/mp4"); intent.setDataAndType(Uri.parse(Environment.getExternalStorageDirectory().getAbsolutePath()+"/mcodecmux26.mp4"), "video/mp4"); startActivity(intent); }else { Log.e(TAG, " can not play video "); if(!f.exists()) { Toast.makeText(TextureViewMediaActivity.this, "Video file not exists!", Toast.LENGTH_SHORT).show(); }else { if(mVideoTrack != -1) { Toast.makeText(TextureViewMediaActivity.this, "Video record not stop!", Toast.LENGTH_SHORT).show(); } } } } }); btn_shutter.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub clickFlag = !clickFlag; if(clickFlag) { if(cameraFlag) { Toast.makeText(TextureViewMediaActivity.this, "Start Record!", Toast.LENGTH_SHORT).show(); btn_shutter.setText("Stop"); try { cameraManager.openCamera(CameraIdList[0], mCameraDeviceStateCallback, null); } catch (CameraAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } startCodec(); }else { Toast.makeText(TextureViewMediaActivity.this, "No camera permission!", Toast.LENGTH_SHORT).show(); } }else { btn_shutter.setText("Start"); videoRenderer.running = false; try { videoRenderer.join(); Log.e(TAG, " videoRenderer stop "); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if (mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } stopCodec(); Toast.makeText(TextureViewMediaActivity.this, "Stop Record!", Toast.LENGTH_SHORT).show(); /* try { mSession.stopRepeating(); mPreviewBuilder.removeTarget(surface); mPreviewBuilder.removeTarget(surface0); surface.release(); surface0.release(); surface = null; surface0 = null; mSession.close(); Log.e(TAG, " mSession stop "); } catch (CameraAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } */ } } }); btn_color.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(videoRenderer != null) { if(videoRenderer.mColorFlag == 0) { videoRenderer.mColorFlag = 7; Toast.makeText(TextureViewMediaActivity.this, "Saturation adjust!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 7) { videoRenderer.mColorFlag = 1; Toast.makeText(TextureViewMediaActivity.this, "Gray Color!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 1) { videoRenderer.mColorFlag = 2; Toast.makeText(TextureViewMediaActivity.this, "Warm Color!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 2){ videoRenderer.mColorFlag = 3; Toast.makeText(TextureViewMediaActivity.this, "Cool Color!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 3){ videoRenderer.mColorFlag = 4; Toast.makeText(TextureViewMediaActivity.this, "Amplify!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 4){ videoRenderer.mColorFlag = 5; Toast.makeText(TextureViewMediaActivity.this, "Vague!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag == 5){ videoRenderer.mColorFlag = 6; Toast.makeText(TextureViewMediaActivity.this, "Beauty!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.mColorFlag ==6){ videoRenderer.mColorFlag = 0; Toast.makeText(TextureViewMediaActivity.this, "Orignal Color!", Toast.LENGTH_SHORT).show(); } } } }); btn_mirror.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if(videoRenderer != null) { if(videoRenderer.xyFlag == 0) { Toast.makeText(TextureViewMediaActivity.this, "X Mirror!", Toast.LENGTH_SHORT).show(); videoRenderer.xyFlag = 1; }else if(videoRenderer.xyFlag == 1){ videoRenderer.xyFlag = 2; Toast.makeText(TextureViewMediaActivity.this, "Y Mirror!", Toast.LENGTH_SHORT).show(); }else if(videoRenderer.xyFlag == 2) { videoRenderer.xyFlag = 0; Toast.makeText(TextureViewMediaActivity.this, "Normal!", Toast.LENGTH_SHORT).show(); } } } }); } public Handler myHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.d(TAG," videoTexture created! "); try { startPreview(mCameraDevice); } catch (CameraAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } break; case 2: Log.d(TAG," vidoe play! "); Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(Uri.parse(mOutputPath), "video/mp4"); //intent.setDataAndType(Uri.parse(Environment.getExternalStorageDirectory().getAbsolutePath()+"/mcodecmux26.mp4"), "video/mp4"); startActivity(intent); break; default : break; } super.handleMessage(msg); } }; Bitmap bitmap=Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); @Override public void onFrame(byte[] bytes, long time) { // TODO Auto-generated method stub ByteBuffer b=ByteBuffer.wrap(bytes); bitmap.copyPixelsFromBuffer(b); //imagView.setImageBitmap(bitmap); runOnUiThread(new Runnable() { @Override public void run() { imagView.setImageBitmap(bitmap); } }); } protected String getSD(){ return Environment.getExternalStorageDirectory().getAbsolutePath(); } //图片保存 public void saveBitmap(Bitmap b){ String path = getSD()+ "/photo/"; File folder=new File(path); if(!folder.exists()&&!folder.mkdirs()){ runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(TextureViewMediaActivity.this, "can not save!", Toast.LENGTH_SHORT).show(); } }); return; } long dataTake = System.currentTimeMillis(); final String jpegName=path+ dataTake +".jpg"; try { FileOutputStream fout = new FileOutputStream(jpegName); BufferedOutputStream bos = new BufferedOutputStream(fout); b.compress(Bitmap.CompressFormat.JPEG, 100, bos); bos.flush(); bos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(TextureViewMediaActivity.this, "save->"+jpegName, Toast.LENGTH_SHORT).show(); } }); } private String path = Environment.getExternalStorageDirectory() + "/mcodecv26.264"; private String mOutputPath = Environment.getExternalStorageDirectory() + "/mcodecmux26.mp4"; public void startCodec() { for(int i=0; i<2; i++) { File f = null; if(i == 0) { f = new File(path); }else if(i == 1) { f = new File(mOutputPath); } if(!f.exists()){ try { f.createNewFile(); Log.e(TAG, " create a file "); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ if(f.delete()){ try { f.createNewFile(); Log.e(TAG, " delete and create a file "); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } if(i == 0) { try { outputStream = new BufferedOutputStream(new FileOutputStream(f)); Log.i(TAG, "outputStream initialized"); } catch (Exception e){ e.printStackTrace(); } } } try { mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC); //mCodec = MediaCodec.createByCodecName(codecInfo.getName()); } catch (IOException e) { e.printStackTrace(); } MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mPreviewView.getWidth(), mPreviewView.getHeight()); mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 500000);//500kbps mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15); //mediaFormat.setInteger("bitrate-mode", 2); //ediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); //COLOR_FormatSurface //mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, // MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); //COLOR_FormatYUV420Planar //mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 90); mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); mCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mEncoderSurface = mCodec.createInputSurface(); //用于使出话 mCodec.setCallback(new EncoderCallback()); mCodec.start(); try { mMuxer = new MediaMuxer(mOutputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } videoRenderer = new SurfaceRenderer(TextureViewMediaActivity.this, mEncoderSurface, mPreviewView.getWidth(), mPreviewView.getHeight()); //回调显示接口设置 videoRenderer.setOnFrameCallback(mPreviewView.getWidth(), mPreviewView.getHeight(), TextureViewMediaActivity.this); videoRenderer.start(); videoRenderer.mColorFlag = 4; } public void stopCodec() { try { new Thread() { @Override public void run(){ //mCodec.setCallback(null); while(mCodec != null) { if(!endFlag) { Log.e(TAG, " stopCodec start "); //mCodec.flush(); //mCodec.setCallback(null); mCodec.stop(); mCodec.release(); mCodec = null; mMuxer.stop(); mMuxer.release(); mMuxer=null; mVideoTrack=-1; Log.i(TAG, " stopCodec end "); } } //Message message = new Message(); //message.what = 2; //myHandler.sendMessage(message); } }.start(); } catch (Exception e) { e.printStackTrace(); mCodec = null; } } int mCount = 0; private int mVideoTrack=-1; private boolean isMuxStarted=false; boolean endFlag = false; private class EncoderCallback extends MediaCodec.Callback{ @Override public void onInputBufferAvailable(MediaCodec codec, int index) { // } @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { endFlag = true; ByteBuffer outPutByteBuffer = mCodec.getOutputBuffer(index); byte[] outDate = new byte[info.size]; outPutByteBuffer.get(outDate); try { //Log.d(TAG, " outDate.length : " + outDate.length); outputStream.write(outDate, 0, outDate.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(isMuxStarted && info.size>0 && info.presentationTimeUs>0){ mMuxer.writeSampleData(mVideoTrack, outPutByteBuffer, info); } mCodec.releaseOutputBuffer(index, false); if(info.flags==MediaCodec.BUFFER_FLAG_END_OF_STREAM){ Log.d(TAG, "CameraRecorder get video encode end of stream"); } endFlag = false; } @Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { Log.d(TAG, "Error: " + e); } @Override public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { Log.d(TAG, "encoder output format changed: " + format); Log.d(TAG, "encoder output format changed: " + format); mVideoTrack=mMuxer.addTrack(codec.getOutputFormat()); mMuxer.start(); isMuxStarted=true; } } CameraManager cameraManager; String[] CameraIdList; boolean cameraFlag = false; @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { cameraManager = (CameraManager) getSystemService(CAMERA_SERVICE); try { Log.i(TAG, "onSurfaceTextureAvailable: width = " + width + ", height = " + height); CameraIdList = cameraManager.getCameraIdList(); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(CameraIdList[0]); characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL); if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { cameraFlag = false; return; }else { cameraFlag = true; } //cameraManager.openCamera(CameraIdList[0], mCameraDeviceStateCallback, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {} @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {} private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { Log.i(TAG, " CameraDevice.StateCallback onOpened "); mCameraDevice = camera; //startPreview(camera); } @Override public void onDisconnected(CameraDevice camera) { if (null != mCameraDevice) { mCameraDevice.close(); mCameraDevice = null; } } @Override public void onError(CameraDevice camera, int error) {} }; Surface surface; Surface surface0; private void startPreview(CameraDevice camera) throws CameraAccessException { //f = new File(path);//Environment.getExternalStorageDirectory(), "camera2mediacodecv.264"); SurfaceTexture texture = mPreviewView.getSurfaceTexture(); texture.setDefaultBufferSize(mPreviewView.getWidth(), mPreviewView.getHeight()); surface = new Surface(texture); surface0 = new Surface(videoRenderer.getVideoTexture()); Log.i(TAG, " startPreview "); try { mPreviewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); //CameraDevice.TEMPLATE_STILL_CAPTURE } catch (CameraAccessException e) { e.printStackTrace(); } mPreviewBuilder.addTarget(surface); mPreviewBuilder.addTarget(surface0); camera.createCaptureSession(Arrays.asList(surface, surface0), mSessionStateCallback, null); } private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { try { Log.i(TAG, " onConfigured "); //session.capture(mPreviewBuilder.build(), mSessionCaptureCallback, mHandler); mSession = session; mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); session.setRepeatingRequest(mPreviewBuilder.build(), null, null); //null } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) {} }; int callback_time; private CameraCaptureSession.CaptureCallback mSessionCaptureCallback =new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { //Toast.makeText(TextureViewMediaActivity.this, "picture success閿涳拷", Toast.LENGTH_SHORT).show(); callback_time++; Log.i(TAG, " CaptureCallback = "+callback_time); } @Override public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) { Toast.makeText(TextureViewMediaActivity.this, "picture failed閿涳拷", Toast.LENGTH_SHORT).show(); } }; @Override protected void onResume() { super.onResume(); Log.v(TAG, "GLViewMediaActivity::onResume()"); } @Override protected void onStart(){ Log.v(TAG, "GLViewMediaActivity::onStart()"); super.onStart(); } @Override protected void onPause() { Log.v(TAG, "GLViewMediaActivity::onPause()"); super.onPause(); } @Override protected void onStop(){ Log.v(TAG, "GLViewMediaActivity::onStop()"); super.onStop(); } @Override protected void onDestroy(){ Log.v(TAG, "GLViewMediaActivity::onDestroy()"); super.onDestroy(); if(mCameraDevice != null) { mCameraDevice.close(); mCameraDevice = null; } }}
demo 效果图:
更多相关文章
- Android(安卓)Service中的startService或者bindService的区别
- 在layout中直接指定onclick方法
- Android(安卓)Handler 绑定自定义线程之HandlerThread
- android 中文api (63) —— SimpleAdapter.ViewBinder
- startService与bindService
- Android(安卓)Webkit简单用例
- Android杂谈---res目录说明&android单位
- ButterKnife基本使用
- osg for android 学习之七:绘制基本的对象