OpenGL ES (17): 使用GLSurfaceView预览Camera,并拍照

1.权限


<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Tell the system this app requires OpenGL ES 2.0. -->
<uses-feature
    android:glEsVersion="0x00020000"
android:required="true" />

2.思路-流程


预览Camera我们以前是使用SurfaceView得到SurfaceHolder , 然后myCamera.setPreviewDisplay(SurfaceHolder),详细例子见文章

相机Camera(预览--拍照)--两种方式(包含横竖屏切换两种方式) 。

但是我们这次使用的是OpenGLES中的 GLSurfaceView 来预览Camera,说这个之前。我们先来说一说 纹理贴图吧!

之前纹理贴图的思路是什么?

  1. 得到GLSurfaceView表面的纹理id
  2. 把图片生成在对应的纹理id上面,并返回这个纹理id。
  3. 这样图片就和这个纹理id绑定了,但是还不知道纹理是如何绘制出来的。
  4. 所以需要传入纹理坐标,跟顶点坐标一一对应。
  5. 然后绘制,就形成了纹理贴图。

这次我们讲的GLSurfaceView预览Camera其实跟纹理贴图的思路差不多。可以理解成在 GLSurfaceView上画一个全屏大小的矩形,

把图片换成了相机Camera数据,把Camera数据 纹理贴图到 这个画的矩形上面,就形成了预览,是不是瞬间秒懂!

只不过还多了一个东西叫 SurfaceTexture ,这个东西是什么?

我把它理解为 纹理层,相当于 view表面的一层衣服,它与GLSurfaceView的表面纹理id 挂钩。

然后执行Camera.setPreviewTexture(SurfaceTexture)。相当于之前的 Camera.setPrieviewHolder(surfaceHolder)。

然后执行camera.startPrieView()就可以开始预览了。没结束,还要看下面的。

这个纹理层还需要设置一个监听SurfaceTexture.setOnFrameAvailableListener(this);监听相机传来的新的流帧,一旦纹理层有新的数据,就要通知GLSurfaceView进行重绘。GLSurfaceView使用脏模式 setRenderMode(RENDERMODE_WHEN_DIRTY) 。

@Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        //回调接口,用于通知新的流帧可用
        //纹理层有新数据,就通知view绘制
        this.requestRender();
    }

这样下来,使用GLSurfaceView预览相机Camera是不是变的很简单!

使用相机拍照还是跟之前一样!设置3个参数,第3个非常重要。

mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);

 ShutterCallback mShutterCallback = new ShutterCallback()
        //如果这个参数设置为Null,将没有卡擦的声音
    {
        public void onShutter() {
        }
    };

    PictureCallback mJpegPictureCallback = new PictureCallback()
            //对jpeg图像数据的回调,最重要的一个回调
    {
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap b = null;
            if(null != data){
                b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
                mCamera.stopPreview();
                isPreviewing = false;
            }
            //保存图片到sdcard
            if(null != b)
            {
                //图片这里要旋转下,相机拍出来的照片是倒着的
                Bitmap rotaBitmap = getRotateBitmap(b, 90.0f);
                saveBitmap(rotaBitmap);
            }
            //再次进入预览
            mCamera.startPreview();
            isPreviewing = true;
        }

    };

3.具体代码


  • MainActivity.java 
  • CameraGLSurfaceView.java    (实现了GLSurfaceView接口,Renderer接口,SurfaceTexture.OnFrameAvailableListener纹理层监听接口)
  • DirectDrawer.java     (一旦需要绘制,在GLSurfaceView中的onDrawFrame()方法中调用DirectDrawer.draw()进行绘制 )
  • CameraInterface.java     (相机Camera封装类,包含开启、预览、关闭)

MainActivity.java

