Android Camera-CameraView和CameraX使用

前面两篇介绍了Camera1和Camera2的使用,发现Camera API从1到2的变化非常大,Camera2的复杂度提升了不少,官方为了让我们更容易使用Camera,出了个一个官方的库cameraview。不过这个库已经Deprecated,官方建议使用Jetpack CameraX 替代。本篇文章就介绍下CameraView和CameraX的使用

CameraView

CameraView的目的就是帮助开发者能够快速集成Camera1和Camera2的特性,可以用下面这张表来说明:

API Level Camera API Preview View
9-13 Camera1 SurfaceView
14-20 Camera1 TextureView
21-23 Camera2 TextureView
24 Camera2 SurfaceView

开发流程

CameraView定义

xml中定义

<com.google.android.cameraview.CameraView
    android:id="@+id/camera"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:keepScreenOn="true"
    android:adjustViewBounds="true"
    app:autoFocus="true"
    app:aspectRatio="4:3"
    app:facing="back"
    app:flash="auto"/>

xml中可以配置:

  • autoFocus:是否自动对焦
  • aspectRatio:预览画面比例
  • facing:前后摄像头
  • flash:闪光灯模式

增加生命周期

@Override
protected void onResume() {
    super.onResume();
    mCameraView.start();
}

@Override
protected void onPause() {
    mCameraView.stop();
    super.onPause();
}

这样声明后,就可以完成预览的工作了

相机状态回调

在xml声明CameraView后,增加回调

if (mCameraView != null) {
    mCameraView.addCallback(mCallback);
}
...
private CameraView.Callback mCallback
            = new CameraView.Callback() {

    @Override
    public void onCameraOpened(CameraView cameraView) {
        Log.d(TAG, "onCameraOpened");
    }

    @Override
    public void onCameraClosed(CameraView cameraView) {
        Log.d(TAG, "onCameraClosed");
    }

    @Override
    public void onPictureTaken(CameraView cameraView, final byte[] data) {
        Log.d(TAG, "onPictureTaken " + data.length);
        Toast.makeText(cameraView.getContext(), R.string.picture_taken, Toast.LENGTH_SHORT)
                .show();
        getBackgroundHandler().post(new Runnable() {
            @Override
            public void run() {
                File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES),
                        "picture.jpg");
                Log.d(TAG, "onPictureTaken file path: " + file.getPath());
                OutputStream os = null;
                try {
                    os = new FileOutputStream(file);
                    os.write(data);
                    os.close();
                } catch (IOException e) {
                    Log.w(TAG, "Cannot write to " + file, e);
                } finally {
                    if (os != null) {
                        try {
                            os.close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    }
                }
            }
        });
    }

};

有三个回调方法,相机打开,相机关闭,和拍照。

拍照

mCameraView.takePicture();

就是这么简单,点击后拍照,然后回调中处理图像数据

CameraX

CameraX 是一个 Jetpack 支持库,目的是简化Camera的开发工作,它是基于Camera2 API的基础,向后兼容至 Android 5.0(API 级别 21)。
它有以下几个特性:

  • 易用性,只需要几行代码就可以实现预览和拍照
  • 保持设备的一致性,在不同相机设备上,对宽高比、屏幕方向、旋转、预览大小和高分辨率图片大小,做到都可以正常使用
  • 相机特性的扩展,增加人像、HDR、夜间模式和美颜等功能

开发流程

库引用

目前CameraX最新版本是1.0.0-alpha06,在app的build.gradle引用:

dependencies {
    // CameraX core library.
    def camerax_version = "1.0.0-alpha06"
    implementation "androidx.camera:camera-core:${camerax_version}"
    // If you want to use Camera2 extensions.
    implementation "androidx.camera:camera-camera2:${camerax_version}"

    def camerax_view_version = "1.0.0-alpha03"
    def camerax_ext_version = "1.0.0-alpha03"
    //other
    // If you to use the Camera View class
    implementation "androidx.camera:camera-view:$camerax_view_version"
    // If you to use Camera Extensions
    implementation "androidx.camera:camera-extensions:$camerax_ext_version"
}

因为CameraX是一个 Jetpack 支持库,相机的打开和释放都是使用了Jetpack的Lifecycle来进行处理。

预览

预览参数设置,使用PreviewConfig.Builder()实现:

PreviewConfig config = new PreviewConfig.Builder()
                .setLensFacing(CameraX.LensFacing.BACK)
                .setTargetRotation(mTextureView.getDisplay().getRotation())
                .setTargetResolution(new Size(640, 480))
                .build();

Preview preview = new Preview(config);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
    @Override
    public void onUpdated(@NonNull Preview.PreviewOutput output) {
        if (mTextureView.getParent() instanceof ViewGroup) {
            ViewGroup viewGroup = (ViewGroup) mTextureView.getParent();
            viewGroup.removeView(mTextureView);
            viewGroup.addView(mTextureView, 0);

            mTextureView.setSurfaceTexture(output.getSurfaceTexture());
            updateTransform();
        }
    }
});

//lifecycle
CameraX.bindToLifecycle(this, preview);

PreivewConfig.Builder可以设置的属性很多,这里只设置了摄像头、旋转方向、预览分辨率,还有很多其他方法,大家可以自行试验。
在preview回调监听中,把output的SurfaceTexture设置到mTextureView中,实现图像预览,最后增加Lifecycle的绑定。

拍照

ImageCaptureConfig captureConfig = new ImageCaptureConfig.Builder()
        .setTargetAspectRatio(AspectRatio.RATIO_16_9)
        .setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
        .setTargetRotation(getWindowManager().getDefaultDisplay().getRotation())
        .build();

ImageCapture imageCapture = new ImageCapture(captureConfig);
mTakePicture.setOnClickListener((view) -> {
    final File file = new File(getExternalMediaDirs()[0], System.currentTimeMillis() + ".jpg");
    Log.d("DEBUG", "##### file path: " + file.getPath());
    imageCapture.takePicture(file, ContextCompat.getMainExecutor(getApplicationContext()), new ImageCapture.OnImageSavedListener() {
        @Override
        public void onImageSaved(@NonNull File file) {
            Log.d("DEBUG", "##### onImageSaved: " + file.getPath());
        }

        @Override
        public void onError(@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
            Log.d("DEBUG", "##### onError: " + message);
        }
    });
});

CameraX.bindToLifecycle(this, preview, imageCapture);

拍照的参数通过ImageCaptureConfig.Builder设置,这里只设置了图片宽高比、拍摄模式和旋转方向,还有很多其他方法,大家可以自行试验。
真正调用拍照的方法:

  • takePicture(OnImageCapturedListener):此方法为拍摄的图片提供内存缓冲区。
  • takePicture(File, OnImageSavedListener):此方法将拍摄的图片保存到提供的文件位置。
  • takePicture(File, OnImageSavedListener, Metadata):此方法可用于指定要嵌入已保存文件的 Exif 中的元数据。

例子调用的是takePicture(File, OnImageSavedListener),直接存为文件。最后再增加Lifecycle的绑定。

图片分析

ImageAnalysisConfig analysisConfig = new ImageAnalysisConfig.Builder()
        .setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
        .build();

ImageAnalysis imageAnalysis = new ImageAnalysis(analysisConfig);
imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(getApplicationContext()),
        new LuminosityAnalyzer());

CameraX.bindToLifecycle(this, preview, imageCapture, imageAnalysis);

...
private class LuminosityAnalyzer implements ImageAnalysis.Analyzer {
    private long lastAnalyzedTimestamp = 0L;

    @Override
    public void analyze(ImageProxy image, int rotationDegrees) {
        final Image img = image.getImage();
        if (img != null) {
            Log.d("DEBUG", img.getWidth() + "," + img.getHeight());
        }
    }
}

图片分析,不是必要的步骤,但是ImageAnalysis,可以对每帧图像进行分析。
设置参数通过ImageAnalysisConfig.Builder(),这里只设置了ImageReaderMode,它有两种模式:

  • 阻止模式(ImageReaderMode.ACQUIRE_NEXT_IMAGE):就是Camera2中的acquireNextImage(),获取下一个最新的可用Image
  • 非阻止模式(ImageReaderMode.ACQUIRE_LATEST_IMAGE):Camera2中的acquireLatestImage(),获得图像队列中最新的图片,并且会清空队列,删除已有的旧的图像

最后还是增加Lifecycle的绑定。CameraX的使用也非常简单,把Camera2中复杂的API封装到统一的config中,只需要几行代码,就实现需要的功能。
文章中涉及到的代码

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容