前面几篇文章介绍了Camera1,Camera2,CameraView和CameraX的使用,对各个API的使用,应该问题不大,不过在真正开发过程当中,也会遇到各类不一样的问题,本篇文章继续介绍相机开发过程当中遇到的问题,主要是相机预览、拍照尺寸,方向,以及图像数据的处理。php 尺寸这里的尺寸,主要是预览尺寸、拍照尺寸和显示预览画面的View大小。html 预览尺寸如何获取预览尺寸?咱们能够从cameraview的源码中获取到,分为了Camera1和Camera2。java Camera1mCameraParameters = mCamera.getParameters(); // Supported preview sizes mPreviewSizes.clear(); for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) { Log.d("DEBUG", "###### SupportedPreviewSizes: width=" + size.width + ", height=" + size.height); mPreviewSizes.add(new Size(size.width, size.height)); } 复制代码 Camera2mPreviewSizes.clear(); for (android.util.Size size : map.getOutputSizes(mPreview.getOutputClass())) { int width = size.getWidth(); int height = size.getHeight(); if (width <= MAX_PREVIEW_WIDTH && height <= MAX_PREVIEW_HEIGHT) { mPreviewSizes.add(new Size(width, height)); } } 复制代码 不一样的厂商和系统所支持的预览尺寸是不同,下面是红米Note 5A手机上支持的全部预览尺寸:android SupportedPreviewSizes: width=1280, height=720 SupportedPreviewSizes: width=960, height=720 SupportedPreviewSizes: width=864, height=480 SupportedPreviewSizes: width=800, height=480 SupportedPreviewSizes: width=768, height=432 SupportedPreviewSizes: width=720, height=480 SupportedPreviewSizes: width=640, height=640 SupportedPreviewSizes: width=640, height=480 SupportedPreviewSizes: width=480, height=640 SupportedPreviewSizes: width=640, height=360 SupportedPreviewSizes: width=576, height=432 SupportedPreviewSizes: width=480, height=360 SupportedPreviewSizes: width=480, height=320 SupportedPreviewSizes: width=384, height=288 SupportedPreviewSizes: width=352, height=288 SupportedPreviewSizes: width=320, height=240 SupportedPreviewSizes: width=240, height=320 SupportedPreviewSizes: width=240, height=160 SupportedPreviewSizes: width=176, height=144 SupportedPreviewSizes: width=144, height=176 SupportedPreviewSizes: width=160, height=120 复制代码 这里尺寸的比例通常都是4:三、16:9,其余比例是在此基础上裁剪出来的git 选取预览尺寸在相同宽高比下,选择最接近View的宽高,避免过大的预览尺寸, 形成性能损耗, 引发预览卡顿。 Camera1private Size chooseOptimalSize(SortedSet<Size> sizes) { if (!mPreview.isReady()) { // Not yet laid out return sizes.first(); // Return the smallest size } int desiredWidth; int desiredHeight; final int surfaceWidth = mPreview.getWidth(); final int surfaceHeight = mPreview.getHeight(); if (isLandscape(mDisplayOrientation)) { desiredWidth = surfaceHeight; desiredHeight = surfaceWidth; } else { desiredWidth = surfaceWidth; desiredHeight = surfaceHeight; } Size result = null; for (Size size : sizes) { // Iterate from small to large if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) { return size; } result = size; } return result; } 复制代码 区分了横竖屏,而后获得尺寸中宽和高等于或者大于View的宽高的尺寸。app Camera2private Size chooseOptimalSize() { int surfaceLonger, surfaceShorter; final int surfaceWidth = mPreview.getWidth(); final int surfaceHeight = mPreview.getHeight(); if (surfaceWidth < surfaceHeight) { surfaceLonger = surfaceHeight; surfaceShorter = surfaceWidth; } else { surfaceLonger = surfaceWidth; surfaceShorter = surfaceHeight; } SortedSet<Size> candidates = mPreviewSizes.sizes(mAspectRatio); // Pick the smallest of those big enough for (Size size : candidates) { if (size.getWidth() >= surfaceLonger && size.getHeight() >= surfaceShorter) { return size; } } // If no size is big enough, pick the largest one. return candidates.last(); } 复制代码 先判断View宽高,区分其中较大值和较小值,而后再获得尺寸中宽和高大于或者等于View的较大值和较小值的尺寸。post 拍照尺寸代码也是从cameraview中截取出来的性能 Camera1mPictureSizes.clear(); for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) { Log.d("DEBUG", "###### SupportedPictureSizes: width=" + size.width + ", height=" + size.height); mPictureSizes.add(new Size(size.width, size.height)); } 复制代码 Camera2protected void collectPictureSizes(SizeMap sizes, StreamConfigurationMap map) { for (android.util.Size size : map.getOutputSizes(ImageFormat.JPEG)) { mPictureSizes.add(new Size(size.getWidth(), size.getHeight())); } } 复制代码 在红米Note 5A手机支持的拍照尺寸:ui SupportedPictureSizes: width=4160, height=3120 SupportedPictureSizes: width=4160, height=2340 SupportedPictureSizes: width=4096, height=3072 SupportedPictureSizes: width=4096, height=2304 SupportedPictureSizes: width=4000, height=3000 SupportedPictureSizes: width=3840, height=2160 SupportedPictureSizes: width=3264, height=2448 SupportedPictureSizes: width=3200, height=2400 SupportedPictureSizes: width=2976, height=2976 SupportedPictureSizes: width=2592, height=1944 SupportedPictureSizes: width=2592, height=1458 SupportedPictureSizes: width=2688, height=1512 SupportedPictureSizes: width=2304, height=1728 SupportedPictureSizes: width=2048, height=1536 SupportedPictureSizes: width=2336, height=1314 SupportedPictureSizes: width=1920, height=1080 SupportedPictureSizes: width=1600, height=1200 SupportedPictureSizes: width=1440, height=1080 SupportedPictureSizes: width=1280, height=960 SupportedPictureSizes: width=1280, height=768 SupportedPictureSizes: width=1280, height=720 SupportedPictureSizes: width=1200, height=1200 SupportedPictureSizes: width=1024, height=768 SupportedPictureSizes: width=800, height=600 SupportedPictureSizes: width=864, height=480 SupportedPictureSizes: width=800, height=480 SupportedPictureSizes: width=720, height=480 SupportedPictureSizes: width=640, height=480 SupportedPictureSizes: width=640, height=360 SupportedPictureSizes: width=480, height=640 SupportedPictureSizes: width=480, height=360 SupportedPictureSizes: width=480, height=320 SupportedPictureSizes: width=352, height=288 SupportedPictureSizes: width=320, height=240 SupportedPictureSizes: width=240, height=320 复制代码 这里尺寸的比例通常也是4:三、16:9 选取拍照尺寸Camaer1和Camera2都是同样的逻辑,选取固定宽高比例中的最大尺寸,这样拍摄的图片最清晰。 Size largest = mPictureSizes.sizes(mAspectRatio).last(); 复制代码 方向这里的设置方向有两种:图像预览方向和拍照方向。在这以前,须要先介绍几个概念:
屏幕坐标方向在Android系统中,以屏幕左上角为坐标系统的原点(0,0)坐标,该坐标系是固定不变的,不会由于设备方向的变化而改变。 屏幕天然方向每一个设备都有一个天然方向,手机和平板天然方向不同,如图所示,这里盗个图: 默认状况下,平板的天然方向是横屏,而手机的天然方向是竖屏方向。Android系统能够经过View的OrientationEventListener 监听设备方向,回调方法:
abstract public void onOrientationChanged(int orientation); 复制代码
摄像头传感器方向手机相机的图像数据都是来自于摄像头硬件的图像传感器,这个传感器在被固定到手机上后有一个默认的取景方向,方向通常是和手机横屏方向一致,如上图所示。相机预览方向将摄像头传感器捕获的图像,显示在屏幕上的方向,就是相机预览方向。默认状况下,和摄像头传感器方向一致,能够经过Camera API进行改变。 后置对横屏来讲,屏幕的天然方向和相机的摄像头传感器方向一致的。对竖屏来讲,看到的图像逆时针旋转了90度,所以预览方向须要顺时针旋转90度,才能与屏幕的天然方向保持一致。 前置前置的 Camera1 private int calcDisplayOrientation(int screenOrientationDegrees) { if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { return (360 - (mCameraInfo.orientation + screenOrientationDegrees) % 360) % 360; // compensate the mirror } else { // back-facing return (mCameraInfo.orientation - screenOrientationDegrees + 360) % 360; } } 复制代码 代码中区分了前置和后置摄像头。
Camera2 使用的TextureView的setTransform进行旋转,并有区分横竖屏。 /** * Configures the transform matrix for TextureView based on {@link #mDisplayOrientation} and * the surface size. */ void configureTransform() { Matrix matrix = new Matrix(); if (mDisplayOrientation % 180 == 90) { final int width = getWidth(); final int height = getHeight(); // Rotate the camera preview when the screen is landscape. matrix.setPolyToPoly( new float[]{ 0.f, 0.f, // top left width, 0.f, // top right 0.f, height, // bottom left width, height, // bottom right }, 0, mDisplayOrientation == 90 ? // Clockwise new float[]{ 0.f, height, // top left 0.f, 0.f, // top right width, height, // bottom left width, 0.f, // bottom right } : // mDisplayOrientation == 270 // Counter-clockwise new float[]{ width, 0.f, // top left width, height, // top right 0.f, 0.f, // bottom left 0.f, height, // bottom right }, 0, 4); } else if (mDisplayOrientation == 180) { matrix.postRotate(180, getWidth() / 2, getHeight() / 2); } mTextureView.setTransform(matrix); } 复制代码 拍照方向设置预览方向并不会改变拍出照片的方向。 Camera1使用 mCameraParameters.setRotation(calcCameraRotation(displayOrientation)); ...... /** * Calculate camera rotation * * This calculation is applied to the output JPEG either via Exif Orientation tag * or by actually transforming the bitmap. (Determined by vendor camera API implementation) * * Note: This is not the same calculation as the display orientation * * @param screenOrientationDegrees Screen orientation in degrees * @return Number of degrees to rotate image in order for it to view correctly. */ private int calcCameraRotation(int screenOrientationDegrees) { if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { return (mCameraInfo.orientation + screenOrientationDegrees) % 360; } else { // back-facing final int landscapeFlip = isLandscape(screenOrientationDegrees) ? 180 : 0; return (mCameraInfo.orientation + screenOrientationDegrees + landscapeFlip) % 360; } } 复制代码 相机采集到的图像,只须要旋转相机orientation度。 Camera2根据 // Calculate JPEG orientation. @SuppressWarnings("ConstantConditions") int sensorOrientation = mCameraCharacteristics.get( CameraCharacteristics.SENSOR_ORIENTATION); captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, (sensorOrientation + mDisplayOrientation * (mFacing == Constants.FACING_FRONT ? 1 : -1) + 360) % 360); 复制代码 图像数据Android Camera默认返回的数据格式是NV21。Camera1经过 YUVYUV是一种颜色编码方法,和它相似的还有RGB颜色编码方法,主要应用于电视系统和模拟视频领域。其中YUV表明三个份量,Y 表明明亮度,U 和 V 表示的是色度,色度又定义了颜色的两个方面:色调和饱和度。将Y与UV分离,没有UV信息同样能够显示完整的图像,可是只能显示灰度图。 YUV采样格式YUV 图像的主流采样方式有以下三种:
盗个图说明比较清晰,黑点表示采样该像素点的Y份量,空心圆圈表示采用该像素点的UV份量 YUV存储格式有两种存储格式,planar和packed。
YUV格式信息能够参考:YUV pixel formats
YUVY格式YUVY格式属于packed存储格式,相邻的两个Y共用其相邻的两个U、V Y0 UO Y1 V0 Y2 U2 Y3 V2 复制代码 Y0、Y1共用 U0、V0 UYVY格式UYVY格式也属于packed存储格式,与YUYV格式不一样的是UV的排列顺序不同而已 YUV422P格式YUV422P格式属于planar存储格式,先连续存储全部像素点的Y,紧接着存储全部像素点的U,随后是全部像素点的V YV十二、YU12格式YU12和YV12格式都属于YUV420P格式,YUV420P是planar存储格式。先存储全部Y,而后在存储U、V。 NV十二、NV21格式NV十二、NV21格式YUV420SP格式,YUV420SP也是planar存储格式。先存储全部Y,而后按照UV或者VU的交替顺序进行存储。 YV21: YYYYYYYY UU VV => YUV420P YV12: YYYYYYYY VV UU => YUV420P NV12: YYYYYYYY UV UV => YUV420SP NV21: YYYYYYYY VU VU => YUV420SP 复制代码 Android Camera 默认数据格式是 NV21,Camera1直接设置 if (format == ImageFormat.NV21) { throw new IllegalArgumentException( "NV21 format is not supported"); } 复制代码 在最新的 YCrCb format used for images, which uses the NV21 encoding format. This is the default format for android.hardware.Camera preview images, when not otherwise set with android.hardware.Camera.Parameters.setPreviewFormat(int). For the android.hardware.camera2 API, the YUV_420_888 format is recommended for YUV output instead. 复制代码 Camera2建议使用 参考 |
|