package cn.tee3.avd;
/**
 * Created by bob.liu on 2015/8/24.
 */

import android.Manifest;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjection;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.Nullable;

import cn.tee3.avd.Device.DeviceStatus;
import tee3.webrtc.Camera1Capturer;
import tee3.webrtc.Camera1Enumerator;
import tee3.webrtc.Camera2Capturer;
import tee3.webrtc.Camera2Enumerator;
import tee3.webrtc.CameraEnumerator;
import tee3.webrtc.CameraVideoCapturer;
import tee3.webrtc.EglBase;
import tee3.webrtc.Logging;
import tee3.webrtc.NativeCapturerObserver;
import tee3.webrtc.ScaleScreenCapturer;
import tee3.webrtc.ScreenCapturerAndroid;
import tee3.webrtc.SurfaceTextureHelper;
import tee3.webrtc.UsbCameraCapturerAndroid;
import tee3.webrtc.VideoCapturer;
import tee3.webrtc.VideoSource;

/**
 * 房间中视频模块接口.
 *
 * 房间视频模块定义房间中视频发布、订阅功能接口。
 */
public class MVideo extends Module {
    private static final String TAG = "MVideo";
    private static final int FRONT = android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
    private static final int BACK = android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
    private static List<android.hardware.Camera.CameraInfo> cameraInfoList = new ArrayList<>();
    private static Intent screenIntent;
    private static Room room;
    private VideoRenderer videoRenderer;
    private static boolean enableTexture = true;
    private static boolean useCamera2API = true;
    private static String cameraName;

    private static HandlerThread thread;
    private static Handler handler;
    private static CameraVideoCapturer.CameraEventsHandler cameraEventListener;
    private static boolean isUsbCameraCaptureNeedStart;

    private static LocalVideoSource usbCameraSource;
    private static List<VideoCapturer> capturerList = new ArrayList<>();
    private static List<LocalVideoSource> cameraSourceList = new ArrayList<>(); // 本地摄像头 source
    private static List<LocalVideoSource> videoSourceList = new ArrayList<>();  // 共享屏幕、USB摄像头 source
    static boolean test = true;
    /**
     * 摄像头类型.
     */
    public static enum CameraType{
        unknow,                             /**< 未知类型  */
        front,                              /**< 前置摄像头  */
        back,                               /**< 后置摄像头  */
    }
    /**
     * 视频采集信息结构.
     *
     * 视频采集结构定义了视频采集相关参数，包含视频分辨率和帧率。
     */
    public static class CameraCapability{
        private int width = 320;            /**< 视频宽度  */
        private int height = 240;           /**< 视频高度  */
        private int maxFPS = 10;            /**< 视频帧率  */

        public CameraCapability(int width, int height, int maxFPS) {
            this.width = width;
            this.height = height;
            this.maxFPS = maxFPS;
        }
        public int getWidth() { return this.width; }
        public void setWidth(int width) { this.width = width; }
        public int getHeight() { return this.height; }
        public void setHeight(int height) { this.height = height; }
        public int getMaxFPS() { return this.maxFPS; }
        public void setMaxFPS(int maxFPS) { this.maxFPS = maxFPS; }

        @Override
        public boolean equals(Object obj) {
            CameraCapability objd = (CameraCapability) obj;
            if (null == objd) {
                return false;
            }
            return objd.getWidth() == this.getWidth()
                    && objd.getHeight() == this.getHeight()
                    && objd.getMaxFPS() == this.getMaxFPS();
        }

        @Override
        public String toString() {
            return "Capability:w=" + this.width + ",h=" + this.height + ",fps=" + this.maxFPS;
        }
    }

    /**
     * 视频摄像头信息结构.
     *
     * 视频摄像头结构定义了摄像头相关参数，主要是摄像头描述信息和视频分辨率信息。
     */
    public static class Camera extends VideoDevice {
        private CameraType type;/**< 摄像头类型  */
        protected Camera(String id, String name, int level, String description, DeviceStatus status, int deviceQualities, int publishedQualities, int type)  {
            super(id, name, level, description, status, deviceQualities,publishedQualities);
            CameraType[] values = CameraType.values();
            for (CameraType cameraType : values) {
                if (cameraType.ordinal() == type) {
                    this.type = cameraType;
                }
            }
        }

        public Camera(String id, String name) {
            super(id, name, 0, "", DeviceStatus.ready, 0, 0);
        }

        public CameraType getCameraType() {
            return this.type;
        }

        public void setType(CameraType type) {
            this.type = type;
        }

        @Override
        public String toString() {
            return "Camera:" + super.toString() + ",type=" + type;
        }
    }

    /**
     * 房间中视频模块回调接口.
     *
     * 视频模块回调接口定义了房间中视频相关的通知和视频异步操作返回。
     */
    public static interface Listener {
        /// 通知
        /** 摄像头状态更改通知
         *
         * @param[in] status 摄像头状态。
         * @param[in] fromId 摄像头Id，唯一标示一个摄像头。
         * @note 当摄像头状态更改后，房间内所有用户接收到此通知， notify when publishLocalCamera,publishLocalAssist,publishRemoteCamera。
         */
        public void onCameraStatusNotify(DeviceStatus status, String fromId);
        /** 摄像头数据更改通知
         *
         * @param[in] level 摄像头整形数据。
         * @param[in] description 摄像头字符串数据。
         * @param[in] fromId 摄像头Id，唯一标示一个摄像头。
         * @note 当摄像头数据更改后，房间内所有用户接收到此通知, notify when publishLocalCamera, updateCameraData。
         */
        public void onCameraDataNotify(int level, String description, String fromId);

        /** 摄像头视频发布通知
         *
         * @param[in] camera 摄像头信息，摄像头信息中level和description为应用层数据，应用层可以使用这些字段保存次摄像头视频相关的应用逻辑数据，方便标示、订阅视频。
         *
         * @note 当摄像头视频发布时，房间内所有用户接收到此通知；因通知的重要程度，将从onCameraStatusNotify中分离出此状态。
         * @sa publishLocalCamera
         * @sa publishLocalAssist
         */
        public void onPublishCameraNotify(Camera camera);
        /** 摄像头视频取消发布通知
         *
         * @param[in] camera 摄像头信息。
         *
         * @note 当摄像头视频取消发布时，房间内所有用户接收到此通知；因通知的重要程度，将从onCameraStatusNotify中分离出此状态。
         * @sa unpublishLocalCamera
         * @sa unpublishLocalAssist
         */
        public void onUnpublishCameraNotify(Camera camera);
        /// 异步返回
        /** 本用户订阅视频异步返回
         *
         * @param[in] result 错误代码。
         * @param[in] fromId 摄像头Id，唯一标示一路视频。
         *
         * @sa subscribe
         */
        public void onSubscribeResult(int result, String fromId);
        /** 本用户取消订阅视频异步返回
         *
         * @param[in] result 错误代码。
         * @param[in] fromId 摄像头Id，唯一标示一路视频。
         *
         * @sa unsubscribe
         */
        public void onUnsubscribeResult(int result, String fromId);
        /** 本用户发布摄像头视频异步返回
         *
         * @param[in] result 错误代码。
         * @param[in] fromId 摄像头Id，唯一标示一路视频。
         *
         * @sa publishLocalCamera
         * @sa publishLocalAssist
         */
        public void onPublishLocalResult(int result, String fromId);
        /** 本用户取消发布摄像头视频异步返回
         *
         * @param[in] result 错误代码。
         * @param[in] fromId 摄像头Id，唯一标示一路视频。
         *
         * @sa unpublishLocalCamera
         * @sa unpublishLocalAssist
         */
        public void onUnpublishLocalResult(int result, String fromId);
    }

    /**
     * 房间中视频模块回调接口.
     *
     * 视频模块回调接口定义了房间中视频相关的通知和视频异步操作返回。
     */
    public interface IInnerListener {
        void onChangeCaptureFormate(String deviceId, int width, int height, int framerate);
    }

    public class InnerListener implements IInnerListener {
        @Override
        public void onChangeCaptureFormate(String deviceId, int width, int height, int framerate) {
            Tlog.i(TAG, "onChangeCaptureFormat(), deviceId=" + deviceId + "width=" + width + ", height=" + height + ", framerate=" + framerate + ", participants=" + (getUserManager().getParticipantsCount() + 1));
            for (LocalVideoSource videoSource : cameraSourceList) {
                if (videoSource.capturer instanceof Camera1Capturer || videoSource.capturer instanceof Camera2Capturer) {
                    if (videoSource.capability.getWidth() == width && videoSource.capability.getHeight() == height) {
                        Tlog.i(TAG, "onChangeCaptureFormat(), duplicate resolution, return.");
                        return;
                    }
                }
            }

            if (getUserManager().getParticipantsCount() > 0) {
                for (LocalVideoSource videoSource : cameraSourceList) {
                    if (videoSource.camera != null && videoSource.camera.getId().equals(deviceId)) {
                        videoSource.capability.setWidth(width);
                        videoSource.capability.setHeight(height);
                        videoSource.capability.setMaxFPS(framerate);
                        videoSource.changeCaptureFormat(width, height, framerate);
                        Tlog.i(TAG, "onChangeCaptureFormat(), done.");
                    }
                }
            }
        }
    }

