android 使用 surfaceView 获取 camera 预览界面图像数据

android中,通过相机获取预览界面的需求似乎很变态,好像也没有什么使用场景。但是,有一个场景需要获取预览界面的图像,就是扫码,比如微信,支付宝的扫一扫,就是需要获取预览界面的图像数据的。

实现逻辑比较简单,不过肯定比打开系统相机要麻烦一点的。

下面简单说一下实现步骤:

  1. 实例化一个SurfaceView
  2. surfaceCreated()回调中去实例化Camera对象,去自动对焦。
  3. onAutoFocus()回调中去调用camera.takePicture(null,null,callback);
  4. 在第3步的callback里面去获取预览图像数据int data[]
  5. (可选)将获取的数据换成成文件。
  6. (可选)将该文件对象加载成bitmap对象。
  7. 在不使用的使用释放相机资源。

代码细节:

  1. 清单文件:
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
  1. java代码:
package com.python.cat.testgradle;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.apkfuns.logutils.LogUtils;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.ChecksumException;
import com.google.zxing.FormatException;
import com.google.zxing.NotFoundException;
import com.google.zxing.RGBLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.yanzhenjie.permission.Action;
import com.yanzhenjie.permission.AndPermission;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class UseCameraActivity extends Activity {


    private Activity get() {
        return this;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asome);
        setTitle(getClass().getSimpleName());
        final FrameLayout frameLayout = findViewById(R.id.prev_content_layout);
        Button btn = findViewById(R.id.start_camera_preview);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AndPermission.with(get())
                        .permission(Manifest.permission.CAMERA,
                                Manifest.permission.WRITE_EXTERNAL_STORAGE
                        )
                        .onDenied(new Action() {
                            @Override
                            public void onAction(List<String> permissions) {
                                LogUtils.e("error....." + permissions);
                            }
                        })
                        .onGranted(new Action() {
                            @Override
                            public void onAction(List<String> permissions) {
                                LogUtils.w("you can do..");
                                ScanView scanView = new ScanView(get());
                                frameLayout.removeAllViews();
                                frameLayout.addView(scanView);
                            }
                        }).start();
            }
        });
    }


    static class ScanView extends SurfaceView implements SurfaceHolder.Callback,
            Camera.AutoFocusCallback {
        private Camera mCamera;
        private final File fileImg;

        private ScanView self;

        public ScanView(Context context) {
            super(context);
            fileImg = new File(context.getCacheDir(), "prev_view.jpg");
            SurfaceHolder mHolder = getHolder();
            self = this;
            mHolder.addCallback(this);
            // deprecated setting, but required on Android versions prior to 3.0
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            LogUtils.w("auto focus..." + success);
            if (mCamera != null) {
                mCamera.takePicture(null, null, null, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {

                        mCamera.cancelAutoFocus();
                        mCamera.stopPreview(); // 拿到数据就停止!!!
                        LogUtils.w("========data========");
                        LogUtils.w("----------data-----------------");
//                        camera.startPreview();
                        File pictureFile = fileImg;
                        if (pictureFile == null) {
                            LogUtils.e("Error creating media file, check storage permissions: " +
                                    null);
                            return;
                        }

                        if (data == null) {
                            return;
                        }
                        try {
                            LogUtils.d(data);
                            FileOutputStream fos = new FileOutputStream(pictureFile);
                            fos.write(data);
                            fos.close();
                            LogUtils.e("save preview complete###!!!");
                            LogUtils.e("save preview complete###!!!" + pictureFile);
                            BitmapFactory.Options options = new BitmapFactory.Options();
                            options.inJustDecodeBounds = true;
                            BitmapFactory.decodeFile(pictureFile.getAbsolutePath(), options);
                            options.inJustDecodeBounds = false;
                            int outWidth = options.outWidth;
                            int outHeight = options.outHeight;
                            if (outWidth >= getWidth() * 2) {
                                options.inSampleSize = outWidth / getWidth();
                            }
                            if (outHeight >= getHeight() * 2) {
                                options.inSampleSize = outHeight / getHeight();
                            }
                            Bitmap bmp = BitmapFactory.decodeFile(pictureFile.getAbsolutePath(), options);
                            Result result = parseInfoFromBitmap(bmp);
                            if (result != null) {
                                Toast.makeText(getContext(), "INFO:" + result.getText(), Toast.LENGTH_SHORT).show();
                                LogUtils.w("解析成功:" + result);
                            } else {
                                LogUtils.e("再次尝试中....");
                                mCamera.startPreview();
                                mCamera.autoFocus(self);
                                // todo:这里也可以做最大重试次数的限制...
                            }
                        } catch (Exception e) {
                            LogUtils.e("Error accessing file: " + e.getMessage());
                        }
                    }
                });
            }
        }

        public Result parseInfoFromBitmap(Bitmap bitmap) {
            int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
            bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
            LogUtils.w("### pixels dest==" + Arrays.toString(pixels));

            RGBLuminanceSource source = new RGBLuminanceSource(bitmap.getWidth(),
                    bitmap.getHeight(), pixels);
            GlobalHistogramBinarizer binarizer = new GlobalHistogramBinarizer(source);
            BinaryBitmap image = new BinaryBitmap(binarizer);
            Result result = null;
            try {
                result = new QRCodeReader().decode(image);
                return result;
            } catch (NotFoundException e) {
                e.printStackTrace();
                Toast.makeText(getContext(), "非二维码图片,不能解析", Toast.LENGTH_SHORT).show();
            } catch (ChecksumException e) {
                e.printStackTrace();
            } catch (FormatException e) {
                e.printStackTrace();
            }

            return null;

        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            LogUtils.e("x surfaceCreated.. #####");
            try {
                mCamera = Camera.open();
                mCamera.setPreviewDisplay(holder);
                mCamera.setDisplayOrientation(90);
                Camera.Parameters parameters = mCamera.getParameters();
//                parameters.setPictureSize(1600, 1200);
//                parameters.setPreviewSize(640, 480);
                mCamera.setParameters(parameters);
                mCamera.startPreview();
                mCamera.autoFocus(this);
                LogUtils.e("surfaceCreated.. #####");

            } catch (IOException e) {
                LogUtils.e("Error setting camera preview: " + e.getMessage());
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            LogUtils.w("--change-");
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            LogUtils.w("destroy--");
            if (mCamera != null) {
                mCamera.cancelAutoFocus();
                mCamera.stopPreview();
                mCamera.release();
            }
        }
    }
}
  1. 布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".UseCameraActivity">


    <Button
        android:id="@+id/start_camera_preview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="@string/start_camera_preview" />

    <FrameLayout
        android:id="@+id/prev_content_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@id/start_camera_preview"></FrameLayout>
