记录:Android-LOLLIPOP(21)之前的Camera拍照


摘要:相机的使用都是简单的拷贝一个demo可以拍照就ok了。对于这样的结果前期很乐见其成。越到后面越感到空旷。决定搜集一下资料。发扬自我精神,抄,抄,抄… .这篇文章主要用于记录API-21之前的Camera类,并且只对拍照进行记录。不对摄像做记录。主要记录Camera类中的参数设置,各个参数的意思。(文中有理解记录错误的地方,希望大家指正。我及时修改)。


Manifest 申明

  呵呵哒,还是按照 camera-api的风格一步一步做记录。如果我们是使用了Camera API在程序里面,那么应该在 AndroidManifest.xml中进行权限的申明

 <uses-permission androidname =“android.permission.CAMERA”/>
注意:如果直接调用现用的相机程序来使用相机。那么,我们自身的程序不需要请求相机权限

  在Camera.java中,我们看见类的注释上面有额外的申请两个权限,其权限如下:

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
  第一个权限是将相机功能添加到清单。这样就可以阻止程序被安装到没有相机的设备上。如果应用程序可以使用相机,但是并不是非它不可。没有相机也可以运行。那么,可以修改成如下的申请
<uses-feature android:name="android.hardware.camera" android:required="false" />

  第二权限为自动对焦模式时,需要的申请。关于相机还有各种权限比如存储啊,定位啊,等等。这些都需要用到就申请。

  在使用自定相机一般都是如下几个步骤

  • 检测和访问相机 - 首先检测设备相机是是否存在,如果存在才能进行请求访问。
  • 创建一个预览来显示相机图像 - 创建一个扩展SurfaceView并实现SurfaceHolder接口实现相机的预览。TextureView也可以实现
  • 设置相机一系列参数 - 如预览尺寸、图片大小、预览方向、图片的方向等等
  • 设置监听对拍摄照片进行保存 -
  • 释放相机资源 - Camera为一个公共的资源,要是一个程序不释放相机,严重情况可能导致其它程序不可以使用相机。或者自己都异常
注意:一定要在不使用的时候调用 mCamera.release()方法,释放相机。一般在activity的onStart( )生命周期方法里面打开相机。在onStop( )生命周期方法中释放相机资源

  在这里罗列一下api中相机的参数设置。看见这个表格之后。只想说句,madan。这都是神马与神马,有的听都没有听过。

参数 API等级 描述
Face Detection 14 识别画面中的人脸,用于对焦,测量,白平衡
Metering Areas 14 在画面中指定一个或者多个区域用来计算白平衡
Focus Areas 14 在画面中指定一个或者多个区域用来对焦
White Balance Lock 14 停止或者启动自动白平衡调节
Exposure Lock 14 停止或者启动自动曝光调节
Video Snapshot 14 支持视频录像时拍照
Time Lapse Video 11 支持延时录像
Multiple Cameras 9 一台设备支持多个camera,包括前置和后置camera
Focus Distance 9 支持焦距调节
Zoom 8 支持缩放
GPS Data 5 支持往照片中写地理位置数据
White Balance 5 设置白平衡模式,该参数会影响照片颜色效果
Focus Mode 5 设置对焦模式,比如:自动对焦,固定对焦,微距,远距
Scene Mode 5 设置场景模式,比如:夜景,沙滩,雪景,烛光等场景
JPEG Quality 5 设置生成的jepg图片质量等级
Flash Mode 5 闪光灯模式,on,off,auto
Color Effects 5 支持滤镜效果
Anti-Banding 5 反带效应(防止闪烁),参数有off,auto,50hz,60hz
Picture Format 1 图片格式,默认图片格式为jpeg
Picture Size 1 图片尺寸,指定一个大小的尺寸用于保存图片大小

配置相机参数

  看见上面的天书表示没有任何信心了,受到一万点真实暴击伤害。下面罗列一下几个常用参数。
