目录

  1. Camera基础知识
  2. 视频采集的流程
  3. 遇到的问题和常见的坑(重点)
  4. 收获

一、 Camera基础知识

Camera 有几个重要的基础概念。

  1. facing相机的方向,一般后置摄像头和前置摄像头。
  2. Orientation:相机采集图片的角度,摄像头的传感器在手机中是横向的,在预览的时候根据Camera的预览方向进行顺时针旋转对应角度进行设置即可正常预览。如果不正确设置会导致预览时出现倒立、镜像等问题。把预览的图片保存为相册也要单独设置方向,注意这个方向和预览方向互不相干。
  3. 预览图片的大小 预览容器的大小和摄像头支持的图片预览的图片大小,如果设置了Camera不支持的预览大小,会导致黑屏。
  4. 可以设置帧回调然后,在每一帧中进行业务处理,比如,人脸识别等功能
  5. Camera预览的图片格式有NV21 YUV420sp等
  6. Camera需要一个容器把的Surface显示在屏幕上,一般SurfaceView,TextureView等。

二、视频采集的流程

  1. 通过SurfaceView拿到SurfaceHolder,然后设置addCallback回调,当Surface创建、销毁、改变时触发对应的回调,在其中可以进行Camera的初始化以及参数设置
  2. 通过new Camera(cameralId)生成一个对象。然后Camera.getParams获取到相关的参数,可以把重要的或者说比较关系的parmas打印出来,比如说支持多少个摄像头、支持的预览图片的大小、每个摄像头的方向等信息。可以根据需要设置对应的参数,比如图片的格式、图片的预览大小等。当然有一个必须要设置的就是Camera的预览展示方向,否则预览的到图片和正常的方向不一致。
  3. 可以设置Camera的setPreviewCallback获取每一帧的回调,根据需要设置处理,开始预览startPreview以及帧回调的处理
  4. 摄像头的切换

如果出发Camera的切换,需要把前一个Camera释放,重新生成和设置Camera切换预览
和Activity生命周期的关系,这个是由SurfaceView决定的,当页面可见时(onResume)创建或重新创建,页面不可见时(onPause)销毁释放

具体实现如下

  1. SurfaceView的设置
