涂鸦 IP 摄像机移动应用 SDK 开发入门教程安卓版

更新时间:2023-06-01 06:18:35下载pdf

涂鸦智能安卓版摄像机(IP Camera,简称 IPC)SDK 是基于智能生活 App SDK 开发而成。通过移动应用控制物理网设备是常见的使用场景,但由于设备的品类丰富,增大了应用开发难度。因此 智能生活 App SDK 提供了常见的垂直品类 SDK,例如 IPC SDK,将网络摄像机设备特有的功能抽离,提供了与其通讯的接口封装,加速应用开发过程。

您可以通过本教程,在一小时内快速开发一款自己的 IoT App,并实现如下功能:

  • 通过 App 连接一个智能摄像头。
  • 预览摄像机实时采集的影像。
  • 播放摄像机存储卡中录制的视频。
  • 手机端录制摄像机采集的影像。

您可以点击下方按钮下载 Sample 查看本教程中的示例代码。本次教程按功能模块进行分类,您可以快速找到对应的代码参考学习。

前往 App 工作台 点击查看 GitHub Sample

效果展示

学习完本教程,结合一定的面板开发您可以创建一个类似以下安卓 App 的 Demo。

涂鸦 IP 摄像机移动应用 SDK 开发入门教程安卓版

准备工作

在您开始本教程前,请先确保您已经 :

  1. 在涂鸦 IoT 平台,注册账号并创建 App 应用,拿到 SDK 的 AppKey,AppSecret。参考 准备工作

    由于 IPC SDK 依赖于 智能生活 App SDK,您需要首先实现如创建账号、添加家庭的操作后才能借助 IPC SDK 实现摄像机相关功能。详情请参考 智能生活 App SDK 快速入门教程

  2. 准备一个涂鸦赋能的智能摄像机产品,本教程以摄像头为例。关于如何获取涂鸦赋能的产品,可访问 涂鸦智选
  3. 使用 Android Studio 将涂鸦安卓智能生活 App SDK 集成到您的项目中 。参考快速集成

设备配网

由涂鸦赋能的智能摄像机支持智能生活 App SDK 中的所有 配网方式,例如 Wi-Fi 热点配网和蓝牙配网等。此外,涂鸦为 IPC 设备提供了独有的扫描二维码配网功能,但如果 IP 摄像机设备无法扫码,可以优先尝试 Wi-Fi 快连方式。本篇教程文档主要讲解扫描二维码配网。

获取 Token

和 Wi-Fi 快连和 Wi-Fi 热点模式类似,开始配网之前,SDK 需要在手机已联网的状态下从涂鸦 获取配网 Token,然后才可以开始配网。

配网 Token 的有效期为 10 分钟,且配置成功后就会失效,再次配网需要重新获取。获取 Token 需要上传当前的 homeId,因此您需要确保用户处于登录状态,并至少创建了一个家庭。

ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
		new IThingActivatorGetToken() {

			@Override
			public void onSuccess(String token) {

			}

			@Override
			public void onFailure(String s, String s1) {

			}
		});

生成配网二维码

获取到配网 Token 后,您还需要当前 Wi-Fi 的名称(SSID)和密码,通过初始化配网参数的回调 onQRCodeSuccess,拿到二维码的 URL 字符串生成一个二维码图片。

相关依赖为 zxingimplementation ‘com.google.zxing:core:3.2.1’)。

// Get Network Configuration Token
            ThingHomeSdk.getActivatorInstance().getActivatorToken(homeId,
                    new IThingActivatorGetToken() {
                        @Override
                        public void onSuccess(String token) {
                            //Create and show qrCode
                            ThingCameraActivatorBuilder builder = new ThingCameraActivatorBuilder()
                                    .setToken(token)
                                    .setPassword(wifiPwd)
                                    .setTimeOut(100)
                                    .setContext(QrCodeConfigActivity.this)
                                    .setSsid(wifiSSId)
                                    .setListener(new IThingSmartCameraActivatorListener() {
                                        @Override
                                        public void onQRCodeSuccess(String qrcodeUrl) {
                                            final Bitmap bitmap;
                                            try {
                                                bitmap = QRCodeUtil.createQRCode(qrcodeUrl, 300);
                                                QrCodeConfigActivity.this.runOnUiThread(new Runnable() {
                                                    @Override
                                                    public void run() {
                                                        mIvQr.setImageBitmap(bitmap);
                                                        mLlInputWifi.setVisibility(View.GONE);
                                                        mIvQr.setVisibility(View.VISIBLE);
                                                    }
                                                });
                                            } catch (WriterException e) {
                                                e.printStackTrace();
                                            }
                                        }

                                        @Override
                                        public void onError(String errorCode, String errorMsg) {

                                        }

                                        @Override
                                        public void onActiveSuccess(DeviceBean devResp) {
                                            Toast.makeText(QrCodeConfigActivity.this,"config success!",Toast.LENGTH_LONG).show();
                                        }
                                    });

                        }

timeout 单位为秒,默认为 100,您可以设置为任意值。但不建议将此值设置得过小,否则将影响配网结果。

其中生成二维码图片示例代码如下:

public static Bitmap createQRCode(String url, int widthAndHeight)
            throws WriterException {
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
        hints.put(EncodeHintType.MARGIN,0);
        BitMatrix matrix = new MultiFormatWriter().encode(url,
                BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight, hints);

        int width = matrix.getWidth();
        int height = matrix.getHeight();
        int[] pixels = new int[width * height];
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                if (matrix.get(x, y)) {
                    pixels[y * width + x] = BLACK; //0xff000000
                }
            }
        }
        Bitmap bitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
        return bitmap;
    }

开始配网

开始配网前,请确保设备处于待配网状态。操作方法可参考设备的使用说明书。

在成功生成二维码后,引导用户将二维码对准摄像头,捕捉到二维码信息后会发出提示音。开始配网操作后,SDK 会持续广播配网信息,直到配网成功或是超时才停止。

调用 配网接口。目前已经创建好 ThingCameraActivatorBuilder 对象 builder,只需进行下面几行代码调用即可。

mThingActivator = ThingHomeSdk.getActivatorInstance().newCameraDevActivator(builder);
        mThingActivator.createQRCode();
        mThingActivator.start();

监听配网结果

在上面构建配网参数时候,您通过 ThingCameraActivatorBuilder 对象 builder 设置监听,通过 IThingSmartCameraActivatorListener 回调监听配网结果。

public interface IThingSmartCameraActivatorListener {
    void onQRCodeSuccess(String qrcodeUrl);

    void onError(String errorCode, String errorMsg);

    void onActiveSuccess(DeviceBean devResp);
}

停止配网

如果需要中途取消配网,如离开配网页面,或配网完成,请调用 停止配网接口

mThingActivator.stop();

视频实时预览(视频直播)

IPC SDK 提供智能摄像机的实时视频播放、设备存储卡录像播放、对当前正在播放的视频截图、录制视频、与摄像机实时通话等基础能力。

第一步:初始化

您需要首先初始化一个 IThingSmartCameraP2P 对象。该对象用于读取智能摄像机设备的相关属性和方法。