闪光灯配置参数
Parameters.FLASH_MODE_OFF 关闭闪光灯
Parameters.FLASH_MODE_AUTO 自动模式,当光线较暗时自动打开闪光灯
Parameters.FLASH_MODE_ON 拍照时闪光灯
Parameters.FLASH_MODE_RED_EYE 闪光灯参数,防红眼模式,
Parameters.FLASH_MODE_TORCH 手电筒模式
对焦模式配置参数
Parameters.FOCUS_MODE_AUTO 自动对焦模式,摄影小白专用模式
Parameters.FOCUS_MODE_INFINITY 远景模式,拍风景大场面的模式
Parameters.FOCUS_MODE_MACRO 微焦模式,拍摄小花小草小蚂蚁专用模式
Parameters.FOCUS_MODE_FIXED 固定焦距模式,拍摄老司机模式
Parameters.FOCUS_MODE_EDOF 景深模式,文艺女青年最喜欢的模式
Parameters.FOCUS_MODE_CONTINUOUS_VIDEO 连续对焦
场景模式配置参数(这个太多了,列举常听的几个)
Parameters.SCENE_MODE_BARCODE 扫描条码场景,NextQRCode项目会判断并设置为这个场景
Parameters.SCENE_MODE_ACTION 动作场景,就是抓拍跑得飞快的运动员、汽车等场景用的
Parameters.SCENE_MODE_AUTO 自动选择场景
Parameters.SCENE_MODE_HDR 高动态对比度场景,通常用于拍摄晚霞等明暗分明的照片
Parameters.SCENE_MODE_NIGHT 夜间场景

打开相机

  对于用SurfaceView或者是用TextureVi这里就不做区分。原因…,容老衲考虑一下在回答你问什么,。。毛线阿,不知道我也不懂啊。我都是用的开源库啊! 这里是官方API相关的介绍:TextureView、SurfaceView、Camera API。在api的 open方法默认为调用后置相机,其api源码如下:

    public static Camera open() {
        int numberOfCameras = getNumberOfCameras();
        CameraInfo cameraInfo = new CameraInfo();
        for (int i = 0; i < numberOfCameras; i++) {
            getCameraInfo(i, cameraInfo);
            if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
                return new Camera(i);
            }
        }
        return null;
    }

  在这之前我使用的测试设备为Android的一体,这东西除有一个Android系统外,什么都需要外接(随便吐槽一下,真的不好。在使用中摄像头中,摄像头不能使用,只有返板卡厂让他们适配一下,升个级可以使用。外接打印机,Android的打印类+打印机插件完全无效,主要是能力太差吧,其使用的设备是hp 154nw打印机,其必须要会使用惠普的ePrint才能打印,所有在github上的demo都不可以在上使用)。一段时间等待之后。终于可以使用相机。出现代码的疏忽,当我把相机拔掉的时候,还是调到可拍照界面,只是一个白板,哀伤的笑笑。后面增加一个判断相机,没相机直接退出界面。通过如下代码可以判断出设备是否拥有相机

        PackageManager pm = getPackageManager();
        // FEATURE_CAMERA - 后置相机
        // FEATURE_CAMERA_FRONT - 前置相机
        if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)
                && !pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT)) {
            Log.i("camera", "non-support");
        } else {
            Log.i("camera", "support");
        }

  随着生活吃的越来越饱,追求也就更上一层了。现在手机前后一个都不能满足对事物的追求了。摄像头越来越多(注:没有测试过现在的双摄)。在Android2.3-api9之上可以学习open( )函数的方法,去判断设备对相机相关数据。在相机打开之后就来到一系列的参数设置。比如上面的图片尺寸设置、预览图片设置、对焦模式设置、闪光灯设置、预览FPS设置、跟随屏幕一起转动设置、缩放比例设置。这些参数也不是必选项,都有默认值。