private SurfaceHolder mSurfaceHolder;private void initSurfaceView() {mSurfaceHolder = surfaceview.getHolder();mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.d(TAG, "surfaceCreated: ");handleSurfaceCreated();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.d(TAG, "surfaceDestroyed: ");handleSurfaceDestroyed();}});}private void handleSurfaceDestroyed() {releaseCamera();mSurfaceHolder = null;Log.i(TAG, "handleSurfaceDestroyed: ");}private void handleSurfaceCreated() {Log.i(TAG, "handleSurfaceCreated: start");if (mSurfaceHolder == null) {mSurfaceHolder = surfaceview.getHolder();}if (mCamera == null) {initCamera(curCameraId);}try {//问题2:页面重新打开后SurfaceView的内容黑屏//Camera is being used after Camera.release() was called//在surfaceDestroyed时调用了Camera的release 但是没有设置为null,//--》如何解耦合,把生命周期相关的方法和Camera的生命周期绑定而不时在回调中处理,方便业务实现//onResume--》surfaceCreated//onPause--》surfaceDestroyedmCamera.setPreviewDisplay(mSurfaceHolder);} catch (IOException e) {e.printStackTrace();Log.e(TAG, "handleSurfaceCreated: " + e.getMessage());}startPreview();Log.i(TAG, "handleSurfaceCreated: end");}private void startPreview() {
//        mCamera.setPreviewCallback(new Camera.PreviewCallback() {
//            @Override
//            public void onPreviewFrame(byte[] data, Camera camera) {
//                Log.i(TAG, "onPreviewFrame: setPreviewCallback");
//            }
//        });//问题:很多时候,不仅仅要预览,在预览视频的时候,希望能做一些检测,比如人脸检测等。这就需要获得预览帧视频,该如何做呐?mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {Log.i(TAG, "onPreviewFrame: setOneShotPreviewCallback");Camera.Size previewSize = mCamera.getParameters().getPreviewSize();YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null);ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);if(!yuvImage.compressToJpeg(new Rect(0,0,previewSize.width,previewSize.height),100,os)){Log.e(TAG, "onPreviewFrame: compressToJpeg error" );return;}byte[] bytes = os.toByteArray();Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);//这里的处理方式是简单的把预览的一帧图保存下。如果需要做人脸别或者其他操作,可以拿到这个bitmap进行分析处理//我们可以通过找出这张图片发现预览保存的图片的方向是不对的,还是Camera的原始方向,需要旋转一定角度才可以。if(curCameraId == Camera.CameraInfo.CAMERA_FACING_BACK){bitmap = BitmapUtils.rotate(bitmap,90);}else {bitmap = BitmapUtils.mirror(BitmapUtils.rotate(bitmap,270));}FileUtils.saveBitmapToFile(bitmap,"oneShot.jpg");}});//        mCamera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
//            @Override
//            public void onPreviewFrame(byte[] data, Camera camera) {
//                Log.i(TAG, "onPreviewFrame: setPreviewCallbackWithBuffer");
//            }
//        });mCamera.startPreview();}

2: Camera的初始化和Params设置

private void initCamera(int cameraId) {curCameraId = cameraId;mCamera = Camera.open(curCameraId);Log.d(TAG, "initCamera: Camera Open ");setCamerDisplayOrientation(this, curCameraId, mCamera);if (!hadPrinted) {printCameraInfo();hadPrinted = true;}Camera.Parameters parameters = mCamera.getParameters();Camera.Size closelyPreSize = CameraUtil.getCloselyPreSize(true, SystemUtils.getDisplayWidth(), SystemUtils.getDisplayHeight(), parameters.getSupportedPreviewSizes());Log.i(TAG, "initCamera: closelyPreSizeW="+closelyPreSize.width+" closelyPreSizeH="+closelyPreSize.height);parameters.setPreviewSize(closelyPreSize.width, closelyPreSize.height);mCamera.setParameters(parameters);}private void printCameraInfo() {//1. 调用getParameters获取ParametersCamera.Parameters parameters = mCamera.getParameters();//2. 获取Camera预览支持的图片格式(常见的是NV21和YUV420sp)int previewFormat = parameters.getPreviewFormat();Log.d(TAG, "initCamera: previewFormat=" + previewFormat); // NV21//3. 获取Camera预览支持的W和H的大小,// 手动设置Camera的W和H时,要检测camera是否支持,如果设置了Camera不支持的预览大小,会出现黑屏。// 那么这里有一个问,由于Camera不同厂商支持的预览大小不同,如果做到兼容呐?// 需要使用方采用一定策略进行选择(比如:选择和预设置的最接近的支持的WH)//通过输出信息,我们可以看到Camera是横向的即 W>HList<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();for (Camera.Size item : supportedPreviewSizes) {Log.d(TAG, "initCamera: supportedPreviewSizes w= " + item.width + " h=" + item.height);}//可以看到Camera的宽高是屏幕的宽高是不一致的,手机屏幕是竖屏的H>W,而Camera的宽高是横向的W>HCamera.Size previewSize = parameters.getPreviewSize();int[] physicalSS = SystemUtils.getPhysicalSS(this);Log.i(TAG, "initCamera: w=" + previewSize.width + " h=" + previewSize.height+ " screenW=" + SystemUtils.getDisplayWidth() + " screenH=" + SystemUtils.getDisplayHeight()+ " physicalW=" + physicalSS[0] + " physicalH=" + physicalSS[1]);//4. 获取Camera支持的帧率 一般是10~30List<Integer> supportedPreviewFrameRates = parameters.getSupportedPreviewFrameRates();for (Integer item : supportedPreviewFrameRates) {Log.i(TAG, "initCamera: supportedPreviewFrameRates frameRate=" + item);}//5. 获取Camera的个数信息,以及每一个Camera的orientation,这个很关键,如果根据Camera的orientation正确的设置Camera的DisplayOrientation可能会导致预览倒止或者出现镜像的情况int numberOfCameras = Camera.getNumberOfCameras();Camera.CameraInfo cameraInfo = new Camera.CameraInfo();for (int i = 0; i < numberOfCameras; i++) {Camera.getCameraInfo(i, cameraInfo);Log.i(TAG, "initCamera: facing=" + cameraInfo.facing+ " orientation=" + cameraInfo.orientation);}}/**** @param activity* @param cameraId* @param camera*/public static void setCamerDisplayOrientation(Activity activity, int cameraId, Camera camera) {Camera.CameraInfo cameraInfo = new Camera.CameraInfo();Camera.getCameraInfo(cameraId, cameraInfo);int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();Log.i(TAG, "setCamerDisplayOrientation: rotation=" + rotation + " cameraId=" + cameraId);int degress = 0;switch (rotation) {case Surface.ROTATION_0:degress = 0;break;case Surface.ROTATION_90:degress = 90;break;case Surface.ROTATION_180:degress = 180;break;case Surface.ROTATION_270:degress = 270;break;}int result = 0;if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (cameraInfo.orientation + degress) % 360;result = (360 - result) % 360;} else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {result = (cameraInfo.orientation - degress + 360) % 360;}Log.i(TAG, "setCamerDisplayOrientation: result=" + result + " cameraId=" + cameraId + " facing=" + cameraInfo.facing + " cameraInfo.orientation=" + cameraInfo.orientation);camera.setDisplayOrientation(result);}

  1. Camera的预览、帧回调处理、保存图片旋转和镜像处理
private void startPreview() {//问题六:很多时候,不仅仅要预览,在预览视频的时候,希望能做一些检测,比如人脸检测等。这就需要获得预览帧视频mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {@Overridepublic void onPreviewFrame(byte[] data, Camera camera) {Log.i(TAG, "onPreviewFrame: setOneShotPreviewCallback");Camera.Size previewSize = mCamera.getParameters().getPreviewSize();YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null);ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);if(!yuvImage.compressToJpeg(new Rect(0,0,previewSize.width,previewSize.height),100,os)){Log.e(TAG, "onPreviewFrame: compressToJpeg error" );return;}byte[] bytes = os.toByteArray();Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);//这里的处理方式是简单的把预览的一帧图保存下。如果需要做人脸设别或者其他操作,可以拿到这个bitmap进行分析处理//我们可以通过找出这张图片发现预览保存的图片的方向是不对的,还是Camera的原始方向,需要旋转一定角度才可以。if(curCameraId == Camera.CameraInfo.CAMERA_FACING_BACK){bitmap = rotate(bitmap,90);}else {bitmap = mirror(rotate(bitmap,270));}saveBitmapToFile(bitmap,"oneShot.jpg");}});mCamera.startPreview();}void saveBitmapToFile(Bitmap bitmap, String fileName) {File file = new File(MyApplication.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);if (file.exists()) {file.delete();}try {file.createNewFile();FileOutputStream fos = new FileOutputStream(file);bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);fos.flush();fos.close();} catch (IOException e) {e.printStackTrace();}}//水平镜像翻转public Bitmap mirror(Bitmap rawBitmap) {Matrix matrix = new Matrix();matrix.postScale(-1f, 1f);return Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.getWidth(), rawBitmap.getHeight(), matrix, true);}//旋转public Bitmap rotate(Bitmap rawBitmap, float degree) {Matrix matrix = new Matrix();matrix.postRotate(degree);return Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.getWidth(), rawBitmap.getHeight(), matrix, true);}

  1. Camera的切换
private void switchCamera() {if (mCamera != null) {releaseCamera();initCamera((curCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) ? Camera.CameraInfo.CAMERA_FACING_BACK : Camera.CameraInfo.CAMERA_FACING_FRONT);try {mCamera.setPreviewDisplay(mSurfaceHolder);} catch (IOException e) {e.printStackTrace();}startPreview();}}private void releaseCamera() {if (mCamera != null) {mCamera.setPreviewCallback(null);mCamera.stopPreview();mCamera.release();mCamera = null;}}

三、遇到的问题 (页面卡住、黑屏、倒立等)

问题一 :切换摄像头后画面卡住

解决:需要先关闭Camera释放资源,然后重新打开切换后的Camera,重新设置PreviewDisplay 然后开始预览

问题二:页面重新打开后(在预览页按Home键推到后台,然后再回到前台)SurfaceView的内容黑屏

解决:通过查看log看到有一个异常信息:“Camera is being used after Camera.release() was called” 原来是在surfaceDestroyed时调用了Camera的release 但是没有设置为null, 在surfaceCreated的时候是根据camera是否为空来判断是否需要重新初始化。

问题三:前摄像头预览出现倒立并且是镜像状态

public static void setCamerDisplayOrientation(Activity activity, int cameraId, Camera camera) {Camera.CameraInfo cameraInfo = new Camera.CameraInfo();Camera.getCameraInfo(cameraId, cameraInfo);int result = 0;if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (cameraInfo.orientation) % 360;} camera.setDisplayOrientation(result);

解决:

`public static void setCamerDisplayOrientation(Activity activity, int cameraId, Camera camera) {Camera.CameraInfo cameraInfo = new Camera.CameraInfo();Camera.getCameraInfo(cameraId, cameraInfo);int result = 0;if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {result = (cameraInfo.orientation) % 360;result = (360 - result) % 360;} camera.setDisplayOrientation(result);

图片来自[Android: Camera相机开发详解(上)]

问题四:很多时候,不仅仅要预览,在预览视频的时候,希望能做一些检测,比如人脸检测等。这就需要获得预览帧视频,该如何做呐?

Camera提供了setPreviewCallback、setOneShotPreviewCallback以及setPreviewCallbackWithBuffer三个方法供使用者进行帧回调处理。比如下面的处理时,通过setOneShotPreviewCallback获取一帧的bitmap,然后进行保存到文件

mCamera.setOneShotPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Log.i(TAG, "onPreviewFrame: setOneShotPreviewCallback");
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
YuvImage yuvImage = new YuvImage(data, ImageFormat.NV21, previewSize.width, previewSize.height, null);
ByteArrayOutputStream os = new ByteArrayOutputStream(data.length);
if(!yuvImage.compressToJpeg(new Rect(0,0,previewSize.width,previewSize.height),100,os)){
Log.e(TAG, "onPreviewFrame: compressToJpeg error" );
return;
}
byte[] bytes = os.toByteArray();
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

//这里的处理方式是简单的把预览的一帧图保存下。如果需要做人脸设别或者其他操作,可以拿到这个bitmap进行分析处理FileUtils.saveBitmapToFile(bitmap,"oneShot.jpg");}});

问题五:发现保存的图片和预览的图片的方向不一致

解决:预览通过Camera的setDisplay Orientation根据前后摄像头的需旋转的角度进行了处理,但是保存为图片是和预览时的设置时不相关的,需要单独处理

图片来自[Android: Camera相机开发详解(上)]

//我们可以通过找出这张图片发现预览保存的图片的方向是不对的,还是Camera的原始方向,需要旋转一定角度才可以。

if(curCameraId == Camera.CameraInfo.CAMERA_FACING_BACK){bitmap = BitmapUtils.rotate(bitmap,90);}else {bitmap = BitmapUtils.mirror(BitmapUtils.rotate(bitmap,270));}public class BitmapUtils {//水平镜像翻转public static Bitmap mirror(Bitmap rawBitmap) {Matrix matrix = new Matrix();matrix.postScale(-1f, 1f);return Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.getWidth(), rawBitmap.getHeight(), matrix, true);}//旋转public static Bitmap rotate(Bitmap rawBitmap, float degree) {Matrix matrix = new Matrix();matrix.postRotate(degree);return Bitmap.createBitmap(rawBitmap, 0, 0, rawBitmap.getWidth(), rawBitmap.getHeight(), matrix, true);}
}

参考资料

《音视频开发进阶指南》
[Android: Camera相机开发详解(上)]
Android camera2 实现相机预览及获取预览帧数据流
Activity启动后View何时开始绘制(onCreate中还是onResume之后?)

收获

  1. Camera的Facing、Orientation 、Size、PreviewCallback等基础知识的实践和了解
  2. camera预览的流程熟悉
  3. 黑屏、卡顿、倒立、镜像等问题的分析和处理

感谢你的阅读。

下一篇我们来一起学习实践MediaExtractor和MediaMuxer 解析和封装MP4文件。欢迎关注,一起成长。

音视频开发之旅(四)Camera视频采集​mp.weixin.qq.com

安卓 camera 调用流程_音视频开发之旅(四)Camera视频采集相关推荐

  1. 安卓 camera 调用流程_[Camera]Camera1 open、preview、take picture流程分析(3)

    本文章转载自sheldon_blogs的博客,具体网址如下:https://www.cnblogs.com/blogs-of-lxl/p/5152578.html 本文章仅供学习研究使用,如须转载请附 ...

  2. 安卓 camera 调用流程_安卓如何做出微信那样的界面仿微信“我”的界面1/5

    本系列目标 通过安卓编程仿写微信"我"的界面,让大家也能做出类似微信界面.效果图如下: 本文目标 做出页面顶部的相机部分(其他部分在后续文章中逐步分享).效果图如下: 实现方案 通 ...

  3. 音视频开发之旅(15) OpenGL ES粒子系统 - 喷泉

    目录 粒子和粒子系统 实践:喷泉效果 遇到的问题 资料 收获 通过该篇的实践实现如下效果 一.什么是粒子和粒子系统 如何定义粒子? 一个粒子有位置信息(x,y,z).运动方向.颜色.生命值(开始和结束 ...

  4. 音视频开发之旅(32)-音视频学习资料

    目录 为什么要学习音视频? 如何学习系统性音视频? 音视频相关的资料 学习实践的输出文章分类聚合 收获 最近有朋友问想学习音视频,应该怎么学,有什么资料吗? 这个问题也困扰我很久,几年前就想开始音视频 ...

  5. 即时通讯音视频开发(一):视频编解码之理论概述

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...

  6. 音视频开发之旅(16) OpenGL ES粒子效果-烟花爆炸

    目录 烟花爆竹场景和属性 实践以及遇到的问题 资料 收获 通过该篇的实践实现如下效果 一.烟花爆竹场景和属性 在上一篇 音视频开发之旅(15) OpenGL ES粒子系统 - 喷泉 的基础上 实现烟花 ...

  7. Android IOS WebRTC 音视频开发总结(四二)-- webrtc开发者大会

    Android IOS WebRTC 音视频开发总结(四二)-- webrtc开发者大会 本文主要介绍11月要在北京举办的webrtc开发者全球大会,文章来自博客园RTC.Blacker,支持原创,转 ...

  8. 即时通讯音视频开发(三):视频编解码之编码基础

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...

  9. 即时通讯音视频开发(二):视频编解码之数字视频介绍

    前言 即时通讯应用中的实时音视频技术,几乎是IM开发中的最后一道高墙.原因在于:实时音视频技术 = 音视频处理技术 + 网络传输技术 的横向技术应用集合体,而公共互联网不是为了实时通信设计的. 系列文 ...

最新文章

  1. mysql的性能瓶颈分析、性能指标、性能指标信息的搜集工具与方法、分析调优工具的使用...
  2. 从人的角度分析进销存管理的需求
  3. 牛客假日团队赛8:F.Telephone Lines(二分+spfa)
  4. Windows 平台编译 WebRTC
  5. 如何用SQL来检测文件是否存在
  6. 坚定不移地加速,并且不断解决新问题
  7. MySQL小问题:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents...
  8. Ubuntu在启动器中添加自定义程序快捷方式
  9. 电商购物APP UI 模板素材,充满时尚感的设计
  10. Permission denied (publickey). 解决方法
  11. win11怎么解除网络限制 windows11解除网络限制的设置方法
  12. 微软正式发布 Silverlight 5
  13. sublime双击选中$
  14. LaTex下载与安装教程(一切为了学术~)
  15. Unable to negotiate with port 51732: no matching host key type found. Their offer:
  16. 医学统计学笔记之分布
  17. 8个国外开放的硕博论文、期刊、数据库下载网站-转
  18. 史上最全Android版本号信息:)_我是亲民_新浪博客
  19. 51单片机 ADC0832酒精传感器
  20. 高匿代理,混淆代理,匿名代理,透明代理略解

热门文章

  1. 传统汽车被“智能”打败?且看汽车进化的黄金十年!
  2. 乘“峰”而上,聚生态之力共创软件产业新未来
  3. Google 开源 ChromeOS.dev,在 ChromeOS 上构建应用更容易!
  4. 盘一盘 Spring 核心技术之依赖注入 | 原力计划
  5. 新生代的他们,正在续写“黑客”传奇
  6. 识别率惊人的 GitHub 口罩检测 | 原力计划
  7. 学习分布式技术,技术人看这里
  8. 漫画:什么是字符串匹配算法?
  9. 没事爱在线上制造故障?这位程序媛有话说
  10. 亲测,终于知道为什么这本 Python 书销量超过13W+!