您的位置:首页 > 移动开发 > Android开发

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

2015-11-12 12:05 351 查看

第二章 自定义的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));
}
}


如果不设置的话,竖屏的时候,大小是不对的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息