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

来源:互联网 发布:淘宝数据魔方架构 编辑:程序博客网 时间:2024/06/06 07:17

第二章 自定义的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
原创粉丝点击