onPreviewFrame

Camera类中有很多内部类和方法,下面分别对这些类和方法做介绍:
Camera类中的内部类
CameraInfo

CameraInfo类用来描述相机信息,通过Camera类中getCameraInfo(int cameraId, CameraInfo cameraInfo)方法获得,主要包括以下两个成员变量:

facing
facing 代表相机的方向,它的值只能是CAMERA_FACING_BACK(后置摄像头) 或者CAMERA_FACING_FRONT(前置摄像头)。

CAMERA_FACING_BACK 和 CAMERA_FACING_FRONT 是CameraInfo类中的静态变量

orientation
这个就比较有意思了,也比较重要,源码中是这样描述的:

     * <p>The orientation of the camera image. The value is the angle that the
     * camera image needs to be rotated clockwise so it shows correctly on
     * the display in its natural orientation. It should be 0, 90, 180, or 270.</p>
     *
     * <p>For example, suppose a device has a naturally tall screen. The
     * back-facing camera sensor is mounted in landscape. You are looking at
     * the screen. If the top side of the camera sensor is aligned with the
     * right edge of the screen in natural orientation, the value should be
     * 90. If the top side of a front-facing camera sensor is aligned with
     * the right of the screen, the value should be 270.</p>

翻译一下,大概意思是:

orientation是相机采集图片的角度。这个值是相机所采集的图片需要顺时针旋转至自然方向的角度值。它必须是0,90,180或270中的一个。
举个栗子:
假如你自然地竖着拿着手机(就是自拍时候的样子...),后置摄像头的传感器在手机里是水平方向的,你现在看着手机,如果传感器的顶部在自然方向上手机屏幕的右边(此时,手机是竖屏,传感器是横屏),那么这个orientation的值就是90。 如果前置摄像头的传感器顶部在手机屏幕右边,那么这个值就是270.

setDisplayOrientation
源码描述如下:

 * Set the clockwise rotation of preview display in degrees. This affects
 * the preview frames and the picture displayed after snapshot. This method
 * is useful for portrait mode applications. Note that preview display of
 * front-facing cameras is flipped horizontally before the rotation, that
 * is, the image is reflected along the central vertical axis of the camera
 * sensor. So the users can see themselves as looking into a mirror.

翻译一下:
设置预览画面顺时针旋转的角度。这个方法会影响预览图像和拍照后显示的照片。这个方法对竖屏应用非常有用。
注意,前置摄像头在进行角度旋转之前,图像会进行一个水平的镜像翻转。
所以用户在看预览图像的时候就像照镜子,看到的是现实的水平方向的镜像。

注:setDisplayOrientation(int degrees)是Camea类中的一个方法,之所以穿插在这里来讲,是为了和上面提到的orientation做一个统一讲解,因为这两个都涉及到了方向问题

看了上面的介绍是不是有点懵逼。。。没关系,下面给小伙伴们详细介绍一下Android手机上几个方向的概念以及在使用Camera过程中会遇到的方向问题:

注:如果你是第一次使用Camera的话,首先要了解以下几点:

    相机图像数据都是来自于相机硬件的图像传感器(Image Sensor),这个Sensor被固定到手机之后是有一个默认的取景方向,且不会改变
    相机在预览的时候是有一个预览方向的,可以通过setDisplayOrientation()设置
    相机所采集的照片也是有一个方向的(就是上面刚刚提到的orientation),这个方向与预览时的方向互不相干

屏幕坐标: 在Android系统中,屏幕的左上角是坐标系统的原点(0,0)坐标。原点向右延伸是X轴正方向,原点向下延伸是Y轴正方向

自然方向:每个设备都有一个自然方向,手机和平板的自然方向不同。手机的自然方向是portrait(竖屏),平板的自然方向是landscape(横屏)

图像传感器(Image Sensor)方向:手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向

图四、传感器方向.png
在这里插入图片描述
相机的预览方向:将图像传感器捕获的图像,显示在屏幕上的方向。在默认情况下,与图像传感器方向一致。在相机API中可以通过setDisplayOrientation()设置相机预览方向。在默认情况下,这个值为0,与图像传感器方向一致

