【读书笔记】《Android多媒体开发高级编程》(二)

第二章 自定义的Camera


一、 自定义步骤

1. 声明权限

<uses-permission android:name="android.permission.CAMERA"/>
2. 预览Surface

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <SurfaceView android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>
Surface是一个抽象的概念,表示绘制图形或图像的位置。 Surface的控制器是 SurfaceHolder。

mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mSurfaceHolder.addCallback(this);

SurfaceHolder.Callback,在创建、修改、销毁Surface时,给Activity发送了通知。

  • surfaceCreated 的时候,获取摄像头,设置摄像头参数(设置摄像头参数需要比较注意,放在第二部分具体解释
  • surfaceChanged 的时候,预览页面产生变化,调整摄像头参数
  • surfaceDestroyed 的时候,预览页面被销毁,释放摄像头

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.d("dhy", "surfaceCreated-----------");
        try {
            cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
            mCamera = Camera.open(cameraId);
            setCameraParameters();

            //给摄像头绑定预览页面
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        }catch (Exception e){
            if (mCamera != null) {
                mCamera.release();
                mCamera = null;
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.d("dhy", "surfaceChanged-----------");
        //重新调整预览页面角度
        if (mCamera != null) {
            mCamera.stopPreview();
            setCameraParameters();
            mCamera.startPreview();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.d("dhy", "surfaceDestroyed-----------");
        if (mCamera != null) {
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

3. 点击页面拍照

首先设置预览页面可点击,然后添加点击事件

mSurfaceView.setFocusable(true);
mSurfaceView.setFocusableInTouchMode(true);
mSurfaceView.setClickable(true);
mSurfaceView.setOnClickListener(this);

点击的时候,调用takePicture拍照

@Override
    public void onClick(View v) {
        mCamera.takePicture(null, null, null, this);
    }
关于这四个参数的解释:

     * @param shutter   the callback for image capture moment, or null
     * @param raw       the callback for raw (uncompressed) image data, or null
     * @param postview  callback with postview image data, may be null
     * @param jpeg      the callback for JPEG image data, or null

此处,只保存jpg,因此只用到最后一个回调。

@Override
    public void onPictureTaken(byte[] data, Camera camera) {
        //使用MediaStore保存图片
        Uri imageFileUri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, new ContentValues());
        try {
            if (imageFileUri != null) {
                OutputStream imageFileOS = getContentResolver().openOutputStream(imageFileUri);

                if (imageFileOS != null) {
                    imageFileOS.write(data);
                    imageFileOS.flush();
                    imageFileOS.close();
                }
            }
        } catch (FileNotFoundException e) {
            Log.d("dhy","file not found");
            e.printStackTrace();
        } catch (IOException e) {
            Log.d("dhy", "io exception");
            e.printStackTrace();
        }

        //拍完一张后,默认自动停止预览。 调用startPreview重新开始预览
        mCamera.startPreview();
    }

二、 摄像头参数注意事项


(1)调整预览页面的方向

默认相机是水平的,所以竖直时,需要调整预览页面的方向,否则会导致90度偏差。

1. 调整偏差涉及两个方面的调整:

一个是预览页面调整角度, 使用 mCamera.setDisplayOrientation(90);

另一个是拍完之后保存的照片信息中记录旋转角度信息,使用 parameters.setRotation(90);

API中提供的调整算法:

public int setCameraDisplayOrientation() {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        int rotation = 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;
        }
//        Log.d("dhy", "info.orientation: " + info.orientation);
        mCamera.setDisplayOrientation(result);
        return result;
    }

//Camera假定的方向是水平的,所以要适应竖直的话,要设置一下
Camera.Parameters parameters = mCamera.getParameters();
parameters.setRotation(setCameraDisplayOrientation());
mCamera.setParameters(parameters);

2. 页面支持横竖屏

<activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="fullSensor"
            android:configChanges="orientation|screenSize"> <!-- 单独设置orientation在4.0以上不起作用,要增加一个screenSize,表示屏幕大小改变了 -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

屏幕其实有四个方向: 竖屏、左横屏、右横屏、反向竖屏。 有些手机默认不支持反向竖屏,所以如果需要四个方向都支持,需要设置 android:screenOrientation="fullSensor"。

为了不让转屏的时候,再次触发onCreate,设置 android:configChanges="orientation|screenSize", 使其转而触发 onConfigurationChanged 事件。

3. 由于转屏的时候,也会触发surfaceChanged,所以直接在surfaceChanged事件中,调整摄像头参数

@Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.d("dhy", "surfaceChanged-----------");
        //重新调整预览页面角度
        if (mCamera != null) {
            mCamera.stopPreview();
            setCameraParameters();
            mCamera.startPreview();
        }
    }

这里有一个问题,当两个横屏直接切换,即左横屏到右横屏,或者右横屏到左横屏,直接180度转屏的时候,并不触发 surfaceChanged 事件,因此使用另一个 OrientationEventListener 来辅助判断。

判断时,使用了偏差30度,这个是调试手机时发现的,当转到离90度还差30度左右的时候,会触发转屏事件,所以为了和转屏保持一致,就放宽了30度。但是这个只是Nexus 4的测试结果,不确定其他手机是不是这样子的。

mOrientationEventListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_UI)
        {
            @Override
            public void onOrientationChanged(int orientation) {
                int rotation = getWindowManager().getDefaultDisplay().getRotation();
                //笨方法:
                // 90度时,转到270度左右(左右偏差30度,即240到300),即认为是直接从左横屏到右横屏
                // 270度时,转到90度左右(左右偏差30度,即60到120),即认为直接从右横屏转到左横屏

                boolean left2right = rotation == Surface.ROTATION_90 && isNumberIn(orientation, 240, 300);
                boolean right2left = rotation == Surface.ROTATION_270 && isNumberIn(orientation, 60, 120);
                if (left2right || right2left) {
                    if (!orientationAdjusted) {//如果已经调整过了,则不需要再调整,否则调整太频繁
                        if (mCamera != null) {
                            mCamera.stopPreview();
                            setCameraParameters();
                            mCamera.startPreview();
                        }
                        orientationAdjusted = true;
                    }
                } else {
                    orientationAdjusted = false;
                }
            }

            private boolean isNumberIn(int number, int min, int max){
                return number < max && number > min;
            }
        };

onresume的时候enable,onpause的时候disable

@Override
    protected void onResume() {
        super.onResume();
        if(mOrientationEventListener != null && mOrientationEventListener.canDetectOrientation())
        {
            mOrientationEventListener.enable();
        }
    }

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

        if (mOrientationEventListener != null){
            mOrientationEventListener.disable();
        }
    }