</RelativeLayout>
  1. gradle配置(可选):
implementation 'com.yanzhenjie:permission:2.0.0-rc4' // 运行时权限语法糖
implementation 'com.google.zxing:core:3.3.1' // zxing 解析二维码图片

嗯,以上就是获取相机预览图像数据的代码了。不过,这里用的Camera接口是过时的了,不建议使用。不过可以运行。我目前的compileSdkVersion=26

就酱咯。 完整app代码也许有的吧。

=======

update: 关于Camera2,我选择放弃….

官方的说法是:

The android.hardware.camera2 package provides an interface to individual camera devices connected to an Android device. It replaces the deprecated Camera class.


This package models a camera device as a pipeline, which takes in input requests for capturing a single frame, captures the single image per the request, and then outputs one capture result metadata packet, plus a set of output image buffers for the request. The requests are processed in-order, and multiple requests can be in flight at once. Since the camera device is a pipeline with multiple stages, having multiple requests in flight is required to maintain full framerate on most Android devices.

反正是看懂了一句话,就是用来替代Camera的。

但是,真的很麻烦。我没有找到中文的,可以直接运行的案例,找到了两个外国人写的博客,里面给了完整的代码。不过估计不能直接访问。我把拷贝到我的项目里面去了。可以直接运行

完整app代码
因为每一篇代码都很长,我就不贴出来了,说一下路径。

  1. com.python.cat.testgradle.MainActivity.java[由于这个原始代码没有加动态权限申请,我手动添加了一下…]

  2. com.python.cat.testgradle.AndroidCameraApi.java
    我给的是我代码的路径,以及原始链接。


不清楚是出于什么考虑,camera2里面比camera多了好几类,复杂度也提升了不少。有需要的可以研究一下。上手难度大于camera。(目前我还是一头雾水,对于camera2)。

中文的找到一个:这个我并没有去下载运行验证,不过看博客里面写的挺多的。

### 使用SurfaceView实现Camera预览Android应用中使用`SurfaceView`作为摄像头预览界面是一种常见做法。这涉及到几个关键步骤,包括初始化`SurfaceView`、配置并打开摄像设备以及设置预览显示。 #### 初始化SurfaceView及其监听器 创建一个继承自`Activity`的类,在布局文件中定义`SurfaceView`组件,并为其设定ID以便于程序内引用。接着,在活动(Activity)代码里找到这个视图对象,注册其持有者状态改变事件处理器: ```java public class CameraPreviewActivity extends Activity { private SurfaceView preview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera_preview); preview = findViewById(R.id.surface_view); // 获取SurfaceHolder实例并与回调关联 final SurfaceHolder holder = preview.getHolder(); holder.addCallback(new SurfaceHolder.Callback() { public void surfaceCreated(SurfaceHolder hldr) {} public void surfaceChanged(SurfaceHolder hldr, int fmt, int w, int h) {} public void surfaceDestroyed(SurfaceHolder hldr) {} }); } } ``` 上述代码片段展示了如何准备用于接收视频流数据的目标表面[^1]。 #### 打开并配置Camera资源 当`SurfaceView`准备好之后(即在其生命周期方法`surfaceCreated()`被调用时),可以尝试获取系统的相机服务接口,并请求访问特定编号的物理摄录装置。对于旧版API而言,这意味着要处理`Camera.open(int)`的结果;而对于较新的版本,则需采用更复杂的权限管理流程配合`CameraManager` API完成相同目的。 一旦成功获得了一个有效的`Camera`实例,就可以继续调整参数集以适应实际需求——比如分辨率大小、聚焦模式等属性都可通过修改相应的`Parameters`结构体成员变量来达成最佳效果。 最后一步就是指定先前建立好的`SurfaceHolder`作为目标输出端口之一,从而允许图像帧序列能够顺利传递给前端展示层面上去渲染呈现出来: ```java private Camera mCamera; // Inside the Callback... @Override public void surfaceCreated(final SurfaceHolder holder){ try{ if (mCamera != null && !isPreviewing) { mCamera.setPreviewDisplay(holder); // 设置预览图像显示位置 [^3] mCamera.startPreview(); // 启动预览过程 isPreviewing = true; // 更新标志位表示正在预览中 } else { Log.d(TAG,"Camera not available or already previewing."); } } catch (IOException e){ releaseCameraAndPreview(); throw new RuntimeException(e.getMessage()); } } // Method to safely close resources when done. protected void releaseCameraAndPreview(){ if(mCamera!=null){ mCamera.stopPreview(); mCamera.release(); mCamera=null; isPreviewing=false; } } ``` 这段逻辑确保了只有在满足必要条件的情况下才会启动预览会话,并且提供了安全释放硬件连接的方法以防内存泄漏或其他异常情况发生[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值