相片尺寸和预览尺寸

  我在Fotoapparat这个开源库里面没有看见可以手动设置尺寸方法。也有可能是没有找到到。毕竟只是在按照demo使用了一下。在给里面提供了最大尺寸和最小尺寸两者模式。在其它的一些开源里面有的有相关的计算,去计算相近的尺寸作为最终尺寸。个人认为这种提供一个最大和最小基本上就满足了,毕竟各种图片裁剪那么厉害。下面贴上使用计算的相关代码

    /**
     * 升序的比较器
     * 程序首先对预览尺寸的list进行升序排序,因为在实际中,有的手机提供的是升序,有的是降序
     */
    public  class CameraSizeComparator implements Comparator<Size>{
        public int compare(Size lhs, Size rhs) {
            if(lhs.width == rhs.width){
                return 0;
            }
            else if(lhs.width > rhs.width){
                return 1;
            }
            else{
                return -1;
            }
        }

    }

    /**通过此方法,保证Size的长宽比率。
     * 一般而言这个比率为 1.333/1.7777即通常说的4:3和16:9比率。
     * @param s  可求出相机自身所支持的 图片比例
     * @param rate 手机预览框的比例
     * @return
     */
    public boolean equalRate(Size s, float rate){
        float r = (float)(s.width)/(float)(s.height);
        if(Math.abs(r - rate) <= 0.03)  //进过测试所得
        {
            return true;
        }
        else{
            return false;
        }
    }

    /** 在此方法里面增加if((s.width > th) && equalRate(s,th)),
     *  除了保证比率外,还保证用户要设置的尺寸宽度的最小值,
     * @param list
     * @param th  图片的宽高比 (在这里是宽高比,因为照相的图片默认是旋转90°,所以其比例是 屏幕的高宽的比)
     * @param minWidth 最小宽度,可随意改动
     * @return
     */
    public Size getPropPictureSize(List list, float th, int minWidth){
        Collections.sort(list, sizeComparator);

        int i = 0;
        for(Size s:list){
            if((s.width >= minWidth) && equalRate(s, th)){
                LogUtils.i(TAG, "PictureSize : w = " + s.width + "h = " + s.height);
                break;
            }
            i++;
        }
        if(i == list.size()){
            i = 0;//如果没找到,就选最小的size
        }
        return list.get(i);
    }


    /** 在此方法里面增加if((s.width > th) && equalRate(s,th)),
     *  除了保证比率外,还保证用户要设置的尺寸宽度的最小值,
     * @param list
     * @param th  图片的宽高比 (在这里是宽高比,因为照相的图片默认是旋转90°,所以其比例是 屏幕的高宽的比)
     * @param minWidth 最小宽度,可随意改动
     * @return 通过判断返回一个相机支持的图片大小,相机自身最小的图片尺寸或者传进来的图片比例
     */
    public Size getPropPreviewSize(List list,float th,int minWidth){
        Collections.sort(list, sizeComparator);   //对尺寸进行升序排序

        int i = 0;
        for(Size s:list){
            if((s.width >= minWidth) && equalRate(s, th)){
                LogUtils.i(TAG, "PreviewSize:w = " + s.width + "h = " + s.height);
                break;
            }
            i++;
        }
        if(i == list.size()){
            i = 0;//如果没找到,就选最小的size
        }
        return list.get(i);
    }

  目前相机像素大于手机的尺寸,如果当相机选着的宽高尺寸小于手机屏幕时,我们又该怎么弄了怎么弄了。在ImageView控件中scaleType有各种形态,这里也可以去动态的做改变。在 fotoapparat 有两种模式分别为

/**
 * The scale type of the preview relatively to the view.
 */
public enum ScaleType {

    /**
     * The preview will be scaled so as its one dimensions will be equal and the other one equal or
     * larger than the corresponding dimension of the view
     */
    CENTER_CROP,

    /**
     * The preview will be scaled so as its one dimensions will be equal and the other one equal or
     * smaller than the corresponding dimension of the view
     */
    CENTER_INSIDE

}

  这里想说一下自己的对于重复造轮子的事情。个人认为要自己熟悉轮子的怎么制作后,再考虑重不重的问题。要是一直使用的话,等于只会使用api而已。当然,个人见解而已。如有不认同,请轻喷。

对焦模式、闪光灯、缩放对焦

  这一块的内容自己一直都是设置在自动对焦,因为由于工作的性质的原因也没有具体在手机上测试过。推荐可以看下参考链接的Android相机实时自动对焦的完美实现这篇文章记录了在对焦时候遇到的一些问题。和其中的代码量很少,实现了前后摄像头的交换,闪光灯的控制。

预览时图像的旋转

  在使用setDisplayOrientation(int degrees)函数时候,上面的注释已经写好了相关的计算公式。这里还是列出 fotoapparat 中的代码

/**
 * Provides orientation of the screen.
 */
public class ScreenOrientationProvider {

    private final Display display;

    public ScreenOrientationProvider(@NonNull Context context) {
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        display = windowManager.getDefaultDisplay();
    }