(2)调整预览页面大小和图片大小

默认情况下,图片是全屏大小的,但是如果要自定义大小的话,要注意预览大小和图片大小要比例一致,否则会导致图片变形。

这里是通过一个笨方法,循环设备支持的previewSize,找一个尽量大的尺寸,同时,找一个与之同比例的pictureSize。具体的筛选策略,可以根据具体需求来调整。

private void setPreviewAndPictureSize(Camera.Parameters parameters) {
        List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes();
        List<Camera.Size> pictureSizes = parameters.getSupportedPictureSizes();

        if (previewSizes.size() > 1) {
            Iterator<Camera.Size> ceiSize = previewSizes.iterator();
            Display currentDisplay = getWindowManager().getDefaultDisplay();
            int maxPreviewWidth = currentDisplay.getWidth() * 2; //最大宽度;
            int maxPreviewHeight = currentDisplay.getHeight() * 2; //最大高度;

            int maxPictureWidth = maxPreviewWidth * 2;
            int maxPictureHeight = maxPreviewHeight * 2;

            while (ceiSize.hasNext()) {
                Camera.Size aSize = ceiSize.next();
                Log.d("dhy", "支持的预览页面大小: " + aSize.width + " - " + aSize.height);
                if (aSize.width <= maxPreviewWidth && aSize.height <= maxPreviewHeight) {
                    //最大值范围内,找一个最大的大小
                    int tmpPreviewWidth = Math.max(aSize.width, bestPreviewWidth);
                    int tmpPreviewHeight = Math.max(aSize.height, bestPreviewHeight);

                    //找一个与之匹配的picture size
                    if (pictureSizes.size() > 1) {
                        Iterator<Camera.Size> ceiPicSize = pictureSizes.iterator();
                        while (ceiPicSize.hasNext()) {
                            Camera.Size pSize = ceiPicSize.next();
//                            Log.d("dhy", "支持的图片大小: " + pSize.width + " - " + pSize.height);
                            if (pSize.width <= maxPictureWidth && pSize.height <= maxPictureHeight) {
                                //最大值范围内,找一个最大的大小
                                int tmpPictureWidth = Math.max(pSize.width, bestPictureWidth);
                                int tmpPictureHeight = Math.max(pSize.height, bestPictureHeight);

                                float previewRatio = tmpPreviewWidth * 1.0f / tmpPreviewHeight;
                                float pictureRatio = tmpPictureWidth * 1.0f / tmpPictureHeight;
//                                Log.d("dhy", "预览页面比例: " + previewRatio + "; 图片大小比例:" + pictureRatio);
                                if (previewRatio == pictureRatio) {
                                    //当前预览尺寸有对应比例的图片尺寸,则可用
                                    bestPreviewWidth = tmpPreviewWidth;
                                    bestPreviewHeight = tmpPreviewHeight;
                                    bestPictureWidth = tmpPictureWidth;
                                    bestPictureHeight = tmpPictureHeight;
                                }
                            }
                        }
                    }
                }
            }

            if (bestPreviewWidth > 0 && bestPreviewHeight > 0) {
                Log.d("dhy", "最终预览大小: bestWidth: " + bestPreviewWidth + "; bestHeight: " + bestPreviewHeight);
                parameters.setPreviewSize(bestPreviewWidth, bestPreviewHeight);

                if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
                    mSurfaceView.setLayoutParams(new RelativeLayout.LayoutParams(bestPreviewWidth, bestPreviewHeight));
                }else{
                    mSurfaceView.setLayoutParams(new RelativeLayout.LayoutParams(bestPreviewHeight, bestPreviewWidth));
                }
            }

            if (bestPictureWidth > 0 && bestPictureHeight > 0) {
                Log.d("dhy", "最终图片大小:bestWidth: " + bestPictureWidth + "; bestHeight: " + bestPictureHeight);
                parameters.setPictureSize(bestPictureWidth, bestPictureHeight);
            }
        }
    }

网上看到一篇文章,是用排序后,固定比例寻找合适大小的,虽然代码有点问题,但是思想可以借鉴下。注意这里是固定比例的,可能会有些适配问题。

http://blog.csdn.net/yanzi1225627/article/details/17652643

因为上面用到了双重循环,效率较低,所以每次重新设置parameters的时候,就不设置大小了,因为同一个设备不管你怎么转屏,算出来的大小还是一样的。

private void setCameraParameters() {
        if (mCamera == null){
            return;
        }
        //Camera假定的方向是水平的,所以要适应竖直的话,要设置一下
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setRotation(setCameraDisplayOrientation());

        //闪光灯模式
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);

        //颜色效果
        List<String> colorEffects = parameters.getSupportedColorEffects();
        Iterator<String> ceiEffects  = colorEffects.iterator();
        while(ceiEffects.hasNext()){
            String currentEffect = ceiEffects.next();
            if (currentEffect.equals(Camera.Parameters.EFFECT_NONE)){
                parameters.setColorEffect(Camera.Parameters.EFFECT_NONE);
                break;
            }
        }

        //预览大小
        if (bestPreviewWidth == 0){
            //如果未计算过,则计算
            setPreviewAndPictureSize(parameters);
        }

        mCamera.setParameters(parameters);
    }

另外,转屏的时候,预览页面大小也要重新设置下

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        Log.d("dhy", "onConfigurationChanged");
        super.onConfigurationChanged(newConfig);

        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            mSurfaceView.setLayoutParams(new RelativeLayout.LayoutParams(bestPreviewWidth, bestPreviewHeight));
        }else{
            mSurfaceView.setLayoutParams(new RelativeLayout.LayoutParams(bestPreviewHeight, bestPreviewWidth));
        }
    }

如果不设置的话,竖屏的时候,大小是不对的。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值