继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android Camera了解一下

慕标5832272
关注TA
已关注
手记 1222
粉丝 229
获赞 1001

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

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

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

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

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

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

webp

take pic.jpg

1.1 获取相机特征权限

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

<manifest ... >    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...</manifest>

如果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访问到。当前前提是要声明写权限:

<manifest ...>    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...</manifest>

如果希望只有本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需要在清单文件中注册,

<provider
            android:authorities="@string/fileprovider_authority"
            android:name="android.support.v4.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

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

<?xml version="1.0" encoding="utf-8"?><paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Android/data/com.example.package.name/files/Pictures" /></paths>

这个路径其实就是上面的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:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical">

    <Button
        android:id="@+id/control"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/btn_bg"
        android:text="@string/capture_image" />

    <com.example.juexingzhe.jueapp.view.CameraPreview
        android:id="@+id/cameraShower"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal"
        android:scaleType="fitXY" /></LinearLayout>
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混合成视频格式,这个后面专门写篇博客分享。



作者:juexingzhe
链接:https://www.jianshu.com/p/1dd07c1cc843


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP