博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Camera了解一下
阅读量:7266 次
发布时间:2019-06-29

本文共 10904 字,大约阅读时间需要 36 分钟。

今天我们来了解下Android Camera的一些基本知识,包括下面一些内容

  1. 调用设备的相机app拍摄照片

  2. 调用设备的相机app拍摄视频

  3. 通过相机api拍摄照片和视频

1.调用设备的相机app拍摄照片

先看下效果图,拍摄可以返回缩略图和原图,这里看下返回原图的效果,点击CAPTURE按钮会调用设备的camera app,拍摄后会返回Bitmap:

1.1 获取相机特征权限

这个和平常的相机权限不一样,声明改特征是如果应用的主要特征是跟相机摄像头有关,那么应用商店Google Play会根据设备是否有相机来决定是否下载该应用:

...
复制代码

如果android:required设置为true,那么如果设备没有相机就不会下载该应用;设置为false,会允许下载,这个时候程序员就需要在代码中增加是否有相机的特征判断:

hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)复制代码
1.2 通过设备camera拍摄照片

做Android的小伙伴们都知道要委托其他app完成某些工作需要通过系统的Intent来做。所以,我们通过设备camera app也是需要Intent,包含三个步骤:

  1. 构造Intent
  2. 启动相机app的activity
  3. 处理返回的数据

看下前面两个步骤通过startActivityForResult的实现:

static final int REQUEST_IMAGE_CAPTURE = 1;private void dispatchTakePictureIntent() {    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);    }}复制代码

这里有一点需要注意的,startActivityForResult之前需要增加一个判断resolveActivity,它会返回第一个可以处理这个intent的activity,如果找不到可以处理这个intent的app,那么我们的app就会crash,所以注意增加这个判断。

第三点,处理返回的数据分成两部分,一个是返回缩略图,像素值小,另外一个就是返回原图片,分别来看下这两种情形。

1.3 返回缩略图

Camera app会在返回intent的extras中的“data”这个可以下带回缩小版的Bitmap,看下代码:

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {        Bundle extras = data.getExtras();        Bitmap imageBitmap = (Bitmap) extras.get("data");        mImageView.setImageBitmap(imageBitmap);    }}复制代码
1.4 保存原图片

可以给Camera app一个路径用于保存原图。如果不是敏感的图片,Android系统推荐通过

getExternalStoragePublicDirectory(DIRECTORY_PICTURES)复制代码

放在这个路径下的图片可以被所有的app访问到。当前前提是要声明写权限:

...
复制代码

如果希望只有本app可以访问到,可以通过下面路径访问到:

getExternalFilesDir(Environment.DIRECTORY_PICTURES)复制代码

所以可以抽离出一个函数返回保存图片的路径:

String mCurrentPhotoPath;private File createImageFile() throws IOException {    // Create an image file name    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());    String imageFileName = "JPEG_" + timeStamp + "_";    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);    File image = File.createTempFile(        imageFileName,  /* prefix */        ".jpg",         /* suffix */        storageDir      /* directory */    );    // Save a file: path for use with ACTION_VIEW intents    mCurrentPhotoPath = image.getAbsolutePath();    return image;}复制代码

接着返回构造一个FileProvider,这样Camera app才能访问到:

private void dispatchTakeFullSizePicIntent() {        Intent takePicIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);        if (takePicIntent.resolveActivity(getPackageManager()) != null) {            // Create the File where the photo should go            File photoFile = null;            try {                photoFile = FileUtils.createImageFile(this);            } catch (IOException e) {                // Error occurred while creating the File                e.printStackTrace();            }            if (photoFile != null) {                currentPhotoPath = photoFile.getAbsolutePath();                Uri photoURI = FileProvider.getUriForFile(                        this,                        getResources().getString(R.string.fileprovider_authority),                        photoFile                );                takePicIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);                startActivityForResult(takePicIntent, REQUEST_TAKE_PHOTO);            }        }    }复制代码

FileProvider需要在清单文件中注册,

复制代码

需要注意的是authorities需要和getUriForFile的第二个参数一样,在meta-data标签中通过在xml目录下的file_paths路径的xml文件指定图片的存储路径:

复制代码

这个路径其实就是上面的photoFile,也就是通过getExternalFilesDir(Environment.DIRECTORY_PICTURES)得到的。

如果要把上面保存的图片放到相册可以通过下面的方式:

private void galleryAddPic() {    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);    File f = new File(mCurrentPhotoPath);    Uri contentUri = Uri.fromFile(f);    mediaScanIntent.setData(contentUri);    this.sendBroadcast(mediaScanIntent);}复制代码

2. 调用设备的相机app拍摄视频

跟上面调用设备的相机app拍摄照片其实差不多,首先也是获取相机特征权限,这个和上面是完全一样的,不再重复说了。调用设备的相机app拍摄视频也是需要三个步骤,

  1. 构造Intent
  2. 启动相机app的activity
  3. 处理返回的数据

首先看下构造intent和启动:

static final int REQUEST_VIDEO_CAPTURE = 1;private void dispatchTakeVideoIntent() {    Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);    if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {        startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);    }}复制代码

和拍摄照片唯一不一样的就是action,视频的是MediaStore.ACTION_VIDEO_CAPTURE.

第三步就是处理返回的数据了:

@Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {            Uri videoUri = data.getData();            videoView.setVideoURI(videoUri);            if (videoView.getVisibility() == View.GONE) {                videoView.setVisibility(View.VISIBLE);            }            videoView.start();        }    }复制代码

通过intent的getData可以拿到视频的uri,通过VideoView进行播放。

3.控制相机

一般创建自定义相机界面有下面几个步骤:

  1. 确认设备有相机和申请权限
  2. 创建一个预览界面,可以用TextureView或者SurfaceView
  3. 创建一个布局,包含上面的预览界面和控制接口UI,比如按钮
  4. 建立UI和预览界面之间的联系,比如点击拍摄,控制相机拍摄然后预览界面显示
  5. 保存拍摄的文件,照片或者视频
  6. 释放相机
3.1 确认设备是否有相机

如前面第一部分说的,如果没有在清单文件中申明app需要相机,那么就需要在代码中增加判断设备是否有相机,可以通过PackageManager.hasSystemFeature()方法判断:

/** Check if this device has a camera */private boolean checkCameraHardware(Context context) {    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){        // this device has a camera        return true;    } else {        // no camera on this device        return false;    }}复制代码

在Android 2.3版本及以上,如果设备有多个相机,可以通过Camera.getNumberOfCameras()确认

3.2 申请权限

在Android 6.0以上需要运行时申请权限,首先需要确认是否已授权:

ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)复制代码

如果未授权,需要申请:

//ask for authorisationActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CODE);复制代码

最后一个参数是常量请求码,在权限反馈结果返回中需要用到,在onRequestPermissionsResult返回结果:

@Override    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {        switch (requestCode) {            case REQUEST_PERMISSION_CODE:                // If request is cancelled, the result arrays are empty.                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {                    // permission was granted                    mPreview.startPreview();                } else {                    // permission denied, boo! Disable the                    // functionality that depends on this permission.                    finish();                }                break;                default:                    break;        }    }复制代码
3.3 获取相机实例

确认过设备有相机和app有相机权限后需要获取相机实例,其中id可以是前置相机(Camera.CameraInfo.CAMERA_FACING_FRONT)或者后置相机(Camera.CameraInfo.CAMERA_FACING_BACK):

private Camera safeCameraOpen(final int id) {        Camera camera;        try {            releaseCameraAndPreview();            camera = Camera.open(id);        } catch (Exception e) {            e.printStackTrace();            camera = null;        }        latch.countDown();        return camera;    }    private void releaseCameraAndPreview() {        if (mCamera != null) {            mCamera.release();            mCamera = null;        }    }复制代码

open操作需要catch 异常,有可能其他app在使用相机或者相机设备不存在,另外open操作是耗时操作,建议放在线程中。

3.4 获取更多相机特征

成功获取相机实例后,可以通过Camera.getParameters()获取更多相机的参数信息,通过Camera.getCameraInfo()获取相机是前置还是后置,和图片的旋转方向。

3.5 创建预览界面

这里通过TextureView创建预览界面,TextureView需要实现接口TextureView.SurfaceTextureListener:

private void initTextureView() {        if (null == textureView) {            textureView = new TextureView(context);            textureView.setKeepScreenOn(true);            textureView.setSurfaceTextureListener(this);        }        LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,                ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER);        addView(textureView, layoutParams);    }复制代码

在系统创建好TextureView后会回调onSurfaceTextureAvailable,在这里就可以创建相机实例了:

@Override    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {        initImg();        coverImg.setImageResource(R.drawable.second);        final Camera[] camera = new Camera[1];        WorkerManager.getInstance().postTask(new Runnable() {            @Override            public void run() {                camera[0] = safeCameraOpen(Camera.CameraInfo.CAMERA_FACING_BACK);            }        });        try {            latch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        setCamera(surface, camera[0]);    }复制代码

其中coverImg是ImageView,在相机创建之前显示一张封面图用的。在异步线程中打开相机,然后把SurfaceTexture传递给相机就可以预览到界面了。

在接口onSurfaceTextureDestroyed记得释放相机:

@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {        stopPreviewAndFreeCamera();        return true;}    /**     * When this function returns, mCamera will be null.     */private void stopPreviewAndFreeCamera() {    if (mCamera != null) {        mCamera.stopPreview();        mCamera.release();        mCamera = null;}复制代码
3.6 创建一个布局

布局很简单,添加一个容器用来放置上面的预览界面CameraPreview:

复制代码
3.7 建立UI和预览界面的关系

接下来就是建立UI,就是上面的按钮,和预览界面CameraPreview之间的关系,这里就是点击按钮拍摄一张照片:

private void initView() {        mPreview = findViewById(R.id.cameraShower);        controlBtn = findViewById(R.id.control);        controlBtn.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (mPreview != null) {                    mPreview.takePicture();                }            }        });    }复制代码
3.8 拍摄照片

上面点击按钮调用CameraPreview的takePicture拍摄照片,这里需要实现Camera.PictureCallback接口,在接口会回调回来二进制数据:

/**     * 拍照片     */    public void takePicture() {        if (mCamera != null) {            mCamera.takePicture(null, null, new Camera.PictureCallback() {                @Override                public void onPictureTaken(byte[] data, Camera camera) {                    if (coverImg == null) {                        return;                    }                    if (data == null || data.length == 0) {                        return;                    }                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);                    if (bitmap != null) {                        coverImg.setVisibility(VISIBLE);                        coverImg.setImageBitmap(BitmapUtils.rotateBitmap(bitmap, (180 - rotationDigree)));                        stopPreviewAndFreeCamera();                    }                }            });        }    }复制代码

这里直接decode二进制数据生成Bitmap,然后给ImageView显示。

4.总结

上面总结了Camera的一些用法,包括通过调用系统的相机app拍摄照片和视频,自定义控制相机拍摄照片,其实还有一个自定义控制相机拍摄视频,官方文档推荐结合用MediaRecorder录制,但是现在比较常用的方式就是通过MediaCodec进行编码,然后通过MediaMuxer混合成视频格式,这个后面专门写篇博客分享。

转载地址:http://apfcm.baihongyu.com/

你可能感兴趣的文章
IO流-文件管理
查看>>
PHP如何判断远程图片文件是否存在
查看>>
【Android】11.0 第11章 活动和片段--本章示例主界面
查看>>
【答】如何获取一个【备份路径】的信息?
查看>>
课堂练习 选择团队类型
查看>>
Git 入门和常用命令详解
查看>>
linux 进阶命令___0001
查看>>
【 python 学习笔记 -- 数据结构与算法 】选择排序 Selection Sort
查看>>
数据结构与算法基础
查看>>
rsync
查看>>
第四十条:谨慎设计方法签名
查看>>
2018-2019-1 20165335 《信息安全系统设计基础》第7周学习总结
查看>>
PHP中数组遍历的几种方法
查看>>
解决Sublime Text 2中文显示乱码问题
查看>>
php页面输出时,js设置input框的选中值
查看>>
Linux 系统下 matplotlib 中文乱码解决办法
查看>>
Public Prize
查看>>
QuickSort
查看>>
asp.net的sessionState节点详解
查看>>
RedHat6.2搭建FTP服务器
查看>>