    private long nativeListener = 0;
    @Nullable private Listener listener4cb = null;
    @Nullable private Handler listenerHandler = null;
    @Nullable private SDKListener listener4native = null;
    @Nullable private IInnerListener innerListener = null;
    ///
    /** 获取房间内视频模块接口
     *
     * @param[in] room 房间对象指针。
     *
     * @return 返回视频模块接口指针。
     */
    @Nullable
    public static MVideo getVideo(Room room) {
        MVideo.room = room;
        return (MVideo) room.getModule(Type.video);
    }
    protected MVideo(Room room) {
        super(room, Type.video, room.nativegetMVideo());
        Tlog.d(TAG, "MVideo, nativeVideo:" + this.nativeobj);
        initNativeListener();
        thread = new HandlerThread("MVideo");
        thread.start();
        handler = new Handler(thread.getLooper());
    }
    @Override
    protected void dispose() {
        if (0 != this.nativeListener) {
            nativeFreeListener(this.nativeListener);
        }
        for (LocalVideoSource videoSource : cameraSourceList) {
            videoSource.dispose();
        }
        for (LocalVideoSource videoSource : videoSourceList) {
            videoSource.dispose();
        }

        room = null;
        capturerList.clear();
        cameraSourceList.clear();
        videoSourceList.clear();
        usbCameraSource = null;
        this.nativeListener = 0;
        this.nativeobj = 0;
        handler.getLooper().quit();
    }

    /** 设置房间聊天模块回调接口
     *
     * @param[in] listener 聊天模块回调接口指针。
     * @return 返回错误代码。
     * @sa Listener
     */
    public boolean setListener(Listener listener) {
        Tlog.d(TAG, "setListener, listener:" + listener + ",listener4native=" + this.listener4native + ",listenerHandler=" + this.listenerHandler);
        this.listener4cb = listener;
        if (null != listener) {
            initNativeListener();
        }
        Tlog.v(TAG, "setListener, out:");
        return true;
    }

    /**
     * 设置摄像头事件监听
     * @param listener 监回调接口
     */
    public static void setCameraEventListener(CameraVideoCapturer.CameraEventsHandler listener) {
        cameraEventListener = listener;
    }

    private void initNativeListener(){
        if (0 != this.nativeListener) {
            return;
        }
        initHandler();
        innerListener = new InnerListener();
        this.nativeListener = nativeCreateListener(listener4native, innerListener);
        Tlog.d(TAG, "initNativeListener, Create nativelistener:" + nativeListener);
    }
    /** 启用或者停止视频自动旋转
     *
     * @param[in] enable 启用或停止。
     * @note 视频自动旋转：当转动手机时，共享的本地视频会调整视频角度，永远以正向视频分享。
     */
    static public void setAutoRotation(boolean enable) {
        Tlog.i(TAG, "setAutoRotation:" + enable);
        //VideoCaptureAndroid.setAutoRotation(enable);
    }
    static public boolean isAutoRotation() {
        return true;
        //return VideoCaptureAndroid.getAutoRotation();
    }
    /// 前后置摄像头操作模式：每一时刻只能打开前后或者后置摄像头中的一路，可以做前后切换。		*************************
    /** 预览本机前置或者后置摄像头视频
     *
     * @param[in] type 摄像头类型。
     * @param[in] render 显示对象接口指针
     * @note 在前后置摄像头操作模式下有效；摄像头模式设置在AVDEngine的SetOption接口中实现；取消预览接口为unpreviewLocalCamera，无需参数。
     * @return 返回错误代码；若不支持此操作返回 Err_Not_Available。
     * @sa VideoRenderer
     * @sa CameraType
     */
    public int previewLocalCamera(CameraType type, VideoRenderer render) {
        Tlog.i(TAG, "previewLocalCamera,type:" + type);
        if (null == type || null == render) {
            Tlog.w(TAG, "previewLocalCamera, params is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        if (null == type) {
            Tlog.w(TAG, "publishLocalCamera, type is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        if (AVDEngine.instance().getContext() == null || !AVDEngine.instance().isWorking()) {
            return ErrorCode.Err_Not_Initialized;
        }
        int permission = ContextCompat.checkSelfPermission(AVDEngine.instance().getContext(), Manifest.permission.CAMERA);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            return ErrorCode.Err_NO_PERMISSION;
        }
        videoRenderer = render;
        render.viewRenderer.enableMeasure(true);
        render.viewRenderer.getEglRenderer().invalidDrawFrame(false);
        render.viewRenderer.clearImage();
        render.setMirror(type == CameraType.front);

        Camera cam = getCamera(type);
        if (!cam.valid()) {
            cam = getCamera(CameraType.back);
        }
        LocalVideoSource localsource = null;
        if (cameraSourceList.size() == 0) {
            localsource = new LocalVideoSource(true);
        } else {
            localsource = cameraSourceList.get(0);
        }
        if (!localsource.init(cam)) {
            Tlog.w(TAG, "previewLocalCamera, video capturer and source create failed.");
            return ErrorCode.Err_Failure;
        }
        int ret = nativepreviewLocalCamera4(localsource.currCamera(), localsource.nativeSource(), render.nativeRender());
        if (ErrorCode.AVD_OK == ret) {
            render.setVideoId(cam.getId());
            setCurrentVideoId4camSwitch(true);
            localsource.startVideoSource();
        }
        Tlog.i(TAG, "previewLocalCamera, out: ret=" + ret);
        return ret;
    }

    /** 取消预览缺省摄像头视频
     * @return 返回错误代码。
     */
    public int unpreviewLocalCamera() {
        if (videoRenderer == null) {
            Tlog.w(TAG, "unpreviewLocalCamera, not preview yet!");
            return ErrorCode.Err_Not_Initialized;
        }
        Tlog.d(TAG, "unpreviewLocalCamera,");
        videoRenderer.viewRenderer.enableMeasure(false);
        videoRenderer.viewRenderer.getEglRenderer().invalidDrawFrame(true);
        videoRenderer.viewRenderer.clearImage();
        videoRenderer.setVideoId(null);
        if (cameraSourceList.size() > 0) {
            cameraSourceList.get(0).stopVideoSource();
            cameraSourceList.get(0).dispose();
            cameraSourceList.remove(0);
        }
        return nativeunpreviewLocalCamera1();
    }

    /** 发布特定类型摄像头视频
     *
     * @param[in] type 摄像头类型。
     * @return 返回错误代码；若不支持此操作返回 Err_Not_Available。
     * @note 发布操作为异步操作，操作结果在 onPublishLocalResult 中返回。在前后置摄像头操作模式下有效；取消预览接口为unpublishLocalCamera，无需参数。
     * @sa onPublishLocalResult
     */
    public int publishLocalCamera(CameraType type) {
        Tlog.i(TAG, "publishLocalCamera,type:" + type);
        if (null == type) {
            Tlog.w(TAG, "publishLocalCamera, type is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        if (AVDEngine.instance().getContext() == null || !AVDEngine.instance().isWorking()) {
            return ErrorCode.Err_Not_Initialized;
        }
        int permission = ContextCompat.checkSelfPermission(AVDEngine.instance().getContext(), Manifest.permission.CAMERA);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            return ErrorCode.Err_NO_PERMISSION;
        }

        LocalVideoSource localsource;
        if (cameraSourceList.size() == 0) {
            localsource = new LocalVideoSource(true);
        } else {
            Tlog.w(TAG, "publishLocalCamera, duplicate publish local camera, return!");
            return ErrorCode.Err_Wrong_Status;
        }
        Camera cam = getCamera(type);
        if (!cam.valid()) {
            cam = getCamera(CameraType.back);
        }
        if (!localsource.init(cam)) {
            Tlog.w(TAG, "publishLocalCamera, video capturer and source create failed.");
            return ErrorCode.Err_Failure;
        }
        int ret = nativepublishLocalCamera4(localsource.currCamera(), localsource.nativeSource());
        if (ErrorCode.AVD_OK == ret) {
            setCurrentVideoId4camSwitch(true);
            localsource.startVideoSource();
        }
        Tlog.i(TAG, "publishLocalCamera, out: ret=" + ret);
        return ret;
    }

    /** 发布摄像头视频
     *
     * @param[in] camera 摄像头信息。
     * @return 返回错误代码。
     * @note 发布操作为异步操作，操作结果在 onPublishLocalResult 中返回。
     * @sa onPublishLocalResult
     */
    public int publishLocalCamera(Camera camera) {
        Tlog.i(TAG, "publishLocalCamera, camera:" + camera);
        if (camera == null || TextUtils.isEmpty(camera.getId())) {
            Tlog.w(TAG, "publishLocalCamera, camera is null or id is empty.");
            return ErrorCode.Err_Invalid_Arg;
        }
        if (AVDEngine.instance().getContext() == null || !AVDEngine.instance().isWorking()) {
            return ErrorCode.Err_Not_Initialized;
        }
        if (isVideoSourceCreated(camera)) {
            Tlog.w(TAG, "publishLocalCamera, video source has created, return now!");
            return ErrorCode.Err_Wrong_Status;
        }

        int permission = ContextCompat.checkSelfPermission(AVDEngine.instance().getContext(), Manifest.permission.CAMERA);
        if (permission != PackageManager.PERMISSION_GRANTED) {
            return ErrorCode.Err_NO_PERMISSION;
        }
        LocalVideoSource localSource = new LocalVideoSource(true);
        if (!localSource.init(camera)) {
            Tlog.w(TAG, "publishLocalCamera, video capturer and source create failed.");
            return ErrorCode.Err_Failure;
        }
        int ret = nativepublishLocalCamera4(localSource.currCamera(), localSource.nativeSource());
        if (ErrorCode.AVD_OK == ret) {
            setCurrentVideoId4camSwitch(true);
            localSource.startVideoSource();
        }
        Tlog.i(TAG, "publishLocalCamera, out: ret=" + ret);
        return ret;
    }

    /** 发布模拟摄像头视频
     *
     * @param[in] fakeDevice 模拟摄像头信息。
     * @param[in] *capturer 模拟摄像头视频输入接口。
     * @return 返回错误代码。
     * @note 发布操作为异步操作，操作结果在 onPublishLocalResult 中返回。
     * 模拟摄像头用于在房间中导入特定已经存在的视频数据流，比如视频文件、录播系统视频流等。
     * 接口需要先生成deviceId,请调用createFakeDeviceID生成
     * 注意：此函数必须用 unpublishLocalCamera(String deviceId)来取消发布，不能用无参数函数；
     * @sa onPublishLocalResult
     * @sa FakeVideoCapturer
     * @sa createFakeDeviceID
     */
    public int publishLocalCamera(MVideo.Camera camera, FakeVideoCapturer capturer) {
        Tlog.i(TAG, "publishLocalCamera,fakeDevice:" + camera + ",capturer=" + capturer);
        if (null == camera || null == capturer) {
            Tlog.w(TAG, "publishLocalCamera, fakeDevice or capturer is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        int ret = nativepublishLocalCamera3(camera, capturer.getNativeobj());
        Tlog.i(TAG, "publishLocalCamera, out:");
        return ret;
    }

    public int publishUsbCamera() {
        Tlog.d(TAG, "publishUsbCamera,type:");
        Camera camera = new Camera(getUserManager().getSelfUserId() + "_UsbCamera", "UsbCamera");
        if (!camera.valid()) {
            return -1;
        }
        if (null == usbCameraSource) {
            usbCameraSource = new LocalVideoSource(false);
        }
        if (!usbCameraSource.initUsbCamera(camera)) {
            Tlog.w(TAG, "publishUsbCamera, video capturer and source create failed.");
            return ErrorCode.Err_Failure;
        }

        int ret = nativepublishLocalCamera4(usbCameraSource.currCamera(), usbCameraSource.nativeSource());
        if (ret == ErrorCode.AVD_OK) {
            isUsbCameraCaptureNeedStart = false;
            usbCameraSource.startVideoSource();
        }
        Tlog.d(TAG, "publishUsbCamera, out: ret=" + ErrorCode.AVD_OK);
        return ret;
    }

    private boolean isVideoSourceCreated(Camera camera) {
        if (camera == null) {
            return false;
        }
        for (LocalVideoSource videoSource : cameraSourceList) {
            if (videoSource.camera != null && videoSource.camera.getId().equals(camera.getId())) {
                return true;
            }
        }
        return false;
    }

    /** 设置摄像头原始数据监听 */
    public void setPreviewCallback(NativeCapturerObserver.PreviewCallback callback) {
        if (cameraSourceList.size() > 0) {
            cameraSourceList.get(0).getSource().setPreviewCallback(callback);
        }
    }

    private void setCurrentVideoId4camSwitch(boolean setcurr) {
        LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
        if (null != camSwitch && camSwitch.isSingleCameraMode()) {
            camSwitch.setCurrentVideoId(setcurr ? getRealLocalVideoId() : null);
        }
    }

    /** 判断本地摄像头是否已经打开
     * @return 返回摄像头是否打开，只在前后摄像头模式使用。
     */
    public boolean ispublishedLocalCamera() {
        List<Camera> cameras = getPublishedCameras();
        if (cameras != null && cameras.size() > 0) {
            for (Camera item : cameras) {
                if (this.isSelfDevice(item.getId()) && item.getCameraType() != CameraType.unknow) {
                    return true;
                }
            }
        }
        return false;
    }

    /** 判断Usb摄像头是否已打开
     * @return 返回摄像头是否打开，只在前后摄像头模式使用。
     */
    public boolean isPublishedUsbCamera() {
        if (usbCameraSource != null) {
            return isCameraPublished(usbCameraSource.camera.getId());
        }
        return false;
    }

    /** 判断本地摄像头是否已经打开
     * @return 返回摄像头是否打开，只在前后摄像头模式使用。
     */
    public int switchToLocalCamera() {
        if (!ispublishedLocalCamera()) {
            return ErrorCode.Err_Wrong_Status;
        }
        String option = AVDEngine.instance().getOption(AVDEngine.Option.eo_camera_mode_frontback);
        Logging.d(TAG, "createCameraCapturer -> eo_camera_mode_frontback: " + option);
        if (!Boolean.parseBoolean(option)) {
            return ErrorCode.Err_Wrong_Status;
        }
        CameraType curType = getCurrentCameraType();
        if (CameraType.front == curType) {
            curType = CameraType.back;
        }
        else {
            curType = CameraType.front;
        }
        return switchToLocalCamera(curType);
    }
    private String getRealLocalVideoId() {
        CameraType curtype = nativegetCurrentCameraType();
        String deviceId = nativegetLocalCameraIdByType(curtype.ordinal());
        return deviceId;
    }
    /** 切换特定类型摄像头视频源
     *
     * @param[in] toType 摄像头类型。
     * @note 在前后置摄像头操作模式下有效。
     * @return 返回错误代码；若不支持此操作返回 Err_Not_Available。
     */
    public int switchToLocalCamera(CameraType toType) {
        Tlog.d(TAG, "switchLocalCamera, toType:" + toType);
        if (cameraSourceList.size() == 0) {
            Tlog.w(TAG, "switchToLocalCamera, camera is not opened yet.");
            return ErrorCode.Err_Wrong_Status;
        }
        if (null == toType) {
            Tlog.w(TAG, "switchToLocalCamera, toType is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        int ret = nativeswitchToLocalCamera1(toType.ordinal());
        cameraSourceList.get(0).switchCamera();
        VideoRenderer render = null;
        LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
        if (null != camSwitch) {
            render = camSwitch.getLocalVideoRender();
        }
        if (null != render) {
            render.setMirror(toType == CameraType.front);
            String vid = getCurrentCameraId();
            setCurrentVideoId4camSwitch(true);
            render.setVideoId(vid);
        }
        Tlog.v(TAG, "switchToLocalCamera, out");
        return ret;
    }
    /** 获取当前使用的摄像头类型
     *
     * @note 在前后置摄像头操作模式下有效。
     * @return 返回摄像头类型。
     */
    public CameraType getCurrentCameraType() {
        return nativegetCurrentCameraType();
    }
    /** 获取当前使用的摄像头Id
     *
     * @note 在前后置摄像头操作模式下有效。
     * @return 返回摄像头类型。
     */
    public String getCurrentCameraId() {
        return nativegetCurrentCameraId();
    }
    /** 取消发布缺省摄像头视频
     *
     * @return 返回错误代码。
     * @note 取消发布操作为异步操作，操作结果在 onUnpublishLocalResult 中返回。
     * @sa onUnpublishLocalResult
     */
    public int unpublishLocalCamera() {
        Tlog.i(TAG, " unpublishLocalCamera");
        if (cameraSourceList.size() == 0) {
            Tlog.w(TAG, "unpublishLocalCamera, video source list is empty.");
            return -1;
        }
        int ret = nativeunpublishLocalCamera1();
        Tlog.v(TAG, "unpublishLocalCamera, native out");
        for (LocalVideoSource videoSource : cameraSourceList) {
            Tlog.v(TAG, "unpublishLocalCamera, stopVideoSource: " + videoSource.hashCode());
            videoSource.stopVideoSource();
            videoSource.dispose();
        }
        cameraSourceList.clear();
        Tlog.i(TAG, "unpublishLocalCamera, out");
        return ret;
    }

    /** 取消发布USB摄像头
     *
     * @return 返回错误代码。
     * @note 取消发布操作为异步操作，操作结果在 onUnpublishLocalResult 中返回。
     * @sa onUnpublishLocalResult
     */
    public int unpublishUsbCamera() {
        if (null == usbCameraSource){
            return -1;
        }
        int ret = nativeunpublishLocalCamera2(usbCameraSource.currCamera().getId());
        Tlog.d(TAG, " unpublishUsbCamera: ret=" + ret);
        usbCameraSource.stopVideoSource();
        usbCameraSource.dispose();
        videoSourceList.remove(usbCameraSource);
        usbCameraSource = null;
        return ret;
    }

    /// 多摄像头操作模式：能同时打开多个摄像头，不区分前置与后置摄像头。						*************************
    /** 预览本地某一摄像头视频
     *
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @param[in] render 显示对象接口指针
     * @return 返回错误代码。
     * @sa VideoRenderer
     */
    public int previewLocalCamera(String deviceId, VideoRenderer render) {
        if (null == render) {
            Tlog.w(TAG, "previewLocalCamera, render is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        int ret = nativepreviewLocalCamera2(deviceId, (null == render) ? 0 : render.nativeRender());
        if (ErrorCode.AVD_OK == ret && null != render) {
            render.setVideoId(deviceId);
        }
        return ret;
    }

    /** 取消预览摄像头视频
     * @param[in] deviceId 摄像头Id。
     * @return 返回错误代码。
     */
//    public int unpreviewLocalCamera(String deviceId) {
//
//        if (videoRenderer == null) {
//            Tlog.w(TAG, "unpreviewLocalCamera, not preview yet!");
//            return ErrorCode.Err_Not_Initialized;
//        }
//        Tlog.d(TAG, "unpreviewLocalCamera,");
//        videoRenderer.setVideoId(null);
//        localsource.stopVideoSource();
//        localsource.dispose();
//        return nativeunpreviewLocalCamera2(videoRenderer.getVideoId());
////        return nativeunpreviewLocalCamera1();
//    }

    /** 取消发布摄像头视频
     *
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @return 返回错误代码。
     * @note 取消发布操作为异步操作，操作结果在 onUnpublishLocalResult 中返回。
     * @sa onUnpublishLocalResult
     */
    public int unpublishLocalCamera(String deviceId) {
        Tlog.i(TAG, "unpublishLocalCamera, deviceId=" + deviceId);
        if (cameraSourceList.size() == 0) {
            Tlog.w(TAG, "unpublishLocalCamera, video source list is empty.");
            return -1;
        }
        if (TextUtils.isEmpty(deviceId)) {
            Tlog.w(TAG, "unpublishLocalCamera, deviceId is empty.");
            return ErrorCode.Err_Invalid_Arg;
        }

        Iterator<LocalVideoSource> iterator = cameraSourceList.iterator();
        while (iterator.hasNext()) {
            LocalVideoSource videoSource = iterator.next();
            if (videoSource.camera != null && deviceId.equals(videoSource.camera.getId())) {
                Tlog.v(TAG, "unpublishLocalCamera, stopVideoSource: " + videoSource.hashCode());
                videoSource.stopVideoSource();
                videoSource.dispose();
                iterator.remove();
            }
        }

        setCurrentVideoId4camSwitch(false);
        int ret = nativeunpublishLocalCamera2(deviceId);
        Tlog.i(TAG, "unpublishLocalCamera, out");
        return ret;
    }

    /**
     * 多摄像头模式（eo_camera_mode_frontback），停止所有已发布的摄像头
     */
    public void unpublishMultipleLocalCameras() {
        Tlog.v(TAG, "unpublishMultipleLocalCameras");
        if (cameraSourceList.size() == 0) {
            Tlog.w(TAG, "unpublishMultipleLocalCameras, no local cameras has published!");
            return;
        }
        for (LocalVideoSource videoSource : cameraSourceList) {
            nativeunpublishLocalCamera2(videoSource.camera.getId());
            videoSource.stopVideoSource();
            videoSource.dispose();
        }
        cameraSourceList.clear();
        Tlog.v(TAG, "unpublishMultipleLocalCameras, out");
    }

    /** 切换视频源
     *
     * @param[in] fromId 原摄像头Id。
     * @param[in] toId 切换后摄像头Id。
     * @return 返回错误代码。
     */
    public int switchLocalCamera(String fromId, String toId) {
        return nativeswitchLocalCamera2(fromId, toId);
    }
    /** 摄像头数据更改
     *
     * @param[in] deviceId 摄像头Id，唯一标示一个摄像头。
     * @param[in] level 摄像头整形数据。
     * @param[in] description 摄像头字符串数据。
     * @sa onCameraDataNotify
     */
    public int updateCameraData(String deviceId, int level, String description) {
        Tlog.d(TAG, " updateCameraData: deviceId = " + deviceId + ", level = " + level + ", description = " + description);
        return nativeupdateCameraData(deviceId, level, description);
    }
    /** 已经published或preview的视频暂停：视频卡住，且也不发送到网络上
     *
     * @param[in] deviceId 摄像头设备ID。
     * @return 返回错误代码。
     */
    public int muteLocalCamera(String deviceId) {
        int ret = nativemuteLocalCamera(deviceId);
        Tlog.v(TAG, "muteLocalCamera, deviceId=" + deviceId + ",ret=" + ret);
        return ret;
    }
    /** 取消视频暂停
     *
     * @param[in] deviceId 摄像头设备ID。
     * @return 返回错误代码。
     * @note muteLocalCamera
     */
    public int unmuteLocalCamera(String deviceId) {
        int ret = nativeunmuteLocalCamera(deviceId);
        Tlog.v(TAG, "unmuteLocalCamera, deviceId=" + deviceId + ",ret=" + ret);
        return ret;
    }
    /// subscribe and publish list
    /** 订阅摄像头视频
     *
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @return 返回错误代码。
     * @note 订阅操作为异步操作，操作结果在 onSubscribeResult 中返回。
     * @sa onSubscribeResult
     */
    public int subscribe(String deviceId) {
        Tlog.d(TAG, "subscribe, deviceId:" + deviceId);
        int ret = nativesubscribe(deviceId);
        Tlog.v(TAG, "subscribe, out");
        return ret;
    }
    /** 订阅摄像头视频指定视频质量视频
     *
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @param[in] stream 指定视频流（主流，辅流1，辅流2）。
     * @note 订阅说明如同名接口所描述。
     * @sa VideoOptions.StreamType
     */
    public int subscribeWithStreamType(String deviceId, VideoOptions.StreamType stream) {
        Tlog.d(TAG, "subscribeWithStreamType, deviceId:" + deviceId + ",stream=" + stream);
        int ret = nativesubscribeWithStreamType(deviceId, stream.ordinal());
        Tlog.v(TAG, "subscribe, out");
        return ret;
    }

    /**
     * 订阅摄像头视频指定视频质量视频
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @param[in] quality 订阅视频的清晰度要求。
     * @note 订阅说明如同名接口所描述。
     * 3.0版本后支持视频清晰度切换：发布视频时设置视频最大分辨率，通过此接口和changeSubscribedVideoQuality接口可以在整个
     * 房间层级来更改订阅的视频的清晰度，但要求房间中所有人清晰度要求一致才生效（通过远端分辨率更改实现）。
     * 如最大分辨率为 quality_high - 1280x720; 则：quality_normal - 640x360(high/2); quality_low - 320x240(normal/2)
     * @sa VideoQuality
     * @sa changeSubscribedVideoQuality
     */
    public int subscribeWithVideoQuality(String deviceId, VideoOptions.VideoQuality quality) {
        Tlog.d(TAG, "subscribeWithVideoQuality, deviceId:" + deviceId + ",quality=" + quality);
        int ret = nativesubscribeWithVideoQuality(deviceId, VideoOptions.VideoQuality2native(quality));
        Tlog.v(TAG, "subscribe, out");
        return ret;
    }

    /**
     * 更改订阅视频的视频质量
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @param[in] quality 视频质量。
     * @sa VideoQuality
     */
    public int changeSubscribedVideoQuality(String deviceId, VideoOptions.VideoQuality quality) {
        Tlog.d(TAG, "changeSubscribedVideoQuality, deviceId:" + deviceId + ",quality=" + quality);
        int ret = nativechangeSubscribedVideoQuality(deviceId, VideoOptions.VideoQuality2native(quality));
        return ret;
    }

    /**
     * 取消订阅摄像头视频
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id，唯一标示一路摄像头视频。
     * @note 取消订阅操作为异步操作，操作结果在 onUnsubscribeResult 中返回。
     * @sa onUnsubscribeResult
     */
    public int unsubscribe(String deviceId) {
        Tlog.d(TAG, "unsubscribe, deviceId:" + deviceId);
        int ret = nativeunsubscribe(deviceId);
        Tlog.v(TAG, "unsubscribe, out");
        return ret;
    }

    /**
     * 获取房间中已发布视频摄像头列表
     *
     * @return 返回摄像头设备列表(包含自己)
     * @note 摄像头列表包含本地发布的摄像头和房间中其他用户的所有正在发布的摄像头，摄像头视频要在界面上显示，远端摄像头需要首先做subcribe；
     * 而本地摄像头显示时不需要subscribe，应用层开发时需要注意。
     */
    public List<Camera> getPublishedCameras() {
        List<Camera> items = nativegetPublishedCameras();
        Tlog.v(TAG, "getPublishedCameras, count:" + (null == items ? 0 : items.size()));
        return items == null ? new ArrayList<>() : items;
    }

    /**
     * 判断摄像头是否已经发布到房间中
     */
    public boolean isCameraPublished(String deviceId) {
        return nativeisCameraPublished(deviceId);
    }

    public boolean isCameraPublished(CameraType type) {
        if (null == type) {
            Tlog.w(TAG, "isCameraPublished, type is null.");
            return false;
        }
        String deviceId = nativegetLocalCameraIdByType(type.ordinal());
        return nativeisCameraPublished(deviceId);
    }

    /**
     * 获取本用户订阅的摄像头视频列表
     *
     * @return 返回摄像头设备列表。
     */
    public List<Camera> getSubscribedCameras() {
        List<Camera> items = nativegetSubscribedCameras();
        Tlog.v(TAG, "getSubscribedCameras, count:" + (null == items ? 0 : items.size()));
        return items == null ? new ArrayList<>() : items;
    }

    /**
     * 判断摄像头是否已经被本用户订阅
     */
    public boolean isCameraSubscribed(String deviceId) {
        return nativeisCameraSubscribed(deviceId);
    }

    /**
     * 获取机摄像头设备列表
     *
     * @return 返回摄像头设备列表。
     */
    public List<Camera> getLocalCameras() {
        return nativegetLocalCameras();
    }

    /**
     * 获取指定类型的本机摄像头信息
     *
     * @return 返回摄像头设备信息。
     * @param[in] type 摄像头类型。
     * @sa CameraType
     */
    @Nullable
    public Camera getCamera(CameraType type) {
        if (null == type) {
            Tlog.w(TAG, "getCamera, type is null.");
            return null;
        }
        String deviceId = nativegetLocalCameraIdByType(type.ordinal());
        return nativegetCamera(deviceId);
    }

    /**
     * 获取指定设备Id的摄像头信息
     *
     * @return 返回摄像头信息。
     * @param[in] deviceId 设备Id。
     * @note 可以返回本地用户和远端用户的摄像头信息。
     */
    public Camera getCamera(String deviceId) {
        return nativegetCamera(deviceId);
    }

    /**
     * 获取指定设备Id的摄像头采集参数列表
     *
     * @return 返回采集参数列表。
     * @param[in] deviceId 设备Id。
     * @sa CapabilitiesType
     */
    public List<CameraCapability> getCameraCapability(String deviceId) {
        return nativegetCameraCapabilities(deviceId);
    }

    @Nullable
    public List<CameraCapability> getCameraCapabilities(CameraType type) {
        if (null == type) {
            Tlog.w(TAG, "getCameraCapabilities, type is null.");
            return null;
        }
        Tlog.d(TAG, "getCameraCapabilities, type:" + type);
        String deviceId = nativegetLocalCameraIdByType(type.ordinal());
        return nativegetCameraCapabilities(deviceId);
    }

    /**
     * 设置指定设备Id的摄像头采集参数当前选项
     *
     * @return 返回错误代码。
     * @param[in] deviceId 设备Id。
     * @param[in] cap 当前采集参数。
     */
    public int setCaptureCapability(String deviceId, CameraCapability cap) {
        return nativesetCaptureCapability(deviceId, cap);
    }

    public int setCaptureCapability(CameraType type, CameraCapability cap) {
        if (null == type) {
            Tlog.w(TAG, "setCaptureCapability, type is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.d(TAG, "setCaptureCapability, type:" + type + ",cap:" + cap);
        String deviceId = nativegetLocalCameraIdByType(type.ordinal());
        return nativesetCaptureCapability(deviceId, cap);
    }
    /// remote camera control

    /**
     * 命令远端用户发布某一摄像头视频
     *
     * @return 返回错误代码。
     * @param[in] camera 摄像头对象。
     */
    public int remotecmdPublishCamera(Camera camera) {
        if (null == camera) {
            Tlog.w(TAG, "remotecmdPublishCamera, camera is null.");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.d(TAG, "publishRemoteCamera, deviceId:" + camera.getId());
        return nativeremotecmdPublishCamera(camera);
    }

    /**
     * 命令远端用户取消发布摄像头视频
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id。
     */
    public int remotecmdUnpublishCamera(String deviceId) {
        Tlog.d(TAG, "unpublishRemoteCamera, deviceId:" + deviceId);
        return nativeremotecmdUnpublishCamera(deviceId);
    }

    public static class LocalVideoSource {
        private static final String VIDEO_CAPTURER_THREAD_NAME = "VideoCapturerThread";
        private EglBase localEglbase;
        Camera camera;
        private String hardwareEncodeType;
        @Nullable
        protected VideoCapturer capturer;
        protected long native_capturer;
        @Nullable
        private CameraCapability capability;
        private boolean videoCapturerStopped = true;
        @Nullable
        private VideoSource source;

        public LocalVideoSource(boolean isCamera) {
            localEglbase = EglBase.create(AVDEngine.instance().getEglBaseContext());
            if (isCamera) {
                cameraSourceList.add(this);
            } else {
                videoSourceList.add(this);
            }
        }

        public void dispose() {
            uninit();
            if (localEglbase != null) {
                localEglbase.release();
                localEglbase = null;
            }
        }

        public void removeVideoSource(LocalVideoSource videoSource) {
            if (videoSource != null) {
                videoSourceList.remove(videoSource);
                cameraSourceList.remove(videoSource);
            }
        }

        public boolean init(Camera cam) {//发布本地视频
            camera = cam;
            if (null != capturer) {
                return true;
            }

            int width = 720, height = 1280, maxFPS = 20;

            try {
                JSONObject jsonObject = new JSONObject(AVDEngine.instance().getOption(AVDEngine.Option.eo_camera_capability_default));
                width = (int) jsonObject.get("width");
                height = (int) jsonObject.get("height");
                maxFPS = (int) jsonObject.get("maxFPS");
            } catch (JSONException e) {
                e.printStackTrace();
            }

            capability = new CameraCapability(width, height, maxFPS);
            capturer = createVideoCapturer();
            capturerList.add(capturer);
            if (null == capturer) {
                Tlog.i(TAG, "init create video capturer failed.");
                return false;
            }
            if (hardwareEncodeType != null) {
                AVDEngine.instance().setOption(AVDEngine.Option.eo_video_codec_hw_encode, hardwareEncodeType);
                hardwareEncodeType = null;
            }
            source = createVideoSource(capturer);
            if (source != null) {
                source.adaptOutputFormat(width, height, maxFPS);
            }
            Tlog.i(TAG, "init capturer:" + capturer.toString() + " source:" + source.toString());
            return source != null;
        }

        public boolean initScreen(int width, int height, int fps) {//屏幕共享
            if (null != capturer) {
                return true;
            }
            capability = new CameraCapability(width, height, fps);
            capturer = createScreenCapturer();
            capturerList.add(capturer);
            if (null == capturer) {
                Tlog.i(TAG, "init create screen capturer failed.");
                return false;
            }
            source = createVideoSource(capturer);
            if (source != null) {
                source.adaptOutputFormat(width, height, fps);
            }
            Tlog.i(TAG, "init capturer:" + capturer.toString() + " source:" + source.toString());
            return source != null;
        }

        public boolean initScaleScreen(ScaleScreenCapturer.ScaleZone scaleZone) {
            capability = new CameraCapability(scaleZone.getWidth(), scaleZone.getHeight(), 20);
            capturer = createScaleScreenCapturer();
            capturerList.add(capturer);
            if (null == capturer) {
                Tlog.i(TAG, "init create screen capturer failed.");
                return false;
            }
            source = createVideoSource(capturer);
            if (source != null) {
                source.adaptOutputFormat(scaleZone.getWidth(), scaleZone.getHeight(), 20);
            }
            Tlog.i(TAG, "init capturer:" + capturer.toString() + " source:" + source.toString());
            return source != null;
        }

        public boolean initUsbCamera(Camera cam) {//屏幕共享
            camera = cam;
            if (null != capturer  && capturer instanceof UsbCameraCapturerAndroid) {
                return true;
            }
            capability = new CameraCapability(1280, 720, 20);
            capturer = createUsbCameraCapturer();
            if (null == capturer) {
                Tlog.i(TAG, "init create screen capturer failed.");
                return false;
            }
            source = createVideoSource(capturer);
            if (source != null) {
                source.adaptOutputFormat(1280, 720, 20);
            }
            Tlog.i(TAG, "init capturer:" + capturer.toString() + " source:" + source.toString());
            return true;
        }

        public void uninit() {
            if (null != source) {
                stop();
                source.dispose();
                source = null;
            }
            if (null != capturer) {
                capturerList.remove(capturer);
                capturer.dispose();
                capturer = null;
            }
            native_capturer = 0;
            Tlog.i(TAG, "uninit ");
        }

        public Camera currCamera() {
            return camera;
        }

        public long nativeSource() {
            return native_capturer;
        }

        public VideoSource getSource() {
            return source;
        }

        protected VideoSource createVideoSource(VideoCapturer capturer) {
            final EglBase.Context eglContext =
                    localEglbase == null ? null : localEglbase.getEglBaseContext();
            final SurfaceTextureHelper surfaceTextureHelper =
                    SurfaceTextureHelper.create(VIDEO_CAPTURER_THREAD_NAME, eglContext);
            if (surfaceTextureHelper == null) {
                Tlog.e(TAG, "SurfaceTextureHelper.create is null!");
                return null;
            }
            native_capturer = nativeCreateVideoSource(capturer.isScreencast());
            final VideoSource videoSource = new VideoSource(
                    native_capturer, surfaceTextureHelper);
            capturer.initialize(surfaceTextureHelper, AVDEngine.instance().getContext(),
                    videoSource.getCapturerObserver());
            return videoSource;
        }

        public void stop() {
            String capturerType = capturer == null ? null : capturer.getClass().getSimpleName();
            Tlog.d(TAG, "Stop video source: " + capturerType);
            try {
                if (capturer != null && !videoCapturerStopped) {
                    capturer.stopCapture();
                    videoCapturerStopped = true;
                }
            } catch (InterruptedException e) {
                Tlog.e(TAG, "Stop video source exception:" + e.toString());
            }
        }

        public void stopVideoSource() {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    stop();
                }
            });
        }

        public void start() {
            if (capturer == null || !videoCapturerStopped) {
                return;
            }

            if (capturer instanceof ScaleScreenCapturer) {
                boolean isScreenCapturerInit = false;
                VideoCapturer screenCapturer = null;
                for (VideoCapturer videoCapturer : capturerList) {
                    if (videoCapturer instanceof ScreenCapturerAndroid) {
                        isScreenCapturerInit = true;
                        screenCapturer = videoCapturer;
                        break;
                    }
                }
                if (!isScreenCapturerInit) {
                    return;
                }
                ((ScreenCapturerAndroid)screenCapturer).setTextureFrameAvailableListener((ScreenCapturerAndroid.TextureFrameAvailable) capturer);
            }
            Tlog.d(TAG, "Start video source, capture: " + capturer.getClass().getSimpleName());
            capturer.startCapture(capability.width, capability.height, capability.getMaxFPS());
            videoCapturerStopped = false;
        }

        public void startVideoSource() {
            handler.post(new Runnable() {
                 @Override
                 public void run() {
                    start();
                 }
            });
        }

        // 创建屏幕共享
        @TargetApi(Build.VERSION_CODES.LOLLIPOP)
        protected VideoCapturer createScreenCapturer() {
            return new ScreenCapturerAndroid(screenIntent
                    , new MediaProjection.Callback() {
                @Override
                public void onStop() {
                    Log.e(TAG, "onStop: 屏幕共享错误" );
                }
            });
        }

        // 创建屏幕共享
        protected VideoCapturer createScaleScreenCapturer() {
            return new ScaleScreenCapturer();
        }

        protected VideoCapturer createUsbCameraCapturer() {
            return UsbCameraCapturerAndroid.getInstance().addUsbCameraListener(usbCameraEvent);
        }

        protected VideoCapturer createVideoCapturer() {
            VideoCapturer videoCapturer;
            if (MVideo.useCamera2API && MVideo.enableTexture && Camera2Enumerator.isSupported(AVDEngine.instance().getContext())) {
                Logging.d(TAG, "Creating capturer using camera2 API.");
                videoCapturer = createCameraCapturer(new Camera2Enumerator(AVDEngine.instance().getContext()));
            } else {
                Logging.d(TAG, "Creating capturer using camera1 API.");
                videoCapturer = createCameraCapturer(new Camera1Enumerator(MVideo.enableTexture));
                if (videoCapturer == null && MVideo.enableTexture) {
                    videoCapturer = createCameraCapturer(new Camera1Enumerator(false));
                }
            }
            if (videoCapturer == null) {
                Tlog.e(TAG, "Failed to open camera");
                return null;
            }
            return videoCapturer;
        }

        private VideoCapturer createCameraCapturer(CameraEnumerator enumerator) {
            String option = AVDEngine.instance().getOption(AVDEngine.Option.eo_camera_mode_frontback);
            Logging.d(TAG, "createCameraCapturer -> eo_camera_mode_frontback: " + option);
            if (Boolean.parseBoolean(option)) {
                if (!TextUtils.isEmpty(cameraName)) {
                    return enumerator.createCapturer(cameraName, cameraEventListener);
                }
                final String[] deviceNames = enumerator.getDeviceNames();
                Logging.d(TAG, "createCameraCapturer -> targetCameraType = " + camera.getCameraType());

                for (String deviceName : deviceNames) {
                    Logging.d(TAG, "enumerator.isFrontFacing(deviceName) ? " + enumerator.isFrontFacing(deviceName) + ", enumerator.isBackFacing(deviceName) ? " + enumerator.isBackFacing(deviceName));
                    if (camera.getCameraType() == CameraType.front && !enumerator.isFrontFacing(deviceName)) {
                        continue;
                    } else if (camera.getCameraType() == CameraType.back && !enumerator.isBackFacing(deviceName)) {
                        continue;
                    }
                    Logging.e(TAG, "Creating " + camera.getCameraType().name() + " facing camera capturer.");
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, cameraEventListener);

                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }

                for (String deviceName : deviceNames) {
                    Logging.d(TAG, "Creating " + deviceName + " capturer, facing " + (enumerator.isFrontFacing(deviceName) ? "front" : "back"));
                    VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, cameraEventListener);

                    if (videoCapturer != null) {
                        return videoCapturer;
                    }
                }

                return null;
            } else {
                LocalVideoSource videoSource = cameraSourceList.get(cameraSourceList.size() - 1);
                String cameraName = videoSource.camera.getName();
                Logging.d(TAG, "createCameraCapturer -> camera: " + cameraName);

                if (enumerator.getDeviceNames().length > 0 && enumerator.getDeviceNames()[0].contains("Camera")) {
                    return enumerator.createCapturer(cameraName, cameraEventListener);
                } else {
                    int index = cameraName.indexOf(",");
                    String subCameraName = cameraName.substring(index - 1, index);
                    return enumerator.createCapturer(subCameraName, cameraEventListener);
                }
            }
        }

        /** 修改分辨率 */
        private void changeCaptureFormat(int width, int height, int framerate) {
            if (null != source){
                Log.d(TAG, "adaptOutputFormat: " + width + "x" + height + "@" + framerate);
                source.adaptOutputFormat(width, height, framerate);
            }
            if (null != capturer) {
                Log.d(TAG, "changeCaptureFormat: " + width + "x" + height + "@" + framerate);
                capturer.changeCaptureFormat(width, height, framerate);
                if (changeCaptureFormatListener != null) {
                    changeCaptureFormatListener.onChangeCaptureFormat();
                }
            }
        }

        public void switchCamera() {
            if (capturer instanceof Camera1Capturer || capturer instanceof Camera2Capturer) {
                Log.d(TAG, "Switch camera");
                CameraVideoCapturer cameraVideoCapturer = (CameraVideoCapturer) capturer;
                cameraVideoCapturer.switchCamera(null);
            } else {
                Log.d(TAG, "Will not switch camera, video caputurer is not a camera");
            }
        }
    }

    public VideoCapturer getCaptureByType(Class inClass) {
        for (VideoCapturer capturer : capturerList) {
            if (inClass == ScreenCapturerAndroid.class && capturer instanceof ScreenCapturerAndroid) {
                return capturer;
            } else if (inClass == UsbCameraCapturerAndroid.class && capturer instanceof UsbCameraCapturerAndroid) {
                return capturer;
            }
        }
        return null;
    }

    /**
     * 调整发布视频分辨率
     * @param width 视频宽
     * @param height 视频高
     * @param rate 视频帧率
     */
    public void changeCaptureFormat(String cameraId, int width, int height, int rate) {
        if (!TextUtils.isEmpty(cameraId) && width > 0 && height > 0 && rate > 0 && rate <= 30) {
            for (LocalVideoSource videoSource : cameraSourceList) {
                if (videoSource.camera != null && videoSource.camera.getId().equals(cameraId)) {
                    videoSource.changeCaptureFormat(width, height, rate);
                    break;
                }
            }
        }
    }

    /** 获取摄像头列表 */
    public String[] getDeviceNames() {
        if (AVDEngine.instance().getContext() == null) {
            return null;
        }
        CameraEnumerator cameraEnumerator;

        if ((MVideo.useCamera2API && MVideo.enableTexture && Camera2Enumerator.isSupported(AVDEngine.instance().getContext()))) {
            cameraEnumerator = new Camera2Enumerator(AVDEngine.instance().getContext());
        } else {
            cameraEnumerator = new Camera1Enumerator(MVideo.enableTexture);
        }
        return cameraEnumerator.getDeviceNames();
    }

    public void configDefaultCamera(String deviceName) {
        if (!TextUtils.isEmpty(deviceName)) {
            this.cameraName = deviceName;
        }
    }

    public void sendScreenIntent (Intent intent){
        screenIntent = intent;
    }

    /// render video
    protected static class LocalCameraSwitch {
        @Nullable
        private String frontLocalVideoId = null;
        @Nullable
        private String currpLocalVideoId = null;
        private boolean issingleCamera = true;
        private List<VideoRenderer> renders = new LinkedList<VideoRenderer>();
        @Nullable
        private MUserManager usermgt;

        public LocalCameraSwitch(Room room) {
            this.usermgt = MUserManager.getUserManager(room);
            String single = AVDEngine.instance().getOption(AVDEngine.Option.eo_camera_mode_frontback);
            this.issingleCamera = single.equalsIgnoreCase("true");
        }

        public void setFrontVideoId(String vid) {
            this.frontLocalVideoId = vid;
        }

        public void setCurrentVideoId(String cid) {
            this.currpLocalVideoId = cid;
        }

        @Nullable
        public String getCurrentVideoId() {
            return this.currpLocalVideoId;
        }

        public boolean isSingleCameraMode() {
            return this.issingleCamera;
        }

        public boolean hasFrontId() {
            return null != frontLocalVideoId;
        }

        public boolean isFrontCamera(String cid) {
            if (null == frontLocalVideoId) {
                return false;
            }
            return frontLocalVideoId.equals(cid) ? true : false;
        }

        //
        public boolean isSelfDevice(String did) {
            return this.usermgt.isSelfDevice(did);
        }

        public void addRender(VideoRenderer render) {
            this.renders.add(render);
        }

        public void removeRender(VideoRenderer render) {
            this.renders.remove(render);
        }

        @Nullable
        public VideoRenderer removeRender(String did) {
            VideoRenderer render = null;
            if (isSelfDevice(did) && null != currpLocalVideoId) {
                render = getRender(currpLocalVideoId);
            } else {
                render = getRender(did);
            }
            if (null != render) {
                this.renders.remove(render);
            }
            return render;
        }

        @Nullable
        public VideoRenderer getRender(String did) {
            if (null == did) {
                return null;
            }
            for (VideoRenderer item : this.renders) {
                if (did.equals(item.getVideoId())) {
                    return item;
                }
            }
            return null;
        }

        @Nullable
        public VideoRenderer getLocalVideoRender() {
            return getRender(currpLocalVideoId);
        }
    }

    private static void getCameraInfos() {
        int cameraCount = android.hardware.Camera.getNumberOfCameras();
        for (int i = 0; i < cameraCount; i++) {
            android.hardware.Camera.CameraInfo cameraInfo = new android.hardware.Camera.CameraInfo();
            android.hardware.Camera.getCameraInfo(i, cameraInfo);
            cameraInfoList.add(cameraInfo);
        }
    }

    private boolean isCameraExist(int facing) {
        if (cameraInfoList.size() == 0) {
            getCameraInfos();
        }
        for (android.hardware.Camera.CameraInfo cameraInfo : cameraInfoList) {
            if (cameraInfo.facing == facing) {
                return true;
            }
        }
        return false;
    }

    /**
     * 关联视频数据和显示对象，在render中显示视频
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id，唯一标示一路共享的摄像头视频。
     * @param[in] render 显示对象接口指针。
     * @note 视频数据可以在多个显示对象上显示，而一个显示对象某一时刻只能显示一路视频。
     * 在本地视频发布到房间中，或者远端视频订阅成功后，调用此接口完成视频与显示对象的对接，从而在显示对象中显示视频。
     * @sa VideoRenderer
     */
    public int attachRender(String deviceId, VideoRenderer render) {
        if (null == render || render.viewRenderer == null) {
            Tlog.e(TAG, "attachRender, invalid render");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.d(TAG, "attachRender, deviceId:" + deviceId + ",render:" + render + ", glView: " + render.viewRenderer);
        if (TextUtils.isEmpty(deviceId)) {
            Tlog.e(TAG, "attachRender, invalid deviceId");
            return ErrorCode.Err_Invalid_Arg;
        }
        int ret = nativeattachRender(deviceId, render.nativeRender());
        if (ErrorCode.AVD_OK == ret) {
            render.setVideoId(deviceId);
            if (this.isSelfDevice(deviceId)) {
                LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
                if (null == camSwitch) {
                    Tlog.w(TAG, "attachRender, camSwitch is null. out");
                    return ret;
                }
                if (isCameraExist(FRONT)) {
                    if (!camSwitch.hasFrontId()) {
                        Camera frontCamera = this.getCamera(CameraType.front);
                        if (null != frontCamera) {
                            camSwitch.setFrontVideoId(frontCamera.getId());
                        }
                    }
                }
                boolean mirror = camSwitch.isFrontCamera(deviceId);
                render.setMirror(mirror);
                camSwitch.addRender(render);
            } else {
                render.setMirror(false);
            }
        }
        render.viewRenderer.enableMeasure(true);
        render.viewRenderer.getEglRenderer().invalidDrawFrame(false);
        render.viewRenderer.clearImage();
        Tlog.v(TAG, "attachRender, out");

        return ret;
    }

    /**
     * 解除摄像头视频数据的所有显示对象关联
     *
     * @return 返回错误代码。
     * @param[in] deviceId 摄像头Id，唯一标示一路共享的摄像头视频。
     */
    public int detachRender(String deviceId) {
        Tlog.d(TAG, "detachRender1, deviceId:" + deviceId);
        LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
        if (null == camSwitch) {
            Tlog.e(TAG, "detachRender1, invalid camSwitch");
            return ErrorCode.Err_Wrong_Status;
        }
        VideoRenderer render1 = camSwitch.getRender(deviceId);
        if (render1 != null) {
            camSwitch.getRender(deviceId).viewRenderer.enableMeasure(false);
            camSwitch.getRender(deviceId).viewRenderer.clearImage();
            camSwitch.getRender(deviceId).viewRenderer.getEglRenderer().invalidDrawFrame(true);
        }
        if (isSelfDevice(deviceId)) {
            nativedetachRender1(deviceId);
        } else {
            boolean hasRender = false;
            VideoRenderer render = camSwitch.removeRender(deviceId);
            while (null != render) {
                hasRender = true;
                render.setVideoId(null);
                render.setMirror(false);
                nativedetachRender2(render.nativeRender());
                // next
                render = camSwitch.removeRender(deviceId);
            }
            if (!hasRender) {
                Tlog.e(TAG, "detachRender1, invalid render");
                return ErrorCode.Err_Not_Found;
            }
        }
        Tlog.v(TAG, "detachRender1, out");
        return 0;
    }

    /**
     * 解除某一显示对象与摄像头视频数据的关联
     *
     * @param[in] render 显示对象接口指针。
     * @sa VideoRenderer
     */
    public int detachRender(VideoRenderer render) {
        if (null == render || render.viewRenderer == null) {
            Tlog.e(TAG, "detachRender, invalid render");
            return ErrorCode.Err_Invalid_Arg;
        }
        Tlog.d(TAG, "detachRender2, render:" + render + ", videoId: " + render.getVideoId());
        if (null == render.viewRenderer){
            return ErrorCode.Err_Invalid_Arg;
        }
        if (null == render.viewRenderer.getEglRenderer()){
            return ErrorCode.Err_Invalid_Arg;
        }
        render.viewRenderer.enableMeasure(false);
        render.viewRenderer.getEglRenderer().invalidDrawFrame(true);
        render.viewRenderer.clearImage();
        render.setVideoId(null);
        render.setMirror(false);
        int ret = nativedetachRender2(render.nativeRender());
        LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
        if (null == camSwitch) {
            Tlog.e(TAG, "detachRender2, invalid camSwitch");
            return ErrorCode.Err_Wrong_Status;
        }
        camSwitch.removeRender(render);
        Tlog.v(TAG, "detachRender2, out");
        return ret;
    }

    /**
     * 获取某用户的缺省摄像头Id
     *
     * @param[in] userId 用户Id。
     */
    public List<Camera> getRemoteCameras(String userId) {
        Tlog.d(TAG, "getCameraIdByUserId, userId:" + userId);
        return nativegetRemoteCameras(userId);
    }

    /**
     * 获取当前使用的android的摄像头对象，每次都要获取，可以返回为null
     *
     * @return 返回摄像头对象。
     */
    @Nullable
    public android.hardware.Camera getAndroidCamera() {
        return null;
        //return VideoCaptureAndroid.getAndroidCamera();
    }

    /**
     * 获取preview和publish摄像头的真实分辨率
     *
     * @return 返回分辨率。
     * @note preview是同步操作，preview后马上可以调用此函数获取； publish是异步操作，需要等onPublishLocalResult中来调用此函数获取
     */
    public CameraCapability getCurrentRealCaptureCapability() {
        CameraCapability cap = nativegetRealCaptureCapability();
        Tlog.d(TAG, "getRealCaptureCapability, cap:" + cap.toString());
        return cap;
    }

    /**
     * 设置视频动态码率调整的范围，最小和最大比特率，
     *
     * @return 返回错误代码。
     * @param[in] deviceId 视频设备对应ID。
     * @param[in] minBitrateBps 最小比特率,单位bps。
     * @param[in] maxBitrateBps 最大比特率,单位bps。
     * @note 例如30k/s = 30*8*1000 bps(bits/s)。当min和max相等时，码率固定，将不会动态调整。
     */
    public int setVideoBitrate(String deviceId, int minBitrateBps, int maxBitrateBps) {
        Tlog.d(TAG, "setVideoBitrate, deviceId=" + deviceId + ",min:" + minBitrateBps + ",max:" + maxBitrateBps);
        return nativesetVideoBitrate(deviceId, minBitrateBps, maxBitrateBps);
    }

    /** 设置相机偏移角度 */
    public void setCameraOrientationOffset(int orientation) {
        Camera1Capturer.setCameraOrientation(orientation);
    }

    public static boolean isEnableTexture() {
        return MVideo.enableTexture;
    }

    public static void enableTexture(boolean enableTexture) {
        MVideo.enableTexture = enableTexture;
    }

    public static boolean isEnableCamera2API() {
        return MVideo.useCamera2API;
    }

    /**
     * 设置是否使用Camera2接口操作摄像头
     * @param enable 是否使用Camera2
     */
    public static void enableCamera2API(boolean enable) {
        MVideo.useCamera2API = enable;
    }

    private static ChangeCaptureFormatListener changeCaptureFormatListener;

    public void setChangeCaptureFormatListener(ChangeCaptureFormatListener listener) {
        changeCaptureFormatListener = listener;
    }

    public interface ChangeCaptureFormatListener {
        void onChangeCaptureFormat();
    }

    private native long nativeCreateListener(Listener listener, IInnerListener innerListener);

    private native void nativeFreeListener(long nativeListener);

    // front and back camera support
    private native int nativepreviewLocalCamera1(int type, long render);

    private native int nativeunpreviewLocalCamera1();

    private native int nativepublishLocalCamera1(int type);

    private native int nativeunpublishLocalCamera1();

    private native int nativeswitchToLocalCamera1(int type);

    private native String nativegetLocalCameraIdByType(int type);

    private native CameraType nativegetCurrentCameraType();

    private native String nativegetCurrentCameraId();

    // multi camera support
    private native int nativepreviewLocalCamera2(String deviceId, long nativerender);

    private native int nativeunpreviewLocalCamera2(String deviceId);

    private native int nativepublishLocalCamera2(Camera device);

    private native int nativeunpublishLocalCamera2(String deviceId);

    private native int nativeswitchLocalCamera2(String fromId, String toId);

    private native boolean nativeisCameraPublished(String deviceId);

    private native boolean nativeisCameraSubscribed(String deviceId);

    //
    private native int nativepreviewLocalCamera3(Camera device, long nativecapturer, long render);

    private native int nativepublishLocalCamera3(Camera device, long nativecapturer);

    private native int nativemuteLocalCamera(String deviceId);

    private native int nativeunmuteLocalCamera(String deviceId);

    private native int nativepreviewLocalCamera4(Camera cam, long nativesource, long render);       /**---------------------------------------------------------------*/

    // camera config
    private native Camera nativegetCamera(String deviceId);

    private native List<Camera> nativegetLocalCameras();

    private native List<CameraCapability> nativegetCameraCapabilities(String deviceId);

    private native int nativesetCaptureCapability(String deviceId, CameraCapability cap);

    // remote camera control
    private native int nativeremotecmdPublishCamera(Camera camera);

    private native int nativeremotecmdUnpublishCamera(String deviceId);

    private native int nativeupdateCameraData(String deviceId, int level, String description);

    private native List<Camera> nativegetRemoteCameras(String userId);

    // subscribe and render video
    private native List<Camera> nativegetPublishedCameras();

    private native List<Camera> nativegetSubscribedCameras();

    private native int nativesubscribe(String deviceId);

    private native int nativesubscribeWithStreamType(String deviceId, int stream);

    private native int nativesubscribeWithVideoQuality(String deviceId, int quality);

    private native int nativechangeSubscribedVideoQuality(String deviceId, int quality);

    private native int nativeunsubscribe(String deviceId);

    private native int nativeattachRender(String deviceId, long nativerender);

    private native int nativedetachRender1(String deviceId);

    private native int nativedetachRender2(long nativerender);

    //
    private native CameraCapability nativegetRealCaptureCapability();

    private native int nativesetVideoBitrate(String deviceId, int minBitrateBps, int maxBitrateBps);

    //
    private static native long nativeCreateVideoSource(boolean is_screencast);

    public native int nativepublishLocalCamera4(Camera cam, long nativesource);

    ///
    private static final int msg_onCameraStatusNotify = 1;
    private static final int msg_onCameraDataNotify = 2;
    private static final int msg_onPublishCameraNotify = 3;
    private static final int msg_onUnpublishCameraNotify = 4;
    private static final int msg_onSubscribeResult = 5;
    private static final int msg_onUnsubscribeResult = 6;
    private static final int msg_onPublishLocalResult = 7;
    private static final int msg_onUnpublishLocalResult = 8;

    private boolean initHandler() {
        if (null == listener4native) {
            listener4native = new SDKListener();
        }
        if (null != listenerHandler) {
            return true;
        }
        AVDEngine.runOnLoopThreadSync(new Runnable() {
            @Override
            public void run() {
                initHandler_main();
            }
        });
        return true;
    }

    private boolean initHandler_main() {
        this.listenerHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                Tlog.d(TAG, "handleMessage, msg:" + msg.toString());
                if (null == listener4cb) {
                    super.handleMessage(msg);
                    return;
                }
                switch (msg.what) {
                    case msg_onCameraStatusNotify:
                        listener4cb.onCameraStatusNotify(DeviceStatus.values()[msg.arg1], (String) msg.obj);
                        break;
                    case msg_onCameraDataNotify:
                        listener4cb.onCameraDataNotify(
                                msg.getData().getInt("level"), msg.getData().getString("description"), msg.getData().getString("fromId"));
                        break;
                    case msg_onPublishCameraNotify:
                        listener4cb.onPublishCameraNotify((Camera) msg.obj);
                        break;
                    case msg_onUnpublishCameraNotify:
                        Camera cam = (Camera) msg.obj;
                        if (null == cam) {
                            break;
                        }
                        //detachRender(cam.getId());
                        if (isSelfDevice(cam.getId())) {
                            LocalCameraSwitch camSwitch = getRoom().getLocalCameraSwith();
                            if (null != camSwitch && camSwitch.isSingleCameraMode()) {
                                String curLocalId = camSwitch.getCurrentVideoId();
                                if (null != curLocalId && !curLocalId.equals(cam.getId())) {
                                    cam = getCamera(curLocalId);
                                }
                                camSwitch.setCurrentVideoId(null);
                            }
                        }
                        if (null != listener4cb) {
                            listener4cb.onUnpublishCameraNotify(cam);
                        }
                        break;
                    case msg_onSubscribeResult:
                        listener4cb.onSubscribeResult(msg.arg1, (String) msg.obj);
                        break;
                    case msg_onUnsubscribeResult:
                        listener4cb.onUnsubscribeResult(msg.arg1, (String) msg.obj);
                        break;
                    case msg_onPublishLocalResult:
                        listener4cb.onPublishLocalResult(msg.arg1, (String) msg.obj);
                        break;
                    case msg_onUnpublishLocalResult:
                        listener4cb.onUnpublishLocalResult(msg.arg1, (String) msg.obj);
                        break;

                }
                super.handleMessage(msg);
            }
        };
        return true;
    }

    private class SDKListener implements Listener {
        public SDKListener() {
        }

        @Override
        public void onCameraStatusNotify(DeviceStatus status, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onCameraStatusNotify, status.ordinal(), 0, fromId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onCameraDataNotify(int level, String description, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = obtainMessage(msg_onCameraDataNotify, level, description, fromId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onPublishCameraNotify(Camera from) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onPublishCameraNotify, from);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onUnpublishCameraNotify(Camera from) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onUnpublishCameraNotify, from);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onSubscribeResult(int result, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onSubscribeResult, result, 0, fromId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onUnsubscribeResult(int result, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onUnsubscribeResult, result, 0, fromId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onPublishLocalResult(int result, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onPublishLocalResult, result, 0, fromId);
            listenerHandler.sendMessage(msg);
        }

        @Override
        public void onUnpublishLocalResult(int result, String fromId) {
            if (null == listenerHandler) {
                return;
            }
            Message msg = Message.obtain(listenerHandler, msg_onUnpublishLocalResult, result, 0, fromId);
            listenerHandler.sendMessage(msg);
        }
    }

    private Message obtainMessage(int what, int level, String description, String fromId) {
        Message msg = Message.obtain(listenerHandler);
        msg.what = what;
        Bundle data = new Bundle();
        data.putInt("level", level);
        data.putString("description", description);
        data.putString("fromId", fromId);
        msg.setData(data);
        return msg;
    }

    private static void usbCameraRelease() {
        if (isUsbCameraCaptureNeedStart) {
            return;
        }
        isUsbCameraCaptureNeedStart = true;
        synchronized (LocalVideoSource.class) {
            if (usbCameraSource != null && MVideo.getVideo(MVideo.room) != null) {
                MVideo.getVideo(MVideo.room).unpublishUsbCamera();
                usbCameraSource.dispose();
                usbCameraSource.removeVideoSource(usbCameraSource);
                usbCameraSource = null;
            }
        }
    }

    private static UsbCameraCapturerAndroid.UsbCameraEvent usbCameraEvent = new UsbCameraCapturerAndroid.UsbCameraEvent() {
        @Override
        public void onAttach() {
        }

        @Override
        public void onDetach() {
            usbCameraRelease();
        }

        @Override
        public void onConnect() {
        }

        @Override
        public void onDisconnect() {
            usbCameraRelease();
        }

        @Override
        public void onCancel() {
            usbCameraRelease();
        }
    };
}