    /**
     * @return rotation of the screen in degrees.
     */
    public int getScreenRotation() {
        int rotation = display.getRotation();

        switch (rotation) {
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
            case Surface.ROTATION_0:
            default:
                return 0;
        }
    }
}
    private int computeDisplayOrientation(int screenRotationDegrees,
                                          Camera.CameraInfo info) {
        return OrientationUtils.computeDisplayOrientation(
                screenRotationDegrees,
                info.orientation,
                info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT
        );
    }


    /**
     * @return closest right angle to given value. That is: 0, 90, 180, 270.
     */
    public static int toClosestRightAngle(int degrees) {
        boolean roundUp = degrees % 90 > 45;

        int roundAppModifier = roundUp ? 1 : 0;

        return (((degrees / 90) + roundAppModifier) * 90) % 360;
    }

    /**
     * @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
     * @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
     *                              orientation.
     * @param cameraIsMirrored      {@code true} if camera is mirrored (typically that is the case
     *                              for front cameras). {@code false} if it is not mirrored.
     * @return display orientation in which user will see the output camera in a correct rotation.
     */
    public static int computeDisplayOrientation(int screenRotationDegrees,
                                                int cameraRotationDegrees,
                                                boolean cameraIsMirrored) {
        int degrees = OrientationUtils.toClosestRightAngle(screenRotationDegrees);

        if (cameraIsMirrored) {
            degrees = (cameraRotationDegrees + degrees) % 360;
            degrees = (360 - degrees) % 360;
        } else {
            degrees = (cameraRotationDegrees - degrees + 360) % 360;
        }

        return degrees;
    }

  computeDisplayOrientation( )中的形参分别是:屏幕的旋转角度,相机的orientation和前后摄像头的判断。在计算完,就直接 camera.setDisplayOrientation( int )。

相片的保存和角度

  在进行拍照之后保存图片,会遇到我预览时正确的,然后保存的图片就角度不正确了。粘贴一下对于图片角度问题的计算代码

    /**
     * @param screenRotationDegrees rotation of the display (as provided by system) in degrees.
     * @param cameraRotationDegrees rotation of the camera sensor relatively to device natural
     *                              orientation.
     * @param cameraIsMirrored      {@code true} if camera is mirrored (typically that is the case
     *                              for front cameras). {@code false} if it is not mirrored.
     * @return clockwise rotation of the image relatively to current device orientation.
     */
    public static int computeImageOrientation(int screenRotationDegrees,
                                              int cameraRotationDegrees,
                                              boolean cameraIsMirrored) {
        int rotation;

        if (cameraIsMirrored) {
            rotation = -(screenRotationDegrees + cameraRotationDegrees);
        } else {
            rotation = screenRotationDegrees - cameraRotationDegrees;
        }

        return (rotation + 360 + 360) % 360;
    }

  其中的参数意思预览设置一样,在拍照的时候还有一个快门的快门的声音问题。想到这个就自己直接加载了一个.mp3。

  因为不清除具体怎么去控制这个声音。就给他加了一个 .mp3。知道大佬给下链接

public class MediaController {

    private static MediaController mAudioControl;
    private MediaPlayer soundPlayer;

    private MediaController() {
        if (this.soundPlayer != null) {
            this.soundPlayer.release();
            this.soundPlayer = null;
        }

        MediaPlayer localMediaPlay = new MediaPlayer();
        this.soundPlayer = localMediaPlay;
    }

    public static MediaController getInstance() {
        if (mAudioControl == null) {
            mAudioControl = new MediaController();
        }

        return mAudioControl;
    }

