本章主要描述基于Android版本设备端SDK开发LinkVisual视频设备的SDK和操作流程。

设备端SDK依赖如下。

依赖SDK 概述
Link Kit Android SDK 提供设备与云端的双向数据通道能力。

初始化SDK

请在Link Kit SDK初始化完毕后对IPC SDK的初始化,需要传入设备证书信息。

LinkKit.getInstance().init(this, params, new ILinkKitConnectListener() {
            @Override
            public void onError(AError error) {
                Log.d(TAG,
                    "onError() called with: error = [" + (error == null ? "null" : (error.getCode() + error.getMsg()))
                        + "]");

            }

            @Override
            public void onInitDone(Object data) {
                Log.d(TAG, "onInitDone() called with: data = [" + JSON.toJSONString(data) + "]");
                // 初始化IPC SDK
                IPCDev.getInstance().init(your_productKey, your_deviceName, your_deviceSecret);
            }

其中your_productnameyour_devicenameyour_devicesecret需要替换为自己的设备证书信息。

IPC SDK需借助Linkkit的能力来完成消息监听和处理。注册监听需要两步,首先在设备服务

//注册异步服务调用监听器
LinkKit.getInstance().getDeviceThing().setServiceHandler(service.getIdentifier(),
                itResRequestHandler);
//异步服务调用监听器
private ITResRequestHandler itResRequestHandler = new ITResRequestHandler() {
        @Override
        public void onProcess(String identify, Object result, ITResResponseCallback
            itResResponseCallback) {
            Log.d(TAG,
                "ITResRequestHandler  onProcess() called with: identify = [" + identify + "], result = ["
                    + JSON.toJSONString(result) + "], itResResponseCallback = ["
                    + itResResponseCallback + "]");
            /**
             * 添加IPC SDK对异步服务调用的监听
             */
            IPCDev.getInstance().notifyAsyncTopicReceived(identify, result, itResResponseCallback);
        }

        @Override
        public void onSuccess(Object o, OutputParams outputParams) {
            Log.d(TAG,
                "onSuccess() called with: o = [" + JSON.toJSONString(o) + "], outputParams = [" + JSON
                    .toJSONString(outputParams) + "]");
        }

        @Override
        public void onFail(Object o, ErrorInfo errorInfo) {
            Log.d(TAG, "onFail() called with: o = [" + JSON.toJSONString(o) + "], errorInfo = [" + JSON
                .toJSONString(errorInfo) + "]");
        }
    };
/**
 * 注册同步服务调用的监听器
 */
LinkKit.getInstance().registerOnPushListener(connectNotifyListener);
//同步服务调用监听器
private IConnectNotifyListener connectNotifyListener = new IConnectNotifyListener() {
        @Override
        public void onNotify(String connectId, String topic, AMessage aMessage) {
            /**
             * 添加IPC SDK对同步服务调用的监听
             */
            IPCDev.getInstance().notifySyncTopicReceived(connectId, topic, aMessage);

            if (CONNECT_ID.equals(connectId) && !TextUtils.isEmpty(topic) &&
                topic.startsWith("/sys/" + productKey + "/" + deviceName + "/rrpc/request")) {
                Log.d(TAG, "IConnectNotifyListener   onNotify() called with: connectId = [" + connectId + "], topic = ["
                    + topic + "], aMessage = ["
                    + new String((byte[])aMessage.data) + "]");
            }
        }

        @Override
        public boolean shouldHandle(String connectId, String topic) {
            return true;
        }

        @Override
        public void onConnectStateChange(String connectId, ConnectState connectState) {

        }
    };

混淆配置

# keep linkvisual

-keep class com.aliyun.iotx.linkvisual.ipc.** { *; }

依赖引入

// 1. 根build.gradle添加对aliyun maven仓库的引用
allprojects {
    repositories {
        maven {
            url "http://maven.aliyun.com/nexus/content/repositories/releases"
        }
    }
}

// 2. app build.gradle中添加依赖
implementation 'com.aliyun.iotx:linkvisual-ipc:1.3.0'

功能描述

  • 存储卡录像查看:将存储在SD卡等外存中的录像文件推到服务端,支持seek到指定位置操作。
  • 语音对讲:与App端建立双向语音通道,设备端采集录音并实时发送至App端,同时接收到App端发送的语音进行播放。
  • 拍照:抓拍当前摄像头画面并上传至云端。
  • 上报事件:上报带图片的事件给云端。

直播推流

支持RTMP推流,视频支持H264/H265,音频支持G711a以及AAC_LC格式。

整个处理流程分为以下几步。

  1. 注册直播事件监听器和流错误监听器。
    PC SDK收到服务端下发的开始推流指令后,会通过事先注册的直播流事件监听器(OnLiveStreamListener)来通知何时开始或结束推流/强制I帧等。

    推流中发生的错误也将通过流错误监听器来通知。请不要在回调接口中执行阻塞任务。

    // 设置直播流事件监听
    IPCDev.getInstance().getIpcStreamManager().setOnLiveStreamListener(MainActivity.this);
    // 设置流错误监听
    IPCDev.getInstance().getIpcStreamManager().setOnStreamErrorListener(MainActivity.this);
    public interface OnLiveStreamListener {
        /**
         * 收到开始推直播流请求
         *
         * @param streamId   流ID
         * @param streamType 码流类型: 0为主码流, 1为辅码流
         * @param preTimeInS 预先录制时间,单位S
         */
        void onStartPushLiveStreaming(final int streamId, final int streamType, final int preTimeInS);
    
        /**
         * 收到停止推流请求
         *
         * @param streamId 流ID
         */
        void onStopPushStreaming(final int streamId);
    
        /**
         * 收到强制I帧请求
         * 需立即构造一个I帧并发送
         * @param streamId 流ID
         */
        void onForceIFrame(int streamId);
    }
    public interface OnStreamErrorListener {
        /**
         * 流异常时回调
         * @param streamId
         * @param error 参考StreamError定义
         */
        void onError(int streamId, StreamError error);
    }

    流错误码如下所示。

    错误码 标志符 错误描述
    1 StreamError.ERROR_STREAM_CREATE_FAILED 创建流实例失败。
    2 StreamError.ERROR_STREAM_START_FAILED 开流失败。
    3 StreamError.ERROR_STREAM_STOP_FAILED 停止流失败。
    4 StreamError.ERROR_STREAM_SEND_VIDEO_FAILED 发送视频数据失败。
    5 StreamError.ERROR_STREAM_SEND_AUDIO_FAILED 发送音频数据失败。
    6 StreamError.ERROR_STREAM_INVALID_PARAMS 无效的流参数。

    RTMP错误码如下所示。

    错误码 标志符 描述
    -1 RTMP_ILLEGAL_INPUT 输入不合法,请检查输入参数。
    -2 RTMP_MALLOC_FAILED 内存分配失败。
    -3 RTMP_CONNECT_FAILED RTMP建连失败。
    -4 RTMP_IS_DISCONNECTED RTMP连接未建立。
    -5 RTMP_UNSUPPORT_FORMAT 不支持的音视频格式。
    -6 RTMP_SEND_FAILED RTMP数据包发送失败。
    -7 RTMP_READ_MESSAGE_FAILED RTMP消息读取失败。
    -8 RTMP_READ_TIMESTAMP_ERROR 输入时间戳错误。
  2. 处理开始直播推流请求。
    当服务端下发推流请求时,回调OnLiveStreamListener.onStartPushLiveStreaming(int streamId, int streamType, int preTimeInS)方法来通知设备端需要开始采流并推流。

    一般需要开启摄像头和录音机进行采流,对摄像头采集的数据调用MediaCodec进行H264编码,对录音机采集的数据进行G711a编码,提前设置对应格式的音视频参数,分别调用发送音视频的接口来持续发送采集到编码后的数据。

    @Override
        public void onStartPushLiveStreaming(int streamId, int streamType, int preTimeInS) {
            this.streamId = streamId;
            try {
    
                // 构造视频参数
                VideoStreamParams videoStreamParams = new VideoStreamParams();
                // 直播流该参数始终为0
                videoStreamParams.setDurationInS(0); 
                videoStreamParams.setVideoFormat(VideoStreamParams.VIDEO_FORMAT_H264);
    
                // 构造音频参数
                AudioStreamParams audioStreamParams = new AudioStreamParams();
                audioStreamParams.setAudioChannel(AudioStreamParams.AUDIO_CHANNEL_MONO);
                audioStreamParams.setAudioFormat(AudioStreamParams.AUDIO_FORMAT_G711A);
                audioStreamParams.setAudioEncoding(AudioStreamParams.AUDIO_ENCODING_16BIT);
                audioStreamParams.setAudioSampleRate(AudioStreamParams.AUDIO_SAMPLE_RATE_8000);
    
                // 设置推流参数
                IPCDev.getInstance().getIpcStreamManager().setStreamParams(streamId, videoStreamParams, audioStreamParams);
    
                // TODO 开始采流、编码并发送音视频数据            
            } catch (NoSuchStreamException e) {
                e.printStackTrace();
            }
        }

    发送音视频数据接口(IPCStreamManager)。

    /**
         * 发送音频帧数据
         *
         * @param streamId      流ID
         * @param data          源数据
         * @param offset        偏移量
         * @param length        数据长度
         * @param timeStampInMs 音频帧时间戳,单位ms
         */
        void sendAudioData(int streamId, byte[] data, int offset, int length, long timeStampInMs) throws NoSuchStreamException
    
         /**
         * 发送视频帧数据
         *
         * @param streamId      流ID
         * @param data          源数据
         * @param offset        偏移量
         * @param length        数据长度
         * @param isIFrame      是否为I帧(I帧数据需要包含SPS+PPS帧)
         * @param timeStampInMs 视频帧时间戳,单位ms
         */
        public void sendVideoData(int streamId, byte[] data, int offset, int length, boolean isIFrame, long timeStampInMs) throws NoSuchStreamException

    H264/H265的帧结构会有一定的要求,可以以打印I帧的前256个字节进行查看,打印代码如下。

    for (int i = 0; i < ((buffer_size > 256)?256:buffer_size); i++) {
        printf("%02x ", buffer[i]);
        if ((i + 1) % 30 == 0) {
            printf("\n");
        }
    }
    printf("\n");

    H264要求I帧为:帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR。如下图所示。

    I帧

    0x000001或者0x00000001是帧分隔符,0x67是SPS的开始,0x68是PPS的开始,0x65是IDR的开始H265要求I帧为:帧分隔符+VPS+帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR。如下图所示。

    I帧

    0x000001或者0x00000001是帧分隔符,0x40是VPS的开始,0x42是SPS的开始,0x44是PPS的开始,0x26是IDR的开始。

  3. 处理结束推流请求。
    当服务端下发停止推流请求时,回调OnLiveStreamListener.onStopPushLiveStreaming()方法来通知设备端停止推流。

    一般需要停止摄像头数据和录音机数据的采集,并调用IPCStreamManager的stopStreaming(int streamId)方法。

    /**
         * 收到停止推流请求
         *
         * @param streamId 流ID
         */
        @Override
        public void onStopPushStreaming(int streamId) {
            // TODO 停止音视频数据的发送
            try {
                // 调用停止推流接口
                IPCDev.getInstance().getIpcStreamManager().stopStreaming(streamId);
            } catch (NoSuchStreamException e) {
                e.printStackTrace();
            }
        }
  4. 处理流错误。
    推流过程中需要处理流错误,通过OnStreamErrorListener.onError(int streamId, StreamError error)来接收和处理。

点播推流

支持RTMP推流,视频支持H264/H265,音频支持G711a以及AAC_LC格式。

  1. 注册点播事件监听器和流错误监听器。
    IPC SDK收到服务端下发的开始推流指令后,会通过事先注册的点播流事件监听器(OnVodStreamListener)来通知何时开始或结束推流/暂停/恢复/seek等。

    推流中发生的错误也将通过流错误监听器来通知。请不要在回调接口中执行阻塞任务。

  2. 处理查询设备端录像列表请求。
    App端发起查询设备端录像列表的请求,设备端会收到同步服务调用(rrpc/request),收到查询设备录像列表请求,响应该请求将当查询范围内的文件列表返回给App端。
    @Override
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                Log.d(TAG, "onNotify() called with: connectId = [" + connectId + "], topic = [" + topic + "], aMessage = ["
                        + new String((byte[]) aMessage.data) + "]");
                /**
                 * 添加SDK的监听。
                 */
                IPCDev.getInstance().notifySyncTopicReceived(connectId, topic, aMessage);
    
                // 处理同步服务调用
                if (CONNECT_ID.equals(connectId) && !TextUtils.isEmpty(topic) &&
                        topic.contains("rrpc")) {
                    Log.d(TAG, "IConnectNotifyListener   onNotify() called with: connectId = [" + connectId + "], topic = ["
                            + topic + "], aMessage = ["
                            + new String((byte[]) aMessage.data) + "]");
    
                    int code = 200;
                    String data = "{}";
    
                    JSONObject json = JSON.parseObject(new String((byte[]) aMessage.data));
                    if (json != null) {
                        String method = json.getString("method");
                        JSONObject params = json.getJSONObject("params");
                        switch (method) {
                            // 查询设备录像列表请求
                            case "thing.service.QueryRecordList":
                                int beginTime = params.getIntValue("BeginTime");
                                int endTime = params.getIntValue("EndTime");
                                int querySize = params.getIntValue("QuerySize");
                                int type = params.getIntValue("Type");
                                appendLog("收到查询设备录像列表的请求: beginTime=" + beginTime +
                                        "\tendTime=" + endTime + "\tquerySize=" + querySize + "\ttype=" + type);
    
                                JSONArray resultArray = new JSONArray();
                                JSONObject item1 = new JSONObject();
                                item1.put("FileName", Base64.encode("file1".getBytes(), Base64.DEFAULT));
                                item1.put("BeginTime", System.currentTimeMillis() / 1000 - 200);
                                item1.put("EndTime", System.currentTimeMillis() / 1000 - 100);
                                item1.put("Size", 1024000);
                                item1.put("Type", 0);
                                resultArray.add(item1);
    
                                JSONObject item2 = new JSONObject();
                                item2.put("FileName", Base64.encode("file2".getBytes(), Base64.DEFAULT));
                                item2.put("BeginTime", System.currentTimeMillis() / 1000 - 100);
                                item2.put("EndTime", System.currentTimeMillis() / 1000);
                                item2.put("Size", 1024000);
                                item2.put("Type", 0);
                                resultArray.add(item2);
    
                                JSONObject result = new JSONObject();
                                result.put("RecordList", resultArray);
    
                                code = 200;
                                data = result.toJSONString();
                                break;
                            default:
                                break;
                        }
                    }
    
                    MqttPublishRequest request = new MqttPublishRequest();
                    request.isRPC = false;
                    request.topic = topic.replace("request", "response");
                    String resId = topic.substring(topic.indexOf("rrpc/request/") + 13);
                    request.msgId = resId;
                    request.payloadObj = "{\"id\":\"" + resId + "\", \"code\":" + code + ",\"data\":" + data + "}";
                    LinkKit.getInstance().publish(request, new IConnectSendListener() {
                        @Override
                        public void onResponse(ARequest aRequest, AResponse aResponse) {
                            appendLog("上报成功");
                        }
    
                        @Override
                        public void onFailure(ARequest aRequest, AError aError) {
                            appendLog("上报失败:" + aError.toString());
                        }
                    });
                }
            }
  3. 处理开始开始推流指令。
    App端请求上一步返回的列表中某个设备录像文件后,服务端会下发推流指令,回调OnVodStreamListener.onStartPushVodStreaming(int streamId, String fileName)方法来通知设备端需要将指定fileName的音频数据推流。
    注意 文件名需使用Base64进行编码。
    @Override
        public void onStartPushVodStreaming(int streamId, String fileName) {
            appendLog("开始推点播流 " + streamId + " 文件名:" + new String(Base64.decode(fileName, Base64.NO_WRAP)));
    
            try {
                // 构造视频参数
                VideoStreamParams videoStreamParams = new VideoStreamParams();
                // 该视频文件的时长,单位S
                videoStreamParams.setDurationInS(H264_DURATION_IN_S);
                videoStreamParams.setVideoFormat(VideoStreamParams.VIDEO_FORMAT_H264);
    
                // 构造音频参数
                AudioStreamParams audioStreamParams = new AudioStreamParams();
                audioStreamParams.setAudioChannel(AudioStreamParams.AUDIO_CHANNEL_MONO);
                audioStreamParams.setAudioFormat(AudioStreamParams.AUDIO_FORMAT_G711A);
                audioStreamParams.setAudioEncoding(AudioStreamParams.AUDIO_ENCODING_16BIT);
                audioStreamParams.setAudioSampleRate(AudioStreamParams.AUDIO_SAMPLE_RATE_8000);
    
                // 设置推流参数
                IPCDev.getInstance().getIpcStreamManager().setStreamParams(streamId, videoStreamParams, audioStreamParams);
    
                // TODO 读取fileName文件,调用发送音视频数据接口进行推流
    
            } catch (NoSuchStreamException e) {
                e.printStackTrace();
            }
        }
  4. 处理暂停/恢复指令。
    需响应暂停/恢复推流指令(OnVodStreamListener),相应的暂停/恢复发送音视频数据。
    /**
         * 收到暂停推流请求
         *
         * @param streamId 流ID
         */
        void onPausePushVodStreaming(int streamId);
    
        /**
         * 收到恢复推流的请求
         *
         * @param streamId 流ID
         */
        void onResumePushVodStreaming(int streamId);
  5. 处理Seek指令。
    需响应Seek指令(OnVodStreamListener),例如App端播放器进度条Seek到80秒时,对应会回调onSeekTo方法,请从该timeStampInS时间点最近的I帧开始继续推流。
    /**
         * 收到重新定位请求
         *
         * @param streamId     流ID
         * @param timeStampInS 时间偏移量,相对于视频开始时间,单位为S
         */
        void onSeekTo(int streamId, long timeStampInS);
  6. 处理停止推流指令。
    当服务端下发停止推流请求时,回调OnVodStreamListener.onStopPushLiveStreaming()方法来通知设备端停止推流。

    一般要停止调用发送音视频接口、关闭视频文件,并调用IPCStreamManager的stopStreaming(int streamId)方法。

    /**
         * 收到停止推流请求
         *
         * @param streamId 流ID
         */
        @Override
        public void onStopPushStreaming(int streamId) {
            // TODO 停止音视频数据的发送
            try {
                // 调用停止推流接口
                IPCDev.getInstance().getIpcStreamManager().stopStreaming(streamId);
             catch (NoSuchStreamException e) {
                e.printStackTrace();
            }
        }
  7. 处理流错误。
    推流过程中需要处理流错误,通过OnStreamErrorListener.onError(int streamId, StreamError error)来接收和处理。

语音对讲

语音对讲支持双工模式。设备端需要依次做音频采集、音频编码(可选)、上传/接收、音频解码(可选)、音频播放等这些流程。

音频格式支持如下。

格式 编码 解码
PCM - -
AAC_LC
G711A
G711U
  1. 注册语音对讲事件监听器和错误监听器。
    IPC SDK收到服务端下发的开始推流指令后,会通过事先注册的语音对讲事件监听器(OnLiveIntercomListener)来来通知开始/结束语音对讲、接收对端的音频参数和语音数据。

    推流中发生的错误也将通过流错误监听器来通知。请不要在回调接口中执行阻塞任务。

    设置如下监听。

    // 设置语音对讲事件监听
    IPCDev.getInstance().getIpcLiveIntercom().setOnLiveIntercomListener(MainActivity.this);
    // 设置语音对讲错误回调
    IPCDev.getInstance().getIpcLiveIntercom().setOnLiveIntercomErrorListener(MainActivity.this);
    public interface OnLiveIntercomListener {
    
        /**
         * 收到App端发起的开始语音对讲请求
         *
         * @return 返回当前设备端上行音频参数格式,如采样率、通道数、采样位宽、音频格式, 请确保对App端能支持该音频参数配置
         */
        AudioParams onStartVoiceIntercom();
    
        /**
         * 收到结束语音对讲请求
         */
        void onStopVoiceIntercom();
    
        /**
         * 收到App端音频参数
         * @param audioParams
         */
        void onAudioParamsChange(AudioParams audioParams);
    
        /**
         * 收到App端发送的音频数据
         * @param buffer
         * @param size
         */
        void onAudioBufferReceive(byte[] buffer, int size);
    }
    public interface OnLiveIntercomErrorListener {
    
        /**
         * 语音对讲发生错误
         * @param error 见{@link LiveIntercomError}
         */
        void onError(LiveIntercomError error);
    }
  2. 响应开始语音对讲请求。
    收到开始语音对讲请求后,需要启动录音机开始音频采集并将音频数据发送给对端
    @Override
        public AudioParams onStartVoiceIntercom() {
            appendLog("收到开始语音对讲指令");
    
            // 收到开始语音对讲请求, 启动录音机
            simpleAudioRecord.setAudioRecordListener(new AudioRecordListener() {
                @Override
                public void onRecordStart() {
                    // 录音开始
                    appendLog("录音开始");
                }
    
                @Override
                public void onRecordEnd() {
                    // 录音结束
                    appendLog("录音结束");
                }
    
                @Override
                public void onBufferReceived(byte[] buffer, int offset, int size) {
                    // 收到录音机抛出的PCM数据, 调用发送接口发送给对端
                    IPCDev.getInstance().getIpcLiveIntercom().sendAudioBuffer(buffer, offset, size);
                }
    
                @Override
                public void onError(int error, String message) {
                    appendLog("录音机错误:" + error + " " + message);
                }
            });
    
            simpleAudioRecord.start();
    
            // 通知使用G711A作为音频发送格式,内部会对PCM数据重新做编码
            return AudioParams.AUDIOPARAM_MONO_8K_G711A;
        }

    发送音频数据接口(IPCLiveIntercom)。

    /**
         * 发送音频数据<br>
         * 等价{@link #sendAudioBuffer(byte[] data, int offset, int length, boolean enableEncode)} enableEncode=true
         */
        void sendAudioBuffer(byte[] data, int offset, int length);
    
        /**
         * 发送音频数据<br>
         *
         * @param data         数据buffer
         * @param offset       偏移量
         * @param length       长度
         * @param enableEncode true: 内部会依据{@link OnLiveIntercomListener#onStartVoiceIntercom()}返回的音频格式对送入的数据(必须为PCM)重新编码并发送
         *                     false: 内部会直接发送音频数据
         */
        void sendAudioBuffer(byte[] data, int offset, int length, boolean enableEncode);
  3. 处理声音播放。
    当App端和设备端语音对讲通道建立后,设备端通过onAudioParamsChange(AudioParams audioParams)收到App端发过来的音频参数。依据该音频参数新建音频播放器,供后续接受的音频数据播放。
    @Override
        public void onAudioParamsChange(AudioParams audioParams) {
            // 收到对端发送的音频参数
            appendLog("收到客户端的音频参数: " + audioParams.toString());
    
            // 初始化播放器
            if (simpleStreamAudioTrack != null) {
                simpleStreamAudioTrack.release();
                audioTrackQueue.clear();
            }
            if (acousticEchoCanceler != null) {
                acousticEchoCanceler.release();
            }
            if (noiseSuppressor != null) {
                noiseSuppressor.release();
            }
            noiseSuppressor = NoiseSuppressor.create(simpleAudioRecord.getAudioSessionId());
            if (noiseSuppressor != null) {
                noiseSuppressor.setEnabled(true);
            }
            simpleStreamAudioTrack = new SimpleStreamAudioTrack(audioParams, AudioManager.STREAM_MUSIC, audioTrackQueue,
                    simpleAudioRecord.getAudioSessionId());
            if (AcousticEchoCanceler.isAvailable()) {
                acousticEchoCanceler = AcousticEchoCanceler.create(simpleAudioRecord.getAudioSessionId());
                if (acousticEchoCanceler != null) {
                    appendLog("已开启回声消除");
                    acousticEchoCanceler.setEnabled(true);
                }
            }
    
            simpleStreamAudioTrack.start();
        }
    
    
        @Override
        public void onAudioBufferReceive(byte[] buffer, int size) {
            // 收到对端发送的PCM数据
            audioTrackQueue.add(buffer);
        }
  4. 处理结束语音对讲指令。
    @Override
    public void onStopVoiceIntercom() {
     appendLog("收到停止语音对讲指令");
     // 收到停止语音对讲请求,停止录音
     simpleAudioRecord.stop();
    }
  5. 语音对讲错误处理。
    @Override
    public void onError(LiveIntercomError error) {
     // 语音对讲发生错误
     appendLog("语音对讲错误:" + error.getCode() + " msg:" + error.getMessage());
    
     // 停止录音
     simpleAudioRecord.stop();
    
     // 停止播放
     simpleStreamAudioTrack.stop();
    }

语音对讲错误列表如下。

错误码 错误描述
LiveIntercomError.INVALID_AUDIO_PARAMS 无效的设备端音频参数
LiveIntercomError.START_LIVE_INTERCOM_REQUEST_FAILED 无效的语音对讲请求
LiveIntercomError.CONNECTION_STREAM_FAILED 建立语音对讲流通道失败
LiveIntercomError.SEND_STREAM_DATA_FAILED 发送音频数据失败
LiveIntercomError.RECEIVE_STREAM_DATA_FAILED 接收音频数据失败

其他相关说明如下。

  • 回声消除支持

    因为涉及到实时语音对讲,设备端必须要支持回声消除。Demo中使用的AcousticEchoCanceler设备相关性大,实际体验效果一般,仅供参考。

  • 录音机与播放器

    IPC SDK提供了录音机(SimpleAudioRecord)和流音频播放器(SimpleStreamAudioTrack),可用于语音对讲音频采集和播放,仅供参考。

  • SimpleAudioRecord
    • 创建录音机实例
      /**
      * 按照指定的音频参数创建录音机实例子
      * @param audioSource 指定源
      * @param audioParams 指定录音机的通道数、采样率、采样位宽
      */
      SimpleAudioRecord(int audioSource, AudioParams audioParams);
    • 启动录音
      void start();
    • 获取AudioSession ID
      int getAudioSessionId()
    • 设置录音事件回调
      void setAudioRecordListener(AudioRecordListener listener)
      
      public interface AudioRecordListener {
      
          /**
           * 开始录音
           */
          void onRecordStart();
      
          /**
           * 结束录音
           */
          void onRecordEnd();
      
          /**
           * 录音数据回调(PCM数据)
           * @param buffer
           * @param offset
           * @param size
           */
          void onBufferReceived(byte[] buffer, int offset, int size);
      
          /**
           * 录音机错误
           * {@link SimpleAudioRecord#ERR_INIT_RECORD}
           * {@link SimpleAudioRecord#ERR_READ_BUFFER}
           * {@link SimpleAudioRecord#ERR_START_RECORD}
           * @param error
           * @param message
           */
          void onError(int error, String message);
      }
    • 结束录音
      void stop();
  • SimpleStreamAudioTrack
    • 创建播放器实例
      /**
           * 创建流播放器
           *
           * @param audioParams     音频参数
           * @param audioStreamType 流类型,见{@link android.media.AudioManager}
           * @param audioBuffer     数据阻塞队列,向该队列中加入数据即播放
           * @param audioSessionId  audio session id,用于回声消除
           * @throws IllegalArgumentException
           */
      SimpleStreamAudioTrack(AudioParams audioParams, int audioStreamType, BlockingQueue audioBuffer, String audioSessionId) throws IllegalArgumentException;
    • 设置播放器模式
      /**
           * 设置播放器模式
           * @param mode MODE_NORMAL    : 普通模式,适用于绝大部分场景
           *             MODE_LOW_DELAY : 低延迟模式,适用于高实时性场景,会在audioBuffer出现一定数据堆积时进行清理
           */
      void setMode(int mode)
    • 开始播放
      void start()
    • 恢复播放
      void resume()
    • 暂停播放
      void pause()
    • 停止播放
      void stop()
    • 获取播放状态
      int getPlayState()
    • 设置播放音量
      void setVolume(float volume)
    • 释放播放器
      void release()

图片上传

当前图片上传主要有两种:

  • App端触发设备拍照,设备拍照后上传。
  • 设备端触发事件主动上报事件而后进行的图片上传

当前图片上传和事件上报中的图片上传都是由一个统一的回调IUploadPicListener通知用户进行图片上传。回调中会为用户提供上传地址,此次上传的图片ID(当此次上传图片为事件上传图片,则此ID为上报事件时上报的图片ID)以及上传类型,用户根据这些信息自行进行图片上传,待上传完成后用户通过IUploadPicProcessCallback回调通知SDK上传任务完成。

说明 需要确认一下拍照的物模型TriggerPicCapture里的UploadUrl字符串长度,如果小于512字符,请增加长度到512字符以上,否则图片上传会受到影响。
  1. 拍照上传。
    拍照上传任务大体流程依次是SDK通知设备拍照、设备拍照、设备上传图片、通知SDK上传结果。

    具体的流程:设备端注册上传图片监听,当App触发拍照,SDK会通过IUploadPicListener的onUpload通知用户进行拍照以及上传,设备端能够通过onUpload中的UploadInfo获取此次拍照上传的URL,设备端自行拍照然后通过URL上传图片,然后通过IUploadPicProcessCallback回调通知SDK上传结果。

    /**
     * 上传图片监听
     */
    public interface IUploadPicListener {
        /**
         * 触发上传
         * @param uploadInfo  上传信息
         * @param callback    上传传结果回调
         */
        void onUpload(UploadInfo uploadInfo, IUploadPicProcessCallback callback);
    }
    /**
     * 上传图片
     */
    public class UploadInfo {
        /**
         * 上传url
         */
        private String uploadUrl;
        /**
         * 上传图片ID(用户生成的,只有事件上报时才有)
         */
        private String picId;
        /**
         * 上传类型,0为拍照,1为事件图片上传。
         */
        private int type;
    }
    /**
     * 上传图片结果回调
     */
    public interface IUploadPicProcessCallback {
        /**
         * 拍照任务成功
         */
        void onSucceed();
    
        /**
         * 上传任务失败
         *
         * @param errorMsg 错误信息
         */
        void onFailed(String errorMsg);
    
    }

    用户通过如下代码进行注册监听,LinkKit SDK初始化成功后进行注册。注册代码如下。

    //注册上传图片监听,当App端触发拍照或者设备上报了事件以后,会在注册的回调中收到上传的回调。
    IPCDev.getInstance().registerUploadPicListener(uploadPicListener);
    
    /**
     * 上传图片回调,回调中只有是事件照片上传的时候picId不为空且为上报事件时用户上传的ID。
     * type为上传类型,0为App触发设备拍照,1为设备上传事件照片。
     */
    private IUploadPicListener uploadPicListener = new IUploadPicListener() {
            @Override
            public void onUpload(final UploadInfo uploadInfo, final IUploadPicProcessCallback callback) {
                final String url = uploadInfo.getUploadUrl();
                final String picId = uploadInfo.getPicId();
                final int type = uploadInfo.getType();
                switch (type) {
                    //拍照上报
                    case 0:
                        //拍照并且上传
                        upLoadFile(url, pic, new CallBack(){
                                 @Override
                                 public void onSucceed() {
                                        Log.d(TAG, "uploadImage onSucceed");
                                        if (callback != null) {
                                                callback.onSucceed();
                                        }
                                 }
    
                                  @Override
                                  public void onFailed(String error) {
                                         Log.e(TAG, "uploadImage onFailed:" + error);
                                         if (callback != null) {
                                                callback.onFailed(error);
                                        }
                                 }                    
                        });            
                        break;
                    //事件图片上报
                    case 1:
                        //上传图片
                        upLoadFile(url, pic, new CallBack(){
                                 @Override
                                 public void onSucceed() {
                                        Log.d(TAG, "uploadImage onSucceed");
                                        if (callback != null) {
                                                callback.onSucceed();
                                        }
                                 }
    
                                  @Override
                                  public void onFailed(String error) {
                                         Log.e(TAG, "uploadImage onFailed:" + error);
                                         if (callback != null) {
                                                callback.onFailed(error);
                                        }
                                 }                    
                        });
                        break;
                    default:
                        break;
                }
            }};
  2. 事件上报。
    事件上报大体流程依次是设备触发事件、事件上报、SDK通知设备上传图片、设备上传图片、通知SDK上传结果。

    具体的流程:设备端注册上传图片监听,当设备端触发事件,设备通过SDK提供的接口进行事件上报(需要上传图片ID),SDK会通过IUploadPicListener的onUpload通知用户进行图片上传,设备端能够通过onUpload中的UploadInfo获取此次上传的URL以及图片ID,设备端自行通过URL上传图片,然后通过IUploadPicProcessCallback回调通知SDK上传结果。

    上报事件接口实例如下。

    //上报报警事件分成两步:1.上传报警,2.上传图片
    //上报报警事件需要用户生成一个报警图片ID,这个报警图片ID要求是设备唯一的图片ID,建议用Unix时间戳。
    // 不过这边需要确认一下拍照的物模型"TriggerPicCapture"里的"UploadUrl"的字符串长度,如果小于512请增加长度到512以上。
    // 上报事件以后会收到IUploadPicListener(需要注册监听)内的onUpload回调,然后请将对应的图片上传到回调中url指定地址。
    final String alarmPicId = String.valueOf(System.currentTimeMillis() / 1000);
    IPCDev.getInstance().reportAlarmEvent(alarmPicId, 1, new ReportAlarmEventListener() {
        @Override
        public void onSucceed() {
            Log.d(TAG, " report onSucceed  alarmPicId:" + alarmPicId);
            appendLog("上报事件成功");
        }
    
        @Override
        public void onFailed(String msg) {
            Log.e(TAG, " report onFailed  e:" + msg);
            appendLog("上报事件失败  e:" + msg);
        }
    });