public class MainActivity extends Activity{
    private static final String TAG = "TAG";
    CameraGLSurfaceView glSurfaceView = null;
    ImageButton shutterBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        glSurfaceView = (CameraGLSurfaceView)findViewById(R.id.camera_textureview);
        shutterBtn = (ImageButton)findViewById(R.id.btn_shutter);
        initViewParams();
        //拍照
        shutterBtn.setOnClickListener(new BtnListeners());
    }

    private void initViewParams(){
        LayoutParams params = glSurfaceView.getLayoutParams();
        Point p = getScreenMetrics(this);
        params.width = p.x; //view宽
        params.height = p.y; //view高
        //设置GLSurfaceView的宽和高
        glSurfaceView.setLayoutParams(params);
        //设置ImageButton的大小
        LayoutParams p2 = shutterBtn.getLayoutParams();
        p2.width = 100;
        p2.height = 100;
        shutterBtn.setLayoutParams(p2);
    }

    private Point getScreenMetrics(Context context){
        DisplayMetrics dm =context.getResources().getDisplayMetrics();
        int w_screen = dm.widthPixels;
        int h_screen = dm.heightPixels;
        return new Point(w_screen, h_screen);
    }

    //拍照
    private class BtnListeners implements OnClickListener{
        @Override
        public void onClick(View v) {
            switch(v.getId()){
                case R.id.btn_shutter:
                    CameraInterface.getInstance().doTakePicture();
                    break;
                default:break;
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
       //更改视图在树中的z顺序,因此它位于其他同级视图之上。
        glSurfaceView.bringToFront();
    }

    @Override
    protected void onPause() {
        super.onPause();
        glSurfaceView.onPause();
    }

}

CameraGLSurfaceView.java

public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener {
    private static final String TAG = "TAG";
    Context mContext;
    //以OpenGL ES纹理的形式从图像流中捕获帧,我把叫做纹理层
    SurfaceTexture mSurface;
    //使用的纹理id
    int mTextureID = -1;
    DirectDrawer mDirectDrawer;
    public CameraGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        setEGLContextClientVersion(2);
        setRenderer(this);
        //根据纹理层的监听,有数据就绘制
        setRenderMode(RENDERMODE_WHEN_DIRTY);
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //得到view表面的纹理id
        mTextureID = createTextureID();
        //使用这个纹理id得到纹理层SurfaceTexture
        mSurface = new SurfaceTexture(mTextureID);
        //监听纹理层
        mSurface.setOnFrameAvailableListener(this);
        mDirectDrawer = new DirectDrawer(mTextureID);
        //打开相机,并未预览
        CameraInterface.getInstance().doOpenCamera();
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        //如果还未预览,就开始预览
        if(!CameraInterface.getInstance().isPreviewing()){
            CameraInterface.getInstance().doStartPreview(mSurface);
        }
    }
    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        //从图像流中将纹理图像更新为最近的帧
        mSurface.updateTexImage();
        mDirectDrawer.draw();
    }

    @Override
    public void onPause() {
        super.onPause();
        CameraInterface.getInstance().doStopCamera();
    }

    private int createTextureID() {
        int[] texture = new int[1];
        GLES20.glGenTextures(1, texture, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        return texture[0];
    }

    @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        //回调接口,用于通知新的流帧可用。
        Log.i(TAG, "onFrameAvailable...");
        //纹理层有新数据,就通知view绘制
        this.requestRender();
    }

}

CameraInterface.java

public class CameraInterface {
    private Camera mCamera;
    private Camera.Parameters mParams;
    private boolean isPreviewing = false;
    private static CameraInterface mCameraInterface;

