Android Things(三)-传感器与相机功能

AndroidThings传感器添加

Android 传感器框架支持多种传感器类型来感知周边环境数据。使用Androidthings提供的传感器驱动接口,可以通过Peripheral I/O接口来添加新的传感器设备。对于Android系统中已经集成的传感器的数据通过SensorManager的接口即可获取,另外我们的Androidtings应用也可以通过创建新的传感器驱动的方式来为系统添加一个新类型的传感器,比如温感器,血糖检测器等传感器。
创建一个新的传感器驱动并将其注册进系统后,系统将会轮询监听传感器来周期性的获取传感器数据, 为了响应系统对传感器数据的轮训请求, 我们需要实现UserSensorDriver 类并且重写read()方法,在read方法中返回传感器数据.下面我们来详细描述添加一个新的传感器的过程。

首先要添加系统权限

    <uses-permission android:name="com.google.android.things.permission.MANAGE_SENSOR_DRIVERS" />

然后要实现一个传感器驱动,用来读取传感器数据,每次调用read方法都要返回一个新的包含当前传感器数据的UserSensorReading对象。代码示例如下

UserSensorDriver mDriver = new UserSensorDriver() {
    // Sensor data values
    float x, y, z;
    @Override
    public UserSensorReading read() {
        try {
            // ...读取传感器硬件数据的步骤此处略过...
            // 将读取到的传感器数据返回
            return new UserSensorReading(new float[]{x, y, z});
        } (catch Exception e) {
            throw new IOException("Unable to read sensor");
        }
    }
};

创建完传感器驱动后,开始为系统添加传感器,传感器分为系统已支持的类型和系统还未支持的类型,两者的添加步骤有区别。
如果需要添加一个系统支持类型的传感器设备:

  1. 使用 UserSensor.Builder 描述传感器类型,传感器类型为Android 目前已经支持的 Sensor types.
  2. 设置 sensor 名称以及驱动供应商名称
  3. 设置传感器数据范围,分辨率,更新频率,电量需求(如果需要).这些参数将帮助framework在接到SensorManager的数据请求时来选择最佳的传感器。
  4. 使用setDriver()方法attach UserSensorDriver

如果要添加一个系统尚未支持的传感器类型:

  1. 使用setCustomType() 方法,设置sensor type 为大于等于 TYPE_DEVICE_PRIVATE_BASE的类型值
  2. 设置传感器类型名称,传感器名称在系统范围内需要是唯一的
  3. 设置传感器数据报告模式

代码示例如下:

// 添加已支持类型的传感器
UserSensor accelerometer = UserSensor.builder()
        .setName("GroveAccelerometer")
        .setVendor("Seeed")
        .setType(Sensor.TYPE_ACCELEROMETER)
        .setDriver(mDriver)
        .build();
// 添加未支持类型的传感器
UserSensor custom = UserSensor.builder()
        .setName("MySensor")
        .setVendor("MyCompany")
        .setCustomType(Sensor.TYPE_DEVICE_PRIVATE_BASE,
                "com.example.mysensor",
                Sensor.REPORTING_MODE_CONTINUOUS)
        .setDriver(mDriver)
        .build();

创建完传感器后就要将其注册到系统,使用UserDriverManager来 注册传感器,将新传感器连接到系统,应用就可以可以通过Android传感器系统服务来获取传感器的数据。实例代码如下

public class SensorDriverService extends Service {

    UserSensor mAccelerometer;

    @Override
    public void onCreate() {
        super.onCreate();
        ...
        UserDriverManager manager = UserDriverManager.getManager();
        // 创建一个新的传感器
        mAccelerometer = ...;
        // 将传感器注册到系统
        manager.registerSensor(mAccelerometer);
    }
}

至此,如何添加一个新的传感器的步骤我们就描述完了。下面我们介绍如何为Androidthings应用添加相机功能,这也是官方提供的一个示例。

Androidthings相机功能添加

首先添加系统权限:

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

然后将相机连接到开发板,将相机模块连接到开发板上的CS1-2相机接口,如下图所示(图片来自于谷歌开发者网站)

完成上面的准备工作后,我们正式开始进行图片获取相关的流程,获取图片的第一步是需要找到相机并且建立和设备之间的连接:

  1. 使用CameraManager 系统服务的getCameraIdList()找到所有可用的相机设备列表。
  2. 创建一个ImageReader 实例用来处理相机原始数据并且生成JPEG编码格式的图像数据。
  3. 建立和相机设备的连接,相机被成功打开后将会调用CameraDevice.StateCallback的onOpened() 回调方法
    示例代码如下:
public class CameraSample {
    // 图像参数 (device-specific)
    private static final int IMAGE_WIDTH  = ...;
    private static final int IMAGE_HEIGHT = ...;
    private static final int MAX_IMAGES   = ...;

    // 用于处理图像结果
    private ImageReader mImageReader;
    // 激活相机设备连接
    private CameraDevice mCameraDevice;
    // 激活图像捕捉
    private CameraCaptureSession mCaptureSession;

    // Initialize a new camera device connection
    public void initializeCamera(Context context,
                                 Handler backgroundHandler,
                                 ImageReader.OnImageAvailableListener imageAvailableListener) {
        // 获取可用的相机设备列表
        CameraManager manager = (CameraManager) context.getSystemService(CAMERA_SERVICE);
        String[] camIds = {};
        try {
            camIds = manager.getCameraIdList();
        } catch (CameraAccessException e) {
            Log.d(TAG, "Cam access exception getting IDs", e);
        }
        if (camIds.length < 1) {
            Log.d(TAG, "No cameras found");
            return;
        }
        String id = camIds[0];
        // 初始化图片处理器 imagereader
        mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT,
                ImageFormat.JPEG, MAX_IMAGES);
        mImageReader.setOnImageAvailableListener(imageAvailableListener, backgroundHandler);
        // 打开相机
        try {
            manager.openCamera(id, mStateCallback, backgroundHandler);
        } catch (CameraAccessException cae) {
            Log.d(TAG, "Camera access exception", cae);
        }
    }

    // 相机相关回调
    private final CameraDevice.StateCallback mStateCallback =
            new CameraDevice.StateCallback() {

        @Override
        public void onOpened(CameraDevice cameraDevice) {
            mCameraDevice = cameraDevice;
        }
        ...
    };

    // 关闭相机资源
    public void shutDown() {
        if (mCameraDevice != null) {
            mCameraDevice.close();
        }
    }
}

上面详细描述了相机初始化,建立设备连接相关的工作,接下来描述一下如何调用相机来捕获图片

和相机建立连接之后, 需要创建一个相机拍照会话,具体步骤如下:

  1. 使用createCaptureSession() 方法创建一个新的CameraCaptureSession 实例
  2. 传入和ImageReader建立连接的surface
  3. 创建一个 CameraCaptureSession.StateCallback回调来监控相机会话配置以及激活的状态 。示例代码如下
public class CameraSample {
    ...
    public void takePicture() {
        // 为了捕获静态图像,首先创建一个图像捕捉会话
        try {
            mCameraDevice.createCaptureSession(
                    Collections.singletonList(mImageReader.getSurface()),
                    mSessionCallback,
                    null);
        } catch (CameraAccessException cae) {
            Log.d(TAG, "access exception while preparing pic", cae);
        }
    }

    // 通过回调来监控会话状态
    private CameraCaptureSession.StateCallback mSessionCallback =
            new CameraCaptureSession.StateCallback() {

            @Override
            public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                // 会话已经准备好,则可以开始拍照.
                mCaptureSession = cameraCaptureSession;
                triggerImageCapture();
            }
            @Override
            public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                Log.w(TAG, "Failed to configure camera");
            }
    };
}

接下来进入到图片捕捉阶段 :

  1. 初始化一个新的 CaptureRequest ,如果静态图片使用TEMPLATE_STILL_CAPTURE 参数.
  2. 设置其他相机拍照参数, 比如 auto-focus ,auto-exposure等
  3. 调用CameraCaptureSession的capture方法来启动拍照请求,拍照完成后,关闭capture session。通过CameraCaptureSession.CaptureCallback回调来处理拍照结果。具体示例代码如下:
public class CameraSample {
    // Active camera device connection
    private CameraDevice mCameraDevice;
    // Active camera capture session
    private CameraCaptureSession mCaptureSession;
    ...
    private void triggerImageCapture() {
        try {
            final CaptureRequest.Builder captureBuilder =
                 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureBuilder.addTarget(mImageReader.getSurface());
            captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
            mCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null);
        } catch (CameraAccessException cae) {
            Log.d(TAG, "camera capture exception");
        }
    }
    // Callback handling capture progress events
    private final CameraCaptureSession.CaptureCallback mCaptureCallback =
        new CameraCaptureSession.CaptureCallback() {
            ...

            @Override
            public void onCaptureCompleted(CameraCaptureSession session,
                                           CaptureRequest request,
                                           TotalCaptureResult result) {
                if (session != null) {
                    session.close();
                    mCaptureSession = null;
                    Log.d(TAG, "CaptureSession closed");
                }
            }
        };
}

最后是对获取到的原始图像数据的处理:

  1. 通过ImageReader 的onImageAvailable 方法获取最新的图像数据 .
  2. 通过getBuffer()方法来获取JPEG编码格式的数据。代码实例如下:
    // Callback to receive captured camera image data
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            // Get the raw image bytes
            Image image = reader.acquireLatestImage();
            ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
            final byte[] imageBytes = new byte[imageBuf.remaining()];
            imageBuf.get(imageBytes);
            image.close();
            onPictureTaken(imageBytes);
        }
    };

    private void onPictureTaken(final byte[] imageBytes) {
        if (imageBytes != null) {
            // ...process the captured image...
        }
    }

这样就完成了通过相机设备来获取静态图像数据的整个流程。
参考资料:https://developer.android.google.cn/things/sdk/drivers/sensors.html#describing_the_sensor

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,864评论 6 13
  • Android传感器定义 Android 传感器相关术语微机电传感器(MEMS)MEMS 通常制作在规格很小的硅芯...
    Jannonx阅读 4,299评论 0 1
  • 【清】顾嗣协 《杂兴》 骏马能历险,犁田不如牛. 坚车能载重,渡河不如舟. 舍才(长)以就短,智高难为谋. 生才贵...
    刘伟书法_沈阳阅读 605评论 1 11