图五、相机预览方向.png
在这里插入图片描述

相机采集的图像方向
相机采集图像后需要进行顺时针旋转的角度,即上面介绍的orientation的值:

图六、采集的图像方向.png
在这里插入图片描述
这里先做个小结:
绝大部分安卓手机中图像传感器方向是横向的,且不能改变,所以orientation是90或是270,也就是说,当点击拍照后保存图片的时候,需要对图片做旋转处理,使其为"自然方向"。 (可能存在一些特殊的定制或是能外接摄像头的安卓机,他们的orientation会是0或者180)
通过setDisplayOrientation方法设置预览方向,使预览画面为"自然方向"。前置摄像头在进行角度旋转之前,图像会进行一个水平的镜像翻转,所以用户在看预览图像的时候就像照镜子一样。

相机在绑定 SurfaceHolder.Callback 之后,可以设置将预览显示在SurfaceView上 。这里SurfaceView有自己的生命周期,但是我们也可以获取到预览的数据。
布局中添加一个SurfaceView

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".CameraActivity">
    <SurfaceView
        android:id="@+id/surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />


</android.support.constraint.ConstraintLayout>

问题1: 问题描述:预览图像被拉伸变形,问题原因:由于预览图像大小跟SurfaceView 大小不一致引起,解决方法:获取系统支持的所有预览尺寸[getSupportedPictureSizes],然后再取一个比较接近的尺寸进行设置[setPreviewSize]
题主参考Google方法给出的Demo,自定义了TextureView,可以自动适配宽高,支持全屏展示。代码地址AutoFitTextureView

  private Point getBestCameraResolution(Camera.Parameters parameters, Point screenResolution){
		float tmp = 0f;
		float mindiff = 100f;
		float x_d_y = (float)screenResolution.x / (float)screenResolution.y;
		Size best = null;
		List<Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
		for(Size s : supportedPreviewSizes){
			tmp = Math.abs(((float)s.height/(float)s.width)-x_d_y);
			if(tmp<mindiff){
				mindiff = tmp;
				best = s;
			}
		}
		return new Point(best.width, best.height); 
	}

这就是通过绑定PreviewCallBack,来获取到视频帧。
问题2、应用竖屏显示时SurfaceView显示出来的画面方向是逆时针旋转了90度的,比如手机是竖着拿的,但是画面是横的,这是由于摄像头默认捕获的画面byte[]是根据横向来的,而你的应用是竖向的,解决办法是调用setDisplayOrientation来设置PreviewDisplay的方向,效果就是将捕获的画面旋转多少度显示。
设置 preview 的顺时针旋转角度。这将影响 preview frames和拍照之后的相片显示。该方法主要用于垂直模式的应用。注意在旋转之前, front-facing cameras 的 preview显示是水平 flip 的,这就是说, image 是沿着 camera sensor 的垂直中心轴来反射的。所以用户可以像照镜子一样看到他们自己。这不会影响传入函数 onPreviewFrame(byte[], Camera) 的、JPEG 相片的、或记录的 video 的 byte array 的顺序, 你可以自己做旋转处理 。在preview 期间是不允许调用该方法的。如果你想要是你的照片和显示出来的角度一致

public static void setCameraDisplayOrientation (Activity activity, int cameraId, android.hardware.Camera camera) {
	android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
	android.hardware.Camera.getCameraInfo (cameraId , info);
	int rotation = activity.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;
	}
	camera.setDisplayOrientation (result);
}

如下图在Activity继承两个回调的接口
在这里插入图片描述SurfaceHolder.Callback 自动重载如下函数

@Override
public void surfaceCreated(SurfaceHolder holder) {
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
  
}

PreviewCallBack自动回调 onPreviewFrame 这个函数
设置预览格式和预览回调:

Camera.Parameters parameters= camera.getParameters();
//强烈建议使用NV21格式和YV21格式,而默认情况下是NV21格式,也就是YUV420SP的。
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setPreviewSize(352, 288);
mCamera.setParameters(parameters);
mCamera.setPreviewCallback(previewCallback);

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
//在预览的时候不停的调用这个函数,有一个自己的子线程。
***data是 NV21格式的数据 ***转换流程是:byte[ ]—YuvImage----ByteArrayOutputStream—byte[ ]-----Bitmap。

//data数组中保存的就是每一帧的数据,动态覆盖掉上一帧的数据
//如需保存则将每一帧的信息取出来另存
}
         //处理data
        Camera.Size previewSize = camera.getParameters().getPreviewSize();//获取尺寸,格式转换的时候要用到
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        newOpts.inJustDecodeBounds = true;
        YuvImage yuvimage = new YuvImage(
                data,
                ImageFormat.NV21,
                previewSize.width,
                previewSize.height,
                null);
        baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);// 80--JPG图片的质量[0-100],100最高
        rawImage = baos.toByteArray();
        //将rawImage转换成bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);

onPreviewFrame 触发方式

onPreviewFrame 这个函数并不是在预览一开始就自动调用的,而是需要触发的。触发的方式有三种:

setPreviewCallback(PreviewCallback cb)
setOneShotPreviewCallback(PreviewCallback cb)
setPreviewCallbackWithBuffer(PreviewCallback cb)

我们可以在按钮的点击事件中加入触发,则程序会自动调用Activity下的 onPreviewFrame() 函数。

在这里插入图片描述下面说一下三种调用方式的区别:

void setPreviewCallback (PreviewCallback cb)
一旦使用此方法注册预览回调接口,onPreviewFrame()方法会一直被调用,直到camera preview销毁
void setOneShotPreviewCallback (PreviewCallback cb)
使用此方法注册预览回调接口时,会将下一帧数据回调给onPreviewFrame()方法,调用完成后这个回调接口将被销毁。也就是只会回调一次预览帧数据
void setPreviewCallbackWithBuffer (PreviewCallback cb)
它跟setPreviewCallback的工作方式一样,但是要求指定一个字节数组作为缓冲区,用于预览帧数据,这样能够更好的管理预览帧数据时使用的内存。它一般搭配addCallbackBuffer方法使用

setPreviewCallbackWithBuffer设置缓冲区

这种方式需要提前设置缓冲区

byte[] mPreBuffer = null;
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
// TODO Auto-generated method stub
if (data == null || mPreviewSize == null|| data.length != mPreviewSize.width * mPreviewSize.height * 3 / 2) {
return;
}
//这里size的算法是我看的网上的代码,我并没有自己计算
//mPreviewSize.width = 预览图像的宽
//mPreviewSize.height = 预览图像的高
int size = mPreviewSize.width * mPreviewSize.height * 3 / 2;
if (mPreBuffer == null) {
mPreBuffer = new byte[size];
}
mCamera.addCallbackBuffer(mPreBuffer);
}

同时我们在设置相机时,在startPreview() 之前需要 addCallbackBuffer(mPreBuffer),因为setPreviewCallbackWithBuffer (PreviewCallback cb)使用之前需要指定字节数组作为缓冲。
在这里插入图片描述注:我们需要在两处添加 addCallBackbuffer() ,一个是在相机初始化的 startPreview() 之前,一个是在onPreviewFrame() 中。

这里对于缓冲区的申请需要计算一下它的size。

对于我们获取的NV12格式的数据,缓冲区的Size可以使用预览图像的宽、高和每个像素的字节数的乘积计算。宽高可以使用getPreviewSize()方法获取。
  
其他两种也有申请缓冲区,但是缓冲区是系统自动分配的,不需要我们去申请,当缓冲小到不能支持预览帧数据时,预览回调接口将会return null,然后从缓冲区队列中移除此缓冲区。

注:在surfaceView销毁时或者是停止调用预览帧时,执行setPreviewCallback(null);中止回调然后再释放相机的资源,会减少异常的出现。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值