    //http://blog.csdn.net/a1274624994/article/details/51659789
    public void startSound(AssetFileDescriptor paramAssetFileDescriptor) {

        try {
            if (this.soundPlayer == null) {
                return;
            } else {
                Log.i("info","startSound -> soundPlayer = ?"+soundPlayer);
                if (this.soundPlayer.isPlaying()) {
                    this.soundPlayer.stop();
                }
//                else{
                this.soundPlayer.reset();
                MediaPlayer localMediaPlay = this.soundPlayer;
                FileDescriptor localFileDescriptor = paramAssetFileDescriptor.getFileDescriptor();
                long offset = paramAssetFileDescriptor.getStartOffset();
                long length = paramAssetFileDescriptor.getLength();
                localMediaPlay.setDataSource(localFileDescriptor,offset,length);
                this.soundPlayer.prepare();
                this.soundPlayer.start();
//                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stopSound() {
        if (this.soundPlayer == null) {
            return;
        } else {
            if (this.soundPlayer.isPlaying()) {
                this.soundPlayer.stop();
            }
            soundPlayer.release();
            Log.i("info","stopSound -> soundPlayer = ?"+soundPlayer);
        }
    }
}
        mMediaController = MediaController.getInstance();
        try {
            sucessSound = this.getAssets().openFd("capture_aduio.mp3");
        } catch (IOException e) {
            e.printStackTrace();
        }
///////////////////////////////
try {

            mMediaController.startSound(sucessSound);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

在预览界面按钮随屏幕转动二转动

  在拍照软件上,有的图标会随着屏幕的旋转而跟着一起旋转。给人一种比较有好的感觉。这里是利用了SensorEventListener

public class AngleUtil {
    public static int getSensorAngle(float x, float y) {
        if (Math.abs(x) > Math.abs(y)) {
            /**
             * 横屏倾斜角度比较大
             */
            if (x > 4) {
                /**
                 * 左边倾斜
                 */
                return 270;
            } else if (x < -4) {
                /**
                 * 右边倾斜
                 */
                return 90;
            } else {
                /**
                 * 倾斜角度不够大
                 */
                return 0;
            }
        } else {
            if (y > 7) {
                /**
                 * 左边倾斜
                 */
                return 0;
            } else if (y < -7) {
                /**
                 * 右边倾斜
                 */
                return 180;
            } else {
                /**
                 * 倾斜角度不够大
                 */
                return 0;
            }
        }
    }
}
    private SensorEventListener sensorEventListener = new SensorEventListener() {
        public void onSensorChanged(SensorEvent event) {
            if (Sensor.TYPE_ACCELEROMETER != event.sensor.getType()) {
                return;
            }
            float[] values = event.values;
            angle = AngleUtil.getSensorAngle(values[0], values[1]);
            rotationAnimation();
        }

        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    };
////////////////////////////////////////////////

    private int rotation = 0;
    private int angle = 0;
  //切换摄像头icon跟随手机角度进行旋转
    private void rotationAnimation() {
        if (mSwitchView == null) {
            return;
        }
        if (rotation != angle) {
            int start_rotaion = 0;
            int end_rotation = 0;
            switch (rotation) {
                case 0:
                    start_rotaion = 0;
                    switch (angle) {
                        case 90:
                            end_rotation = -90;
                            break;
                        case 270:
                            end_rotation = 90;
                            break;
                    }
                    break;
                case 90:
                    start_rotaion = -90;
                    switch (angle) {
                        case 0:
                            end_rotation = 0;
                            break;
                        case 180:
                            end_rotation = -180;
                            break;
                    }
                    break;
                case 180:
                    start_rotaion = 180;
                    switch (angle) {
                        case 90:
                            end_rotation = 270;
                            break;
                        case 270:
                            end_rotation = 90;
                            break;
                    }
                    break;
                case 270:
                    start_rotaion = 90;
                    switch (angle) {
                        case 0:
                            end_rotation = 0;
                            break;
                        case 180:
                            end_rotation = 180;
                            break;
                    }
                    break;
            }
            ObjectAnimator animC = ObjectAnimator.ofFloat(xxxx, "rotation", start_rotaion, end_rotation);
            ObjectAnimator animF = ObjectAnimator.ofFloat(xxxx, "rotation", start_rotaion, end_rotation);
            AnimatorSet set = new AnimatorSet();
            set.playTogether(animC, animF);
            set.setDuration(500);
            set.start();
            rotation = angle;
        }
    }

  借此就可以实现图标的旋转了。感谢各位开源的大大。以上代码来之Fotoapparat、SweetCamera、CameraView

参考链接

  1. Camera API
  2. Android Camera开发之基础知识篇
  3. Android相机实时自动对焦的完美实现
  4. Android 笔记 camera.takePicture(),使用flashMode,改变focusMode,continuous-picture中遇到的问题
  5. 玩转Android Camera开发(二):使用TextureView和SurfaceTexture预览Camera 基础拍照demo
  6. 安卓开发中枚举类型的使用

你可能感兴趣的:(Android)