zoukankan      html  css  js  c++  java
  • Android自定义相机超详细讲解

    Android自定义相机超详细讲解

    转载请标明出处: http://blog.csdn.net/vinicolor/article/details/49642861

    由于网上关于Android自定义相机的文章写得不是太详细,Google官方的文档又说得不太容易理解,所以今天我来详细讲解一下Android自定义相机。

    这篇文章主要写给一些刚刚接触Android的那些看官方API困难以及不太了解Android机制的同学们,所以熟练开发者可以绕道了。

    最近在使用Camera类的时候发现居然被弃用了,API 21中出现了camera2这个类来代替camera类,但是笔者的手机才andorid 4.4,国内要用上6.0至少明年去了,所以本次还是讲解Camera这个类。

    首先是加入权限,这个直接按照google的api向导或者看api文档会有详细说明的,所以这里不讲了。

    那么接下来,使用相机我们总需要一个能够看到图像的地方吧,这里Google叫我们使用SurfaceView这个类,那么SurfaceView这个类是什么呢,首先这个类是继承View的,可以在将图像绘制在屏幕上并显示给用户。其实能够显示的原因是SurfaceView中包含一个Surface对象,Surface是SurfaceView的可见部分,好了我们提到了Surface,又是一个让很多人头疼的概念,好吧让我们重头来讲解。

    首先我们在手机屏幕上看到的是这些画面都可以算是View(当然SurfaceView也算View),那么View是什么?View其实就是手机内存中的一小块区域,所谓显示,就是显卡等硬件将内存中的信息显示在屏幕上的过程,这下我想大家应该清楚一点了吧,我们继续,那我们说到的可见部分又是怎么回事呢,其实我们看到的屏幕可以说是2维的,也就是长和宽,但是在它的内部其实是3维的,还有一个维度就是层Layer,也就是层的概念,用过Visio或者AutoCAD的同学应该很好理解,在画图的时候,上层有时会将下层的遮挡,我们看到的图像就是这样一层一层堆叠起来的,这当中有些层不可见,有些层部分可见,有些层完全可见,我们看到的就是它们之中可见的部分,而Surface就是SurfaceView中的一个可见的部分,我们在摄像或者拍照用的就是它显示了。

    了解了View的作用,我想有人会问了:为什么不使用View,而用SurfaceView,首先在这里我想说用View这是可以的,但是用SurfaceView会更好,SurfaceView中Google为摄像等方法重写了很多方法,而且SurfaceView类是在一个新起的单独线程中重新绘制画面,而View是在UI线程上绘制画面,可以想象,如果你用View来预览图像(当然你必须要重写View中的大量方法来实现预览,这是我们不愿意看到的),那么在摄像的时候你就什么都别想做了,因为如果你打算更新UI的话,线程就可能会阻塞,你的APP就可能未响应了,Android系统就自动提示关闭了,这是用户极其不好的体验,而我们希望在摄像的也能更新UI,所以我们用SurfaceView类来预览图像。

    接下来我们看官方给我们的代码:

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
        private SurfaceHolder mHolder;
        private Camera mCamera;
    
        public CameraPreview(Context context, Camera camera) {
            super(context);
            mCamera = camera;
    
            // Install a SurfaceHolder.Callback so we get notified when the
            // underlying surface is created and destroyed.
            mHolder = getHolder();
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }
    
        public void surfaceCreated(SurfaceHolder holder) {
            // The Surface has been created, now tell the camera where to draw the preview.
            try {
                mCamera.setPreviewDisplay(holder);
                mCamera.startPreview();
            } catch (IOException e) {
                Log.d(TAG, "Error setting camera preview: " + e.getMessage());
            }
        }
    
        public void surfaceDestroyed(SurfaceHolder holder) {
            // empty. Take care of releasing the Camera preview in your activity.
        }
    
        public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
            // If your preview can change or rotate, take care of those events here.
            // Make sure to stop the preview before resizing or reformatting it.
    
            if (mHolder.getSurface() == null){
              // preview surface does not exist
              return;
            }
    
            // stop preview before making changes
            try {
                mCamera.stopPreview();
            } catch (Exception e){
              // ignore: tried to stop a non-existent preview
            }
    
            // set preview size and make any resize, rotate or
            // reformatting changes here
    
            // start preview with new settings
            try {
                mCamera.setPreviewDisplay(mHolder);
                mCamera.startPreview();
    
            } catch (Exception e){
                Log.d(TAG, "Error starting camera preview: " + e.getMessage());
            }
        }
    }

    首先别忘了最重要的事:就是获取Camera实例,不然肯定会报空指针异常的,我们用Camera.open()方法来获得实例对象,对于Andorid 2.3以前的手机其实是没有前置摄像头的,所以直接Camera.open()方法就行了,但是之后版本的手机拥有了前置摄像头,所以手机有了两个摄像头,那么Camera.open()就不能清楚表明到底开启那个摄像头了,所以Google提供了Camera.open(0)方法,这个方法其实就是指用手机的后置摄像头,而前置摄像头用Camera.open(1)方法,这样就可以获得Camera对象了。

    需要注意的是:由于Android机制的缘故,Android相机只能够被一个APP线程所绑定,也就是说如果你正在使用相机进行拍照摄像的时候,另一个程序便不能使用Camera类来启用相机,而且会报出Can not release的错误,那么如何判定你是否使用了相机呢,其实只要你使用了Camera.open()方法获得了相机的示例,系统就认为你使用了相机,所以当你使用了完了相机一定记得要释放相机的资源,不然别的应用程序用不了呀,我们可以使用 Camera.release()来释放相机资源,Google官方的意见是重写Activity的onPause()方法来Camera.release(),其实也可以重写Activity的onBackPressed()方法来释放相机,也就是用户按Back键的时候释放相机资源。

    看了上面的讲述我们知道了Surface其实就是对应的一个内存区域,而在内存区中的数据是有生存周期的,可以动态申请创建和销毁,当然也会更新,于是就有了对内存区的操作,在本例中就是surfaceCreated/Changed/Destroyed,也就是创建/修改/销毁,而3个操作放在一起就是Callback。 
    Surface代码中要求我们implements SurfaceHolder.Callback,那么为什么要使用SurfaceHolder.Callback呢,callback 意思是回调,那么它为什么要回调呢?这里我解释一下回调,回调在大部分情况就是程序在运行到需要一个函数(但是它本身没有)这里就是程序需要查看一下它内部记录的能处理这种情况的函数了,借用网上的比方:A人有能力做某件事但是现在不用他去做,所以他去登记一下自己的能力,到了需要用到他的时候,就会有人叫他去做,到程序里面就是A人能做surfaceCreated/Changed/Destroyed 3件事,他去登记了,并有个统称就叫SurfaceHolder.Callback,而在此程序中需要做这3件事,所以会Callback。而SurfaceHolder是什么呢,它在本例中就好比用开发商,而SurfaceView就像一个建房子计划负责人,它知道要首先要找到开发商,所以mHolder = getHolder()找开发商,而开发商就找到能够有建房能力的这个人A,就是回调他登记的信息mHolder.addCallback(this),而A的能力可以比喻成surfaceCreated建房子,surfaceDestroyed拆房子,surfaceChanged装修房子,故名思意,surfaceChanged只能在建房和拆房之间了。

    mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
    这是给mHolder设置缓存信息了,这个在Android 3.0之后就是自动设置的了,所以我们可以忽略这句代码了。

    好了到了surfaceCreated了,这个就是在屏幕创建时的准备了,首先我们需要将相机的预览界面绑定到我们的可见区域SurfaceView上,而getHolder()得到的Holder是对Surface有绝对控制权的,所以我们使用了holder,这个方法也就是把SurfaceView跟Camera连接起来,准备一个实时的Camera图像预览界面。 
    mCamera.setPreviewDisplay(holder); 
    接下来就可以设置开始预览了 
    mCamera.startPreview(); 
    当然别高兴了,这还没完呢,程序在执行完了surfaceCreated后是肯定会接着执行surfaceChanged的,这个方法的意思就是只要屏幕改变就会调用一个它,在首次启动程序时会调用surfaceCreated接着就会调用一次surfaceChanged,所以surfaceChanged方法在整个使用相机过程中必定至少调用一次,所以我们通常在surfaceChanged方法中进行判断可见区域Surface是否正常,也控制当界面改变时相机的改变,如横竖屏切换时相机的改变,下面这段代码大家就应该能够看懂了,其实意思就是当检测到surface改变的时候检测Surface是否正常,所有环节都是先停止相机的预览,再根据Surface的变化,改变相机的相关属性后再按如上述surfaceChanged方法中的mCamera.setPreviewDisplay(mHolder)mCamera.startPreview()启动预览即可。

    需要说一句:如果你要使用相机的自动聚焦以及闪光灯等一系列功能也可以在surfaceChanged方法中设置,这里贴出少量代码:

    Camera.Parameters params = mCamera.getParameters();
    List<String> focusModes = params.getSupportedFocusModes();     
    if(
    focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
    params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);}
    mCamera.setParameters(params);
    •  

    主要就是先判断手机有不有这个功能focusModes.contains(),然后再设置这个功能params.setFocusMode(),更多的功能设置可以在API中Camera.Parameters类中查看并使用。

    可以拍照了

    接下来我们该拍照了,那么就面对怎么将照片储存的问题了,其实Google提供了mCamera.takePicture()方法,这个方法有3个参数,如下:

    takePicture (Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)

    前两个参数Camera.ShutterCallback是在拍照瞬间回调的,可以用来添加拍照声音之类的,也可以在照相之后预览给用户看,询问是否需要保留,Camera.PictureCallback则是返回一个原生的图像数据,这两个参数其实在正常使用中并不实用(如果要预览我们也可以用Camera.PictureCallback中的数据来预览),所以我们设null表示不用,Camera.PictureCallback这个才是我们需要的,它是返回一个经过压缩的jpeg文件,正好适合我们的日常使用,所以我们选择这个方法,具体代码如下:

    public void onClick(View v) {
        switch (v.getId()) {
           case R.id.button_capturePicture:
    mCamera.takePicture(null, null, new Camera.PictureCallback() {
           @Override
    public void onPictureTaken(byte[] data, Camera camera) {  
    
                            }
                        });
    •  

    好了,其实这段代码就比较明显了,onPictureTaken是andorid自动回调的方法,onPictureTaken方法中的data参数就是获取的照片的数据了,只需要一个输出流将数据写入到外部存储就行了,这里对于数据的输出就不详细讲解了。当在调用takePicture的时候相机会自动停止预览了,也就是图像停止了,我们可以在重写回调函数onPictureTaken中再次启动预览就行了:

    mCamera.startPreview();
    •  

    该摄像了:

    摄影的代码稍微麻烦一点,我们需要用到MediaRecorder这个类,首先我们获得实例: 
    mMediaRecorder = new MediaRecorder(); 
    对于使用摄像机我们需要将相机解锁也就是: mCamera.unlock(); 
    那么我们到底要解锁相机的什么呢?原来相机在我们使用了Camera.open()后,就被绑定到了这个程序的进程上,那么其他的进程自然也就访问不了了,也就是我们需要用来录制图像和声音的MediaRecorder类就无法使用Camera了,所以我们才需要解锁,让Camera能够被MediaRecorder类使用,这个解锁是暂时的,在使用后(也就是摄像完成之后)我们可以通过reconnect ()方法让相机重新连接到我们的之前程序的进程上,当然了相机的解锁机制在Android 4.0之后我们就不需要手动解锁了,Andorid会自动帮我们完成的。 
    接下来我们就可以用MediaRecorder来连接相机了: 
    mMediaRecorder.setCamera(mCamera); 
    下面我们需要设置一些摄像时的资源了:

    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);           mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));    mMediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
    •  

    setAudioSource就是设置在摄像时获取声音的组件,CAMCORDER大致就是相机中的感声元件吧,当然我们也可以用MIC代替,也就是手机的麦克风了,setVideoSource就是设置摄像时获取图像的组件,这个无异议了,肯定是CAMERA相机了,那么mMediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))呢,这是设置相机的配置文件,会读入前面设置的setAudioSource和setVideoSource属性,分配给我们说所的音频和视频,并设定录制的质量,所以这个方法必须在setAudioSource和setVideoSource方法之后才可以调用,否则就会出错哦。好了接下来我们要指定我们录制的文件放在哪,setOutputFile()方法就是设置放在哪了,我们需要传递一个地址参数给它,也就是地址字符串了,注意了,地址需要加上拓展名如.mp4(其实在Android 2.2之前我们是需要setOutputFormat()也就是设置它的格式的,但是之后的版本Android就会自动帮我们完成了) 
    好了我们的准备工作都做完了(不少同学肯定会骂这个都多代码才把准备工作工作做完-_-),我们调用mMediaRecorder.prepare()方法让Android帮我们检测之前的设置对不对,所以需要捕获异常哦

    mMediaRecorder.prepare();
    • 1

    我们的摄像的数据Android会自动帮我们存储到我们给予的路径上。 
    准备完成了,我们就开始摄像吧!

    mMediaRecorder.start();
    • 1

    当然如果你要停止摄像记得先用stop()方法哦mMediaRecorder.stop(); 
    然后释放mMediaRecorder占用的系统资源:

    if (mMediaRecorder != null) {
                mMediaRecorder.reset();   
                mMediaRecorder.release(); 
                mMediaRecorder = null;
                mCamera.lock();  
                }

    mMediaRecorder.reset()就是重置你的mMediaRecorder配置信息,mMediaRecorder.release()也就是释放mMediaRecorder所占用的资源,当然我们之前解锁的相机,这个时候既然我们不用摄像了,我们的就给它锁住mCamera.lock(),但是记得如果你还想要拍照或者再次摄像的话先不要调用Camera.release()来释放相机的资源,否则你的预览会立即关闭了,如果想要再次摄像就重复上面的准备步骤,再start()就行了。

  • 相关阅读:
    check2
    LYF模板连接.txt
    mvc中的表现和数据分离怎么理解?
    node中websocket的使用
    vue随笔
    python安装Django常见错误
    node中的session的使用
    为什么很多IT公司不喜欢进过培训机构的人呢
    vue数据交互
    vuecli的服务代理
  • 原文地址:https://www.cnblogs.com/my334420/p/6921373.html
Copyright © 2011-2022 走看看