ThingCameraView 是 IPC SDK 提供的视频渲染视图,通过它您可以快速的搭建渲染出视频预览画面。

    // 1. 创建 IThingSmartCameraP2P
    IThingSmartCameraP2P mCameraP2P = null;
    IThingIPCCore cameraInstance = ThingIPCSdk.getCameraInstance();
    if (cameraInstance != null) {
        mCameraP2P = cameraInstance.createCameraP2P(devId));
    }
    ThingCameraView mVideoView = findViewById(R.id.camera_video_view);
    // 2. 为渲染视图容器设置回调
    mVideoView.setViewCallback(new AbsVideoViewCallback() {
        @Override
        public void onCreated(Object view) {
            super.onCreated(view);
            //4. 渲染视图构造完成时,为 IThingSmartCameraP2P 绑定渲染视图
            if (null != mCameraP2P){
                mCameraP2P.generateCameraView(view);
            }
        }
    });
    // 3. 构造渲染视图
    mVideoView.createVideoView(p2pType);
    // 4. 注册 P2P 监听
    AbsP2pCameraListener absP2pCameraListener = new AbsP2pCameraListener() {
        @Override
        public void onSessionStatusChanged(Object camera, int sessionId, int sessionStatus) {
                super.onSessionStatusChanged(o, i, i1);
                // sessionStatus = -3 (超时)或 -105(鉴权失败) 时,建议发起一次重连,注意避免循环调用
                }
    };
    if (null != mCameraP2P){
        mCameraP2P.registerP2PCameraListener(absP2pCameraListener);
    }

建议不要为一个设备同时创建两个 IThingSmartCameraP2P 对象,否则会导致资源错误释放出现异常情况。

第二步:连接 P2P 通道

一系列初始化动作完成后,需要先连接 P2P 通道。P2P 的连接状态需要您自行维护。

开始连接 P2P 通道,参数 mode 可以指定优先选择的连接模式,是通过局域网还是公网来连接。如果指定局域网连接优先,但是 App 和设备没有在同一个局域网内建立 TCP 连接,或者设备不支持局域网连接优先,SDK 会自动使用公网连接模式,详情请参考 P2P 连接

  • 建立指定通道

    void connect(String devId, int mode, OperationDelegateCallBack callBack);
    
  • 断开通道

    在关闭通道或离开当前页面时,您需要调用接口关闭通道。

    void disconnect(String devId, OperationDelegateCallBack callBack);
    
  • 状态监听

    若您需要在过程中监听通道的连接和断开,通过 OperationDelegateCallBack 来实现监听。

    mCameraP2P.connect(devId, new OperationDelegateCallBack() {
        @Override
        public void onSuccess(int sessionId, int requestId, String data) {
            //连接成功
        }
    
        @Override
        public void onFailure(int sessionId, int requestId, int errCode) {
            //连接失败
        }
    });
    }
    

第三步:预览实时视频

在建立 P2P 通道后,用户就可以预览在线视频了。

  • 开始预览

    以下代码中,clarity 是清晰度模式,2 代表标清、4 代表高清。

    void startPreview(int clarity, OperationDelegateCallBack callBack);
    
  • 结束预览

    int stopPreview(OperationDelegateCallBack callBack);
    
  • 状态监听

    实现如下代码可以监听视频开始预览的状态变化,stopPreview 同理。

    mCameraP2P.startPreview(new OperationDelegateCallBack() {
        @Override
        public void onSuccess(int sessionId, int requestId, String data) {
            //开始播放实时视频成功
        }
    
        @Override
        public void onFailure(int sessionId, int requestId, int errCode) {
            //开始播放实时视频失败
        }
    });
    

第四步:注销进程

不再使用摄像机视频功能的时候,请务必注销 P2P 监听器和删除 P2P 对象。

@Override
public void onDestroy() {
    if (null != mCameraP2P) {
        mCameraP2P.removeOnP2PCameraListener(absP2pCameraListener);
        mCameraP2P.destroyP2P();
    }
}

视频录制与实时对讲

在预览实时视频的同时,您还可以对录制在线视频或通过摄像机实时对讲。更多接口的使用说明,可参考 音视频功能

请注意,录制视频需要写存储卡权限。根据不同的安卓版本,设置会有所不同。

  • 开始录制或对讲

    int startRecordLocalMp4(String folderPath, Context context, OperationDelegateCallBack callBack);
    void startAudioTalk(OperationDelegateCallBack callBack);
    
  • 结束录制/对讲

    int stopRecordLocalMp4(OperationDelegateCallBack callBack);
    void stopAudioTalk(OperationDelegateCallBack callBack);
    
  • 监听状态

    您可以通过监听回调事件 OperationDelegateCallBack 来监听录制和实时对讲的状态。

    public interface OperationDelegateCallBack {
        void onSuccess(int sessionId, int requestId, String data);
    
        void onFailure(int sessionId, int requestId, String data);
    }
    

回放历史视频

设备在存储卡中保存视频录像后,可以通过 IPC SDK 在 App 端播放视频录像。

和视频预览一样,在开始回放前,您需要先连接上 P2P 通道。

IPC SDK 支持以天为单位查看和播放视频录像,并且提供查询某年某月中,哪几天保存有视频录像,以便于用户查看,查询结果通过 OperationDelegateCallBack 监听回调返回。

设备端保存在存储卡中的视频片段,最长为 10 分钟一段,最短为 10 秒钟一段。有关存储卡的录制功能和相关设置,请参考 存储卡功能

查询视频列表

创建 IThingSmartCameraP2P 对象,连接上 P2P 通道。P2P 通道连接成功后,可以获取到设备端存储卡中录制的视频片段时间信息。

第一步:查询特定月份保存过视频的日期

int year = Integer.parseInt(substring[0]);
int mouth = Integer.parseInt(substring[1]);
queryDay = Integer.parseInt(substring[2]);
mCameraP2P.queryRecordDaysByMonth(year, mouth, new OperationDelegateCallBack() {
  @Override
  public void onSuccess(int sessionId, int requestId, String data) {
    //data 是获取到的月份数据
    MonthDays monthDays = JSONObject.parseObject(data, MonthDays.class);
    mBackDataMonthCache.put(mCameraP2P.getMonthKey(), monthDays.getDataDays());
    mHandler.sendMessage(MessageUtil.getMessage(MSG_DATA_DATE, ARG1_OPERATE_SUCCESS, data));
  }

  @Override
  public void onFailure(int sessionId, int requestId, int errCode) {
    mHandler.sendMessage(MessageUtil.getMessage(MSG_DATA_DATE, ARG1_OPERATE_FAIL));
  }
});

第二步:获取特定日期的视频列表

您可以根据上一步返回的日期查询当天的视频信息列表。

int year = Integer.parseInt(substring[0]);
int mouth = Integer.parseInt(substring[1]);
int day = Integer.parseInt(substring[2]);
mCameraP2P.queryRecordTimeSliceByDay(year, mouth, day, new OperationDelegateCallBack() {
  @Override
  public void onSuccess(int sessionId, int requestId, String data) {
    //data 是获取到指定日期的视频片段数据
    parsePlaybackData(data);
  }

  @Override
  public void onFailure(int sessionId, int requestId, int errCode) {
    mHandler.sendEmptyMessage(MSG_DATA_DATE_BY_DAY_FAIL);
  }
});

data 不会直接返回视频数据本身,您可以参考下面方法进行解析。

private void parsePlaybackData(Object obj) {
        RecordInfoBean recordInfoBean = JSONObject.parseObject(obj.toString(), RecordInfoBean.class);
        if (recordInfoBean.getCount() != 0) {
            List<TimePieceBean> timePieceBeanList = recordInfoBean.getItems();
            if (timePieceBeanList != null && timePieceBeanList.size() != 0) {
                mBackDataDayCache.put(mCameraP2P.getDayKey(), timePieceBeanList);
            }
            mHandler.sendMessage(MessageUtil.getMessage(MSG_DATA_DATE_BY_DAY_SUCC, ARG1_OPERATE_SUCCESS));
        } else {
            mHandler.sendMessage(MessageUtil.getMessage(MSG_DATA_DATE_BY_DAY_FAIL, ARG1_OPERATE_FAIL));
        }
    }

