设备端SDK(Android)文档

更新时间:2019-07-16 18:00:26

概述

本节为Link Visual 关于运行Android系统的IPC设备SDK功能描述.

依赖SDK 概述
Link Kit Android 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(productKey, deviceName, 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(name: 'linkvisualipc-1.3.0')

功能描述

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

直播推流

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

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

  1. 注册直播事件监听器和流错误监听器

    IPC SDK收到服务端下发的开始推流指令后,会通过事先注册的直播流事件监听器(OnLiveStreamListener)来通知何时开始/结束推流/强制I帧.
    推流中发生的错误也将通过流错误监听器(OnStreamErrorListener)来通知. 注意回调接口中不要执行阻塞任务。

    // 设置直播流事件监听
    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 输入时间戳错误

  1. 处理开始直播推流请求

当服务端下发推流请求时,回调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,如图
image.png
0x000001或者0x00000001是帧分隔符,0x67是SPS的开始,0x68是PPS的开始,0x65是IDR的开始
H265要求I帧为: 帧分隔符+VPS+帧分隔符+SPS+帧分隔符+PPS+帧分隔符+IDR,如图:
image.png
0x000001或者0x00000001是帧分隔符,0x40是VPS的开始,0x42是SPS的开始,0x44是PPS的开始,0x26是IDR的开始

  1. 处理结束推流请求

当服务端下发停止推流请求时,回调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();
        }
    }
  1. 处理流错误

推流过程中需要处理流错误,通过OnStreamErrorListener.onError(int streamId, StreamError error)来接收和处理.

点播推流

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

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

  1. 注册点播事件监听器和流错误监听器

    IPC SDK收到服务端下发的开始推流指令后,会通过事先注册的点播流事件监听器(OnVodStreamListener)来通知何时开始/结束推流/暂停/恢复/seek等.
    推流中发生的错误也将通过流错误监听器(OnStreamErrorListener)来通知. 注意回调接口中不要执行阻塞任务。

  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());
                    }
                });
            }
        }
  1. 处理开始开始推流指令

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();
        }
    }
  1. 处理暂停/恢复指令

需响应暂停/恢复推流指令(OnVodStreamListener). 相应的暂停/恢复发送音视频数据.

/**
     * 收到暂停推流请求
     *
     * @param streamId 流ID
     */
    void onPausePushVodStreaming(int streamId);

    /**
     * 收到恢复推流的请求
     *
     * @param streamId 流ID
     */
    void onResumePushVodStreaming(int streamId);
  1. 处理Seek指令

需响应Seek指令(OnVodStreamListener). 例如: App端播放器进度条seek到80s时,对应会回调onSeekTo方法,请从该timeStampInS时间点最近的I帧开始继续推流.

/**
     * 收到重新定位请求
     *
     * @param streamId     流ID
     * @param timeStampInS 时间偏移量,相对于视频开始时间,单位为S
     */
    void onSeekTo(int streamId, long timeStampInS);
  1. 处理停止推流指令

当服务端下发停止推流请求时,回调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();
        }
    }
  1. 处理流错误

推流过程中需要处理流错误,通过OnStreamErrorListener.onError(int streamId, StreamError error)来接收和处理.

语音对讲

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

音频格式支持情况:

编码 解码
PCM - -
AAC_LC
G711A
G711U

语音对讲集成分为以下几个步骤:

  1. 注册语音对讲事件监听器和错误监听器

IPC SDK收到服务端下发的语音对讲开始指令后,会通过事先注册的语音对讲事件监听器(OnLiveIntercomListener)来通知开始/结束语音对讲、接收对端的音频参数和语音数据. 推流中发生的错误也将通过错误监听器(OnLiveIntercomErrorListener)来通知. 注意回调接口中不要执行阻塞任务。
设置监听:

// 设置语音对讲事件监听
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);
}
  1. 响应开始语音对讲请求

收到开始语音对讲请求后,需要启动录音机开始音频采集并将音频数据发送给对端.

@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);
  1. 处理声音播放

当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);
    }
  1. 处理结束语音对讲指令

    @Override
    public void onStopVoiceIntercom() {
     appendLog("收到停止语音对讲指令");
     // 收到停止语音对讲请求,停止录音
     simpleAudioRecord.stop();
    }
    
  2. 语音对讲错误处理

    @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

  • 创建录音机实例

    ```java
    /**

  • 按照指定的音频参数创建录音机实例子

  • @param audioSource 指定源

  • @param audioParams 指定录音机的通道数、采样率、采样位宽

*/
SimpleAudioRecord(int audioSource, AudioParams audioParams);


- 启动录音<br />
```java
void start();
  • 获取AudioSession id

    int getAudioSessionId()
    
  • 设置录音事件回调

    ```java
    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);

}


- 结束录音<br />
```java
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()
    
  • 获取播放状态,见AudioTrack#PLAYSTATE

    int getPlayState()
    
  • 设置播放音量

    void setVolume(float volume)
    
  • 释放播放器

    void release()
    

图片上传

当前图片上传主要有两种:1.App端触发设备拍照,设备拍照后上传;2.设备端触发事件主动上报事件而后进行的图片上传。
当前图片上传和事件上报中的图片上传都是由一个统一的回调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);
    }
});




ChangeLog

1.0.0:

  • 支持直播推流(RTMP)

1.0.1:

  • 修复未加密直播推流崩溃

1.1.0:

  • 支持点播推流(RTMP)

  • 支持语音对讲功能

  • 支持图片上传

1.2.0

  • 语音对讲和推流支持AAC_LC格式音频数据发送

  • 包名由LinkVision变更为LinkVisual

1.3.0

  • 调整拍照上传以及事件上报接口

  • 优化语音对讲响应速度

  • 支持H265推流

  • 直播推流开启P2P

常见问题

1.系统版本要求
支持Android API 18及其以上版本, Android 4.3.x
ABI: armeabi-v7a
2.查看详细日志
将日志级别调至Debug

ALog.setLevel(ALog.LEVEL_DEBUG);

版权信息

librtmp

Copyright (C) 2005-2008 Team XBMC
     http://www.xbmc.org
     Copyright (C) 2008-2009 Andrej Stepanchuk
     Copyright (C) 2009-2010 Howard Chu

 librtmp is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as
 published by the Free Software Foundation; either version 2.1,
 or (at your option) any later version.

 librtmp is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with librtmp see the file COPYING.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA  02110-1301, USA.
 http://www.gnu.org/copyleft/lgpl.html

results matching ""

    No results matching ""