章节

Camera开发系列之一-显示摄像头实时画面

Camera开发系列之二-相机预览数据回调

Camera开发系列之三-相机数据硬编码为h264

Camera开发系列之四-使用MediaMuxer封装编码后的音视频到mp4容器

Camera开发系列之五-使用MediaExtractor制作一个简易播放器

Camera开发系列之六-使用mina框架实现视频推流

Camera开发系列之七-使用GLSurfaceviw绘制Camera预览画面 最近一直在做安卓摄像头方面的功能,不得不说这里面的坑简直多的一批,要注意的地方简直不要太多,可以说是从入门到入土系列。总之,这玩意儿差点要我的老命。

本系列文章使用的都是使用android.hardware.Camera包下面的api,并没有使用Camer2。主要是考虑到兼容性问题,另外一个很重要的原因是我还没看camera2的文档,啥都不会=_=。

本片文章主要分为以下几个小点讲解:

  1. 如何调用手机摄像头并获取实时数据
  2. 如何显示摄像头预览画面
  3. 在使用摄像头时注意的地方

实现相机预览

Mainfest权限申明

在开发设备相机之前,你需要在Mainfest中申明如下权限:

<uses-permission android:name="android.permission.CAMERA" />
复制代码

当然,上面是最基本的权限,如果你的应用需要保持照片或者视频到设备存储中,你必须在Manifest指定文件的写权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
复制代码

如果还需要录像功能,则要添加录音权限:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
复制代码

当然如果你需要拍摄的照片记录地理位置,你同样需要申请如下权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
复制代码

申明权限之后,就可以使用camera相关的api了

检测相机硬件

在用相机之前,先要干什么呢?当然是要先检测该设备是否有相机硬件,别说你的手机都有,你做的应用是不是你一个人使用你心里没有一点13数么?如果有相机硬件,才进一步去访问相机,如下是检测相机硬件是否存在是代码示例:

private boolean checkCameraHardware(Context context) {  if(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        return true;
    } else {
        return false;
    }
}
复制代码

Android 设备可以有多个相机硬件,现在一般手机都是前后两个camera,后置摄像头一般设备id为0,前置摄像头一般设备id为1。为了访问相机基本功能,可以使用Camera的open()方法来获得一个Camera的实例。

try {
    Camera camera = Camera.open(mCamerId);
}catch (Exception e){
    LogUtil.i("摄像头被占用");
    e.printStackTrace();
}
复制代码

这里记得要捕获一下异常,有可能其他应用程序占用了相机而crash。

获取&设置相机参数

一旦你可以成功访问相机设备,你可以使用Camera#getParameters()方法来获取相机参数信息,可以根据 返回值 Camera.Parameters 类来查看当前camea支持哪些参数设置等。也可以使用Camera#setParameters方法给相机设置相应的参数。

private void initCamera(int width,int height){
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewFormat(ImageFormat.NV21);
        //根据设置的宽高 和手机支持的分辨率对比计算出合适的宽高算法
        Camera.Size optionSize = CameraUtil
                .getInstance(mCamera, mCamerId)
                .getOptimalPreviewSize(width, height);
        parameters.setPreviewSize(optionSize.width, optionSize.height);
        //设置照片尺寸
        parameters.setPictureSize(optionSize.width, optionSize.height);
        //设置实时对焦 部分手机不支持会crash
       
    parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
        mCamera.setParameters(parameters);
        //开启预览
        mCamera.startPreview();
    }
复制代码
显示相机预览画面

相机准备就绪,但是,还差最后一步,为了有效的拍照或者录像,我们必须在屏幕上能看到相机的预览。一个相机预览类是由SurfaceView控件来实时显示来自camera的预览数据,如此我们才能看到每一帧数据和捕获图片或者视频。

mSurfaceView = findViewById(R.id.surfaceView);
mHolder = mSurfaceView.getHolder();
mHolder.addCallback(new SurfaceCallback());
复制代码

SurfaceCallback是个什么呢?SurfaceCallback继承SurfaceView.Callback接口类,并且需要实现里面的接口方法以便监听SurfaceView控件的创建以及销毁事件的回调,在回调方法中关联相机预览显示。

private class SurfaceCallback implements SurfaceHolder.Callback {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                mCamera = Camera.open(mCamerId);
            }catch (Exception e){
                LogUtil.i("摄像头被占用");
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            try {
                initCamera(mSurfaceView.getWidth(),mSurfaceView.getHeight());
                mCamera.setPreviewDisplay(holder);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {

        }
    }
复制代码

布局文件很简单:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />
</LinearLayout>
复制代码

现在就可以点击运行了,运行之后你会看到如下效果:

咦,怎么回事,画面怎么是旋转的,是代码有问题吗?nonono,兄弟不要慌,camera 预览默认的方向是横屏的,故在该例子中布局指定水平方向以及固定该应用为横屏显示。为了简便渲染camera预览,你可以在manifest配置文件中指定Activity的方向为横屏。

<activity android:name=".MainActivity" android:screenOrientation="landscape">
复制代码

也可以使用如下方法,在startPreview()调用之前调用该方法:

/**
     * 得到摄像头默认旋转角度后,旋转回来  注意是逆时针旋转
     *
     * @param activity
     */
    public void setCameraDisplayOrientation(Activity activity) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraId, info);
        int rotation = activity.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; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        LogUtil.i("摄像头被旋转的角度;" + result);
        mOrienta = result;//该值有其它用途
        mCamera.setDisplayOrientation(result);
    }
复制代码

为了美观,弄个全屏主题~

<!--全屏-->
    <style name="FullScreenTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowNoTitle">true</item>
    </style>
复制代码

最后,使用完camera之后记得释放camera资源。

if (null != camera) {
    camera.stopPreview();
    camera.release();
    camera = null;
}
复制代码

项目地址:github 欢迎start和fork

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