android相机
输出的原始数据格式一般都是
NV21(实际上就是YUV420SP的格式)
或者
NV12(实际上就是YUV420P的格式)
,笔者的
小米MIX 2S
的默认输出格式就是
NV21
的,关于格式的问题,后续博客再详细说明。
前面的博客以及说明了如何通过
OpenGL ES
来渲染图像,一般
YUV格式
的数据是无法直接用
OpenGL ES
来渲染的,而在OpenGL中使用的绝大部分纹理ID都是RGBA的格式,在
OpenGL ES 3.0
的扩展
#extension GL_OES_EGL_image_external_essl3
定义了一个纹理的扩展类型,即
GL_TEXTURE_EXTERNAL_OES
,否则整个转换过程将会非常复杂。同时这种纹理目标对纹理的使用方式也会有一些限制,纹理绑定需要绑定到类型
GL_TEXTURE_EXTERNAL_OES
上,而不是类型GL_TEXTURE_2D上,对纹理设置参数也要使用
GL_TEXTURE_EXTERNAL_OES
类型,生成纹理与设置纹理参数的代码如下:
int[] tex = new int[1];
GLES30.glGenTextures(1, tex, 0);
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
在实际的渲染过程中绑定纹理的代码如下:
GLES30.glActiveTexture(GL_TEXTURE0);
GLES30.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
GLES30.glUniform1i(uniformSamplers, 0);
在OpenGL ES的顶点着色器
中,任何需要从纹理中采样的OpenGL ES的顶点着色器
都需要声明其对此扩展的使用,使用指令如下:
#extension GL_OES_EGL_image_external : require
#extension GL_OES_EGL_image_external_essl3 : require
上面的过程就是使用这种扩展类型的纹理ID从创建到设置参数,再到真正的渲染整个过程,接下来再根据一个完整的示例
看一下具体的旋转角度问题,因为在使用摄像头的时候很容易在预览的时候会出现倒立、镜像等问题。
先不多说,直接实践看效果,基于之前的项目工程,新建CameraSurfaceRenderer.java
文件:
* @anchor: andy
* @date: 2018-11-09
* @description: 基于相机
public class CameraSurfaceRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "TextureRenderer";
private final FloatBuffer vertexBuffer, mTexVertexBuffer;
private final ShortBuffer mVertexIndexBuffer;
private int mProgram;
private int textureId;
* 顶点坐标
* (x,y,z)
private float[] POSITION_VERTEX = new float[]{
0f, 0f, 0f,
1f, 1f, 0f,
-1f, 1f, 0f,
-1f, -1f, 0f,
1f, -1f, 0f
* 纹理坐标
* (s,t)
private static final float[] TEX_VERTEX = {
0.5f, 0.5f,
1f, 1f,
0f, 1f,
0f, 0.0f,
1f, 0.0f
private static final short[] VERTEX_INDEX = {
0, 1, 2,
0, 2, 3,
0, 3, 4,
0, 4, 1
private float[] transformMatrix = new float[16];
* 渲染容器
private GLSurfaceView mGLSurfaceView;
* 相机ID
private int mCameraId;
* 相机实例
private Camera mCamera;
* Surface
private SurfaceTexture mSurfaceTexture;
* 矩阵索引
private int uTextureMatrixLocation;
private int uTextureSamplerLocation;
public CameraSurfaceRenderer(GLSurfaceView glSurfaceView) {
this.mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
this.mGLSurfaceView = glSurfaceView;
mCamera = Camera.open(mCameraId);
setCameraDisplayOrientation(mCameraId, mCamera);
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
vertexBuffer.put(POSITION_VERTEX);
vertexBuffer.position(0);
mTexVertexBuffer = ByteBuffer.allocateDirect(
TEX_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(TEX_VERTEX);
mTexVertexBuffer.position(0);
mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(VERTEX_INDEX);
mVertexIndexBuffer.position(0);
private void setCameraDisplayOrientation(int cameraId, Camera camera) {
Activity targetActivity = (Activity) mGLSurfaceView.getContext();
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = targetActivity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (info.orientation - degrees + 360) % 360;
camera.setDisplayOrientation(result);
* 加载外部纹理
* @return
public int loadTexture() {
int[] tex = new int[1];
GLES30.glGenTextures(1, tex, 0);
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
return tex[0];
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_camera_shader));
final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_camera_shader));
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
uTextureMatrixLocation = GLES30.glGetUniformLocation(mProgram, "uTextureMatrix");
uTextureSamplerLocation = GLES30.glGetUniformLocation(mProgram, "yuvTexSampler");
textureId = loadTexture();
loadSurfaceTexture(textureId);
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
GLES30.
glUseProgram(mProgram);
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(transformMatrix);
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES30.glUniform1i(uTextureSamplerLocation, 0);
GLES30.glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, mTexVertexBuffer);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
public boolean loadSurfaceTexture(int textureId) {
mSurfaceTexture = new SurfaceTexture(textureId);
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mGLSurfaceView.requestRender();
});
try {
mCamera.setPreviewTexture(mSurfaceTexture);
} catch (IOException e) {
e.printStackTrace();
return false;
mCamera.startPreview();
return true;
顶点着色器:
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aTextureCoord;
uniform mat4 uTextureMatrix;
out vec2 yuvTexCoords;
void main() {
gl_Position = vPosition;
gl_PointSize = 10.0;
yuvTexCoords = (uTextureMatrix * aTextureCoord).xy;
片段着色器:
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
vFragColor = texture(yuvTexSampler,yuvTexCoords);
注意:
外部纹理扩展在OpenGL ES 2.0
和OpenGL ES 3.0
中不太一样。
#extension GL_OES_EGL_image_external : require
在启动的Activity中处理初始化的部分:
private void setupView() {
mGLSurfaceView = new GLSurfaceView(this);
mGLSurfaceView.setEGLContextClientVersion(3);
mGLSurfaceView.setRenderer(new CameraSurfaceRenderer(mGLSurfaceView));
setContentView(mGLSurfaceView);
来个剪刀手看看效果:
通过纹理将相机采集的数据渲染到GLSurfaceView
的过程中,我们也可以添加各种滤镜。
给相机添加黑白滤镜,修改片段着色器
#version 300 es
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
vec4 vCameraColor = texture(yuvTexSampler,yuvTexCoords);
float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
vFragColor = vec4(fGrayColor, fGrayColor, fGrayColor, 1.0);
可以参照下面的几个转换公式:
标清电视使用的标准BT.601
\begin{bmatrix} Y^{'} \\ U \\ V \\ \end{bmatrix} = \begin{bmatrix} 0.299 & 0.587 & 0.114\\ -0.14713 & -0.28886 & 0.436 \\ 0.615 & -0.51499 & -0.10001 \end{bmatrix} \begin{bmatrix} R \\ G \\ B \\ \end{bmatrix}
⎣⎡Y′UV⎦⎤=⎣
⎡0.299−0.147130.6150.587−0.28886−0.514990.1140.436−0.10001⎦⎤⎣⎡RGB⎦⎤
\begin{bmatrix} R \\ G \\ B \\ \end{bmatrix} = \begin{bmatrix} 1 & 0 & 1.13983\\ 1 & -0.39465 & -0.58060 \\ 1 & 2.03211 & 0 \end{bmatrix} \begin{bmatrix} Y^{'} \\ U \\ V \\ \end{bmatrix}
⎣⎡RGB⎦⎤=⎣⎡1110−0.394652.032111.13983−0.580600⎦⎤⎣⎡Y′UV⎦⎤
标清电视使用的标准BT.709
\begin{bmatrix} Y^{'} \\ U \\ V \\ \end{bmatrix} = \begin{bmatrix} 0.2126 & 0.7152 & 0.0722\\ -0.09991 & -0.33609 & 0.436 \\ 0.615 & -0.55861 & -0.05639 \end{bmatrix} \begin{bmatrix} R \\ G \\ B \\ \end{bmatrix}
⎣⎡Y′
UV⎦⎤=⎣⎡0.2126−0.099910.6150.7152−0.33609−0.558610.07220.436−0.05639⎦⎤⎣⎡RGB⎦⎤
\begin{bmatrix} R \\ G \\ B \\ \end{bmatrix} = \begin{bmatrix} 1 & 0 & 1.28033\\ 1 & -0.21482 & -0.38059 \\ 1 & 2012798 & 0 \end{bmatrix} \begin{bmatrix} Y^{'} \\ U \\ V \\ \end{bmatrix}
⎣⎡RGB⎦⎤=⎣⎡1110−0.2148220127981.28033−0.380590⎦⎤⎣⎡Y′UV⎦⎤
上述的例子完整的实现了相机预览数据通过OpenGL ES 3.0
实时渲染到GLSurfaceView
上。但是如果你稍不注意,很可能会出现手机摄像头预览的时候会出现倒立、镜像等问题,下面来看看这个过程。
前置摄像头从采集到最终显示的过程:
后置摄像头从采集到最终显示的过程:
OpenGL坐标:
OpenGL二维纹理坐标:
不做任何旋转的纹理坐标:
private static final float[] TEX_VERTEX = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
顺时针旋转90度的纹理坐标:(可以想象一下将上述的OpenGL二维纹理坐标顺时针旋转90度)
private static final float[] TEX_VERTEX = {
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 0.0f,
0.0f, 1.0f
顺时针旋转180度的纹理坐标:
private static final float[] TEX_VERTEX = {
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f
顺时针旋转270度的纹理坐标:
private static final float[] TEX_VERTEX = {
0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 1.0f,
1.0f, 0.0f
我们再来看看之前说的计算机中的图像坐标系:
所以我们实际在处理相机的预览图像时要对每一个纹理坐标做一个VFlip
的变换(即把每一个顶点的y值由0变为1或者由1变为0)
,这样就可以得到一个正确的图像旋转了。而前置摄像头还存在镜像的问题,因此需要对每一个纹理坐标做一个HFlip
的变换(即把每一个顶点的x值由0变为1或者由1变为0)
,从而让图片在预览界面中看起来就像在镜子中的一样。
项目地址:
https://github.com/byhook/opengles4android
参考:
https://blog.csdn.net/lb377463323/article/details/77071054#t0
《音视频开发进阶指南》
android平台下OpenGL ES 3.0对相机Camera预览实时处理摄像头的预览配置好摄像头之后,剩下的事情就是配置摄像头采集每一帧图像的回调,并且获取到图像之后将图像渲染到屏幕上。本书的第4章已经讲解过了如何通过OpenGL ES来渲染图像,这里先来回顾一下:首先把图像解码为RGBA格式;然后将RGBA格式的字节数组上传到一个纹理上;最终将该纹理渲染到屏幕上。所以这里的渲染到屏幕上也...
在前面使用OpenGLEs的过程中由于使用的是OpenGlEs3.0的版本所以会带来不少因为版本兼容问题的坑(开始的时候没有发现,因为一直使用的是Android8.1.0的系统的手机。后来测试兼容性的时候发现了坑,我这里最低版本的手机是装有4.4.2系统的)。
问题产生的原因是因为Android系统随着版本的不同里面自带的OpenGL ES版本也会随之不同,而在我们编写GLSL文件的时候如果...
最简单的办法,就是转换为RGB。这个有现在的代码,吾亦提供了一个:
https://blog.csdn.net/quantum7/article/details/105720150
性能较差。有...
一、问题描述
在将一份OpenGL ES的片段着色器从 GLSL 2.0 改成 GLSL 3.0语法的时候,编译完之后运行,报错了,如下所示:
2021-11-28 17:13:18.898 14115-14199/com.oyp.openglesdemo I/AdrenoGLES-0: ERROR: 0:32: 'texture2D' : type is for Vulkan api only
ERROR: 0:32: 'texture2D' : cannot construct this
一、相机简介
在Android OpenGL基础(三、绘制Bitmap纹理)一文中,我们简单介绍了如何绘制如何把一张图片贴到四边形上。本文介绍如何用GLSurfaceView来实现预览相机。与单张图片纹理不同的地方在于,相机是一个内容不断变化的纹理。
首先,先简单介绍相机的几个常用方法:
1.1 声明相机权限
如果APP需要使用相机,则需要在manifest.xml中声明:
<uses-permission android:name="android.permission.CAMERA"
OpenGL ES 3.0 上的一个三角形例子,网上可以下载到
android skd 版(java)和
android ndk (c&c++版)
为了了解一下JNI,于是写了如下小程序。
这个例子是
使用jni, java中调用c中的代码完成三角形的渲染, 其中shader代码保存在assets目录下,如下目录图:
Android OpenGL+Camera2渲染(1) —— OpenGL简单介绍
Android OpenGL+Camera2渲染(2) —— OpenGL实现Camera2图像预览
Android OpenGL+Camera2渲染(3) —— 大眼,贴纸功能实现
Android OpenGL+Camera2渲染(4) —— 美颜功能实现
Android OpenGL+Camera2渲染...
GLSurfaceView是Android平台上用于渲染OpenGL ES图形的视图。要从GLSurfaceView中获取图像,你可以使用Android的截屏功能来实现。
你可以使用以下代码来截取GLSurfaceView的图像:
// 获取GLSurfaceView实例
GLSurfaceView glSurfaceView = ...;
// 获取GLSurfaceView的Bitmap
Bitmap bitmap = glSurfaceView.getDrawingCache();
// 保存Bitmap到文件
FileOutputStream out = new FileOutputStream("screenshot.png");
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.close();
请注意,在使用getDrawingCache()方法之前,你需要先调用GLSurfaceView的setDrawingCacheEnabled(true)方法。
需要注意的是,GLSurfaceView的截图可能并不总是准确的,因为它可能在渲染时使用了缓存,或者因为屏幕的刷新率导致图像存在残影。如果你需要更精确的截图,你可以考虑使用OpenGL ES的帧缓冲区(Framebuffer Object)或者其他技术来实现。