    private CameraInterface(){
    }
    public static synchronized CameraInterface getInstance(){
        if(mCameraInterface == null){
            mCameraInterface = new CameraInterface();
        }
        return mCameraInterface;
    }
    //打开相机
    public void doOpenCamera(){
        if(mCamera == null){
            mCamera = Camera.open();
        }else{
            doStopCamera();
        }
    }
    /*使用TextureView预览Camera*/
    public void doStartPreview(SurfaceTexture surface){
        if(isPreviewing){
            mCamera.stopPreview();
            return;
        }
        if(mCamera != null){
            try {
                //将相机画面预览到纹理层上,纹理层有数据了,再通知view绘制,此时未开始预览
                mCamera.setPreviewTexture(surface);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //真正开启预览,Camera.startPrieView()
            initCamera();
        }
    }

    /**
     * 停止预览,释放Camera
     */
    public void doStopCamera(){
        if(null != mCamera)
        {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            isPreviewing = false;
            mCamera.release();
            mCamera = null;
        }
    }
    /**
     * 拍照
     */
    public void doTakePicture(){
        if(isPreviewing && (mCamera != null)){
            mCamera.takePicture(mShutterCallback, null, mJpegPictureCallback);
        }
    }

    public boolean isPreviewing(){
        return isPreviewing;
    }

    private void initCamera(){
        if(mCamera != null){
            mParams = mCamera.getParameters();
            mParams.setPictureFormat(PixelFormat.JPEG);//设置拍照后存储的图片格式
            mCamera.setDisplayOrientation(90);
            //设置摄像头为持续自动聚焦模式
            mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
            mCamera.setParameters(mParams);
            mCamera.startPreview();//开启预览
            //设置预览标志位
            isPreviewing = true;
        }
    }

    ShutterCallback mShutterCallback = new ShutterCallback()
        //如果这个参数设置为Null,将没有卡擦的声音
    {
        public void onShutter() {
        }
    };

    PictureCallback mJpegPictureCallback = new PictureCallback()
            //对jpeg图像数据的回调,最重要的一个回调
    {
        public void onPictureTaken(byte[] data, Camera camera) {
            Bitmap b = null;
            if(null != data){
                b = BitmapFactory.decodeByteArray(data, 0, data.length);//data是字节数据,将其解析成位图
                mCamera.stopPreview();
                isPreviewing = false;
            }
            //保存图片到sdcard
            if(null != b)
            {
                //图片这里要旋转下,相机拍出来的照片是倒着的
                Bitmap rotaBitmap = getRotateBitmap(b, 90.0f);
                saveBitmap(rotaBitmap);
            }
            //再次进入预览
            mCamera.startPreview();
            isPreviewing = true;
        }

    };

    //旋转图片
    private Bitmap getRotateBitmap(Bitmap b, float rotateDegree){
        Matrix matrix = new Matrix();
        matrix.postRotate((float)rotateDegree);
        return Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), matrix, false);
    }
    private static String initPath(){
        String storagePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/" + "PlayCamera";
        File f = new File(storagePath);
        if(!f.exists()){
            f.mkdir();
        }
        return storagePath;
    }
    private void saveBitmap(Bitmap b){
        String path = initPath();
        String jpegName = path + "/" + System.currentTimeMillis() +".jpg";
        try {
            BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(jpegName));
            b.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

最后我们来看 DirectDrawer.java中的代码:

顶点坐标数组为:

static float squareCoords[] = {
        -1.0f,  1.0f,
        -1.0f, -1.0f,
        1.0f, -1.0f,
        1.0f,  1.0f,
};

但是纹理坐标数组为:

static float textureVertices[] = {
        0.0f, 1.0f,  
        1.0f, 1.0f,
        1.0f, 0.0f,
        0.0f, 0.0f,
};

为什么不是:

static float textureVertices[] = {
        0.0f, 0.0f,
        0.0f, 1.0f,
        1.0f, 1.0f,
        1.0f, 0.0f,
};

我们看下面一张图解释:

因为摄像头Camera自己旋转了的原因,实际上对应顶点(-1,1)的位置在(0,1)处,而不在(0,0)处,所以。。。

我们的绘制顺序是012023.

具体代码如下:

public class DirectDrawer {
    private final String vertexShaderCode =
            "attribute vec4 vPosition;" +
                    "attribute vec2 inputTextureCoordinate;" +
                    "varying vec2 textureCoordinate;" +
                    "void main()" +
                    "{"+
                    "gl_Position = vPosition;"+
                    "textureCoordinate = inputTextureCoordinate;" +
                    "}";

    private final String fragmentShaderCode =
            "#extension GL_OES_EGL_image_external : require\n"+
                    "precision mediump float;" +
                    "varying vec2 textureCoordinate;\n" +
                    "uniform samplerExternalOES s_texture;\n" +
                    "void main() {" +
                    "  gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
                    "}";

    private FloatBuffer vertexBuffer, textureVerticesBuffer;
    private ShortBuffer drawListBuffer;
    private final int mProgram;
    private int mPositionHandle;
    private int mTextureCoordHandle;

    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 };

    private static final int COORDS_PER_VERTEX = 2;

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

    static float squareCoords[] = {
            -1.0f,  1.0f,
            -1.0f, -1.0f,
            1.0f, -1.0f,
            1.0f,  1.0f,
    };

    static float textureVertices[] = {
            0.0f, 1.0f,
            1.0f, 1.0f,
            1.0f, 0.0f,
            0.0f, 0.0f,
    };

    private int texture;

    public DirectDrawer(int texture)
    {
        this.texture = texture;
        //顶点坐标
        ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);
        //顶点绘制顺序
        ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
        //纹理坐标
        ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
        bb2.order(ByteOrder.nativeOrder());
        textureVerticesBuffer = bb2.asFloatBuffer();
        textureVerticesBuffer.put(textureVertices);
        textureVerticesBuffer.position(0);
        //编译着色器
        int vertexShader    = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShader  = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
        mProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mProgram, vertexShader);
        GLES20.glAttachShader(mProgram, fragmentShader);
        GLES20.glLinkProgram(mProgram);
    }

    public void draw()
    {
        GLES20.glUseProgram(mProgram);
        //使用纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
        //顶点位置
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
        //纹理坐标
        mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
        GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
        GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureVerticesBuffer);
        //绘制
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
        //结束
        GLES20.glDisableVertexAttribArray(mPositionHandle);
        GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
    }

    //编译着色器
    private  int loadShader(int type, String shaderCode){
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

}

最终效果如图,拍照照片在sd卡的PlayCamera文件夹中:

猜你喜欢

转载自blog.csdn.net/qq_38261174/article/details/83140543