以上代码涉及了以下 2 个实体对象类:

  • RecordInfoBean 数据模型:

    参数 说明
    count 片段个数
    List 视频片段集合
  • TimePieceBean 数据模型

    参数 说明
    startTime 视频片段开始时间
    endTime 视频片段结束时间
    playTime 视频片段播放时间

完成以上步骤,您就可以获取到有回放的视频片段。

播放视频

成功查询到某天中的视频录像片段后,就可以开始播放录像视频了。

在以下场景中,您需要重新 查询当天的视频录像片段,避免出现播放异常:

  • 停止播放视频录像片段后,再实时播放视频。
  • 断开 P2P 连接,重新连接上 P2P 通道后。
  • 开始播放

    mCameraP2P.startPlayBack(timePieceBean.getStartTime(),
                            timePieceBean.getEndTime(),
                            timePieceBean.getStartTime(), new OperationDelegateCallBack() {
                            @Override
                            public void onSuccess(int sessionId, int requestId, String data){
                                isPlayback = true;
                            }
    
                            @Override
                            public void onFailure(int sessionId, int requestId, int errCode){
                                isPlayback = false;
                            }
                            }, new OperationDelegateCallBack() {
                            @Override
                            public void onSuccess(int sessionId, int requestId, String data){
                                isPlayback = false;
                            }
    
                            @Override
                            public void onFailure(int sessionId, int requestId, int errCode){
                                isPlayback = false;
                            }
                            });
    

    该方法的三个参数均为时间戳:

    • PlayTime 为播放的初始帧时间,可以与startTime 设置为同一值。
    • StartTime 为播放的开始时间,为 TimePieceBean 数据模型中 startTime 相对应的值。
    • EndTime 为播放的结束时间,为 TimePieceBean 数据模型中 endTime 相对应的值。
  • 暂停回放

    void pausePlayBack(OperationDelegateCallBack callBack);
    
  • 继续回放

    void resumePlayBack(OperationDelegateCallBack callBack);
    
  • 停止回放

    void stopPlayBack(OperationDelegateCallBack callBack);
    
  • 监听回调(OperationDelegateCallBack)

    public interface OperationDelegateCallBack {
        void onSuccess(int sessionId, int requestId, String data);
    
        void onFailure(int sessionId, int requestId, String data);
    }
    

    pausePlayBackstopPlayBack 都能达到停止播放视频的目的,它们之间的区别和联系是:

    • 调用 stopPlayback 后,无法调用 resumePlayBack 来恢复播放。停止播放后,如果想要继续之前的进度开始播放,那么必须保存停止播放时,最后一帧视频的时间戳,以及播放的视频录像的时间片段,以重新调用 startPlayBack 方法来继续播放。
    • 在成功查询到视频录像片段时间数据后,无论是正在播放录像视频,还是暂停播放,都可以直接调用 startPlayBack 重新播放另一个视频录像片段,而不必先调用 stopPlayBack 停止播放。

相关概念

涂鸦智能摄像机的视频录像分为 连续录像事件录像

  • 连续录像:视频录像会是 10 分钟一个片段,且所有视频片段是连续的,但是如果中间有停止过视频录像,那么连续录像模式下的视频片段间也可能会有间隔。

    如果某天的视频录像片段是连续的,那么播放录像时,会自动播放下一段。也就是说,即使调用开始播放接口,传入的是当天第一个视频片段的时间点,视频也会一直播放到当天最后一个视频片段的最后一帧视频,才会回调视频播放结束的代理方法。

  • 事件录像:每个录像片段长度不等,且片段间的间隔时间也长短不一。

    如果某天的视频录像片段是不连续的,即视频录像片段 A 结束后隔了一段时间才有视频录像片段 B,在播放到断开的地方(即播放到视频片段 A 的最后一帧),视频流会自动停止。

下一步

IPC SDK 还提供了更多功能,您可以根据 Sample 工程和 IPC SDK 文档了解如何构建 App。