【读书笔记】《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)); } }
如果不设置的话,竖屏的时候,大小是不对的。
- 【读书笔记】《Android多媒体开发高级编程》(二)
- Android多媒体开发高级编程读书笔记
- 【读书笔记】《Android多媒体开发高级编程》(一)
- Android多媒体开发高级编程
- Android-应用开发-多媒体编程(九)
- UNIX环境高级编程-读书笔记-网络编程(二)
- Android多媒体(二)
- Android开发--多媒体应用开发(二)--SoundPool的使用
- 文件和目录(二)--unix环境高级编程读书笔记
- linux信号(二)--unix环境高级编程读书笔记
- UNIX环境高级编程-读书笔记-文件操作(二)
- android 多媒体编程(二) MediaPlayer 播放视频
- android 学习随笔二十(多媒体编程 )
- Android多媒体开发高级编程——为智能手机和平板电脑开发图形、音乐、视频和富媒体应用
- Android开发艺术探索读书笔记(二)
- Android开发艺术探索读书笔记(二)
- Android多媒体高级编程(一)——Camera和简单的图像处理
- Android多媒体编程(待续)
- 小马哥----高仿苹果6 主板型号LT6082 芯片6582 真2g运存 15年新版山寨机型
- android四大组件(详细总结)
- 搭建服务器jenkins+sonar+maven的持续集成代码质量环境
- Ubuntu下安装opencv 2.4.11
- [python] Layer2攻击
- 【读书笔记】《Android多媒体开发高级编程》(二)
- ROS的geometry_msgs/PoseWithCovarianceStamped Message 消息格式
- 有一篇文章,共有3行文字,每行有80个字符,请求帮忙,谢谢
- 真机崩溃日志处理 定位行
- C++ 用libcurl库进行http通讯网络编程
- juqery cookie操作
- Objective-C Doxyfile
- myeclipse安装svn插件的多种方式
- linux shell 中文件编码查看及转换方法