Android端集成大疆SDK(MSDK)

Android端集成大疆MSDK

  • 大疆无人机SDK集成项目
    • 1. 无人机接入准备
    • 2. 编译环境准备
    • 3. 关键代码
    • 4. 项目地址

大疆无人机SDK集成项目

本项目主要是集成大疆SDK,并通过大疆API来获取无人机实时传递回来的h264数据,以及通过编解码API拿到YUV数据;
该项目里面封装了一些基础的API调用,方便后续集成调用。
 1. 无人机接入准备
 2. 编译环境准备
 3. 关键代码
 4. 项目地址

1. 无人机接入准备

 1. 本项目测试机型为大疆的御pro 2,遥控器为不带屏遥控器。(带屏遥控器是无法使用大疆SDK的)
 2. 集成使用要点:
	* 需先用测试手机下载大疆专用APP (DJI GO 4)软件进行无人机与遥控器之间的对频操作,此时应该注意,每次将手机和无人机进行USB连接时,会弹出默认启动应用弹框,应选择仅次一次;不然后续装上自己的应用后,连上无人机是无法拉起我们自己的应用的。
	* 当连接成功,可以正常起飞后,此时在手机端装入自己的应用,启动并执行 DroneModel中的 startRegisterSDK 方法,执行注册大疆SDK。
	* 注册成功后,会弹出登录弹框,提示登录大疆账号,此时应填入自己的大疆账号(国内每三个月登录一次,国外不受此限制)
	* 接口 setDjiProductStateCallBack 是回调产品数据,当存在产品时,会执行onAirPlaneStateChange(bool)方法,传入参数为true
	* 接口 setDroneH264VideoDataCallBack 回调,来获取无人机回传的H264数据,获取H264数据
	* 接口 DroneYUVDataCallBack回调,来获取无人机回传的数据,通过CodeManager解码后获取YUV数据
	* 方法 videoPreview方法,传入surfaceView,来执行无人机摄像头数据预览
	* 方法 setAirPlaneCameraParam方法,来设置无人机摄像头参数,具体参数请查看DJICameraParams类,其中有详细的说明
	* DroneModel是单例方法,直接通过getInstance拿到单例对象即可
	* FileUtils工具类提供了写入H264流到文件,写出的文件可以直接通过暴风影音来进行播放,查看H264是否正常

2. 编译环境准备

1. 当前编译环境:Android studio 3.2  gradle版本为:3.2.1  gradle-wrapper版本为:4.6
2. 大疆SDK导入 :在当前项目build.gradle 下导入
	1. build.gradle 配置
	   android {
		   defaultConfig {
			   ……
			   ndk {
				   // 这里只做了armeabi-v7a架构的支持,大疆SDK不支持armeabi架构
				    abiFilters 'armeabi-v7a'
			   }
		   }
		  dexOptions {
		    javaMaxHeapSize "4g"
		   }
		   
		  packagingOptions {
			doNotStrip "*/*/libdjivideo.so"
			doNotStrip "*/*/libSDKRelativeJNI.so"
			doNotStrip "*/*/libFlyForbid.so"
			doNotStrip "*/*/libduml_vision_bokeh.so"
			doNotStrip "*/*/libyuv2.so"
			doNotStrip "*/*/libGroudStation.so"
			doNotStrip "*/*/libFRCorkscrew.so"
			doNotStrip "*/*/libUpgradeVerify.so"
			doNotStrip "*/*/libFR.so"
			doNotStrip "*/*/libDJIFlySafeCore.so"
			doNotStrip "*/*/libdjifs_jni.so"
			doNotStrip "*/*/libsfjni.so"
			exclude 'META-INF/rxjava.properties'
		  }
		  dependencies {
			implementation 'com.android.support:multidex:1.0.2'
			implementation('com.dji:dji-sdk:4.9', {
			//if your app needn't support Mavic 2 Pro and Mavic 2 Zoom, pls exclude the library-anti-distortion module
			//like following
			//exclude module: 'library-anti-distortion'

			//Uncomment the following line if your APP does not need network RTK.
			//For Phantom 4 RTK and Matrice 210 RTK V2. Network RTK can be used as a virtual reference station to provide
			//cm level accurate position.
			//exclude module: 'library-anti-distortion'
			})
			compileOnly 'com.dji:dji-sdk-provided:4.9'
		  }
	   }
	2. AndroidManifest配置
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		
		//必须加入这两个feature
		
		
		// application中需要填入apikey,这个key主要用于注册大疆SDK,需要向大疆开发者官网进行申请key,根据对应的包名申请
		
			  
		
		// 对应接入大疆遥控器时,需要拉起的页面,这个页面主要旨在监听USB,所以需要配置过滤器以及meta-data
		
			
                
			
            
		
	

3. 关键代码

----------------------------------------------------------------------------------------------------


/**
 * @author 李文烙
 * @date 2019/10/26
 * @Desc 核心Application类,这里要执行加载大疆SDK
 */
public class MainApplication extends Application {

    private static BaseProduct djiProduct;

    @Override
    public void onCreate() {
        super.onCreate();
    }

    public static synchronized BaseProduct getProductInstance() {
        if (null == djiProduct) {
            djiProduct = DJISDKManager.getInstance().getProduct();
        }
        return djiProduct;
    }

    public static synchronized void updateProduct(BaseProduct product) {
        djiProduct = product;
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        com.secneo.sdk.Helper.install(MainApplication.this);
    }
}

----------------------------------------------------------------------------------------------------

/**
 * @author 李文烙
 * @date 2019/10/26
 * @Desc 无人机Api调用模块类
 */
public class DroneModel {

    private static final String TAG = "DJIModel";
    private Context context;
    private long lastUpdate;
    private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
    private Camera airPlaneCamera;
    private DJIProductStateCallBack djiProductStateCallBack;
    private VideoFeeder.VideoDataListener receviedVideoDataListener = null;
    private DroneH264VideoDataCallBack droneH264VideoDataCallBack;
    private DroneYUVDataCallBack droneYUVDataCallBack;
    private static DroneModel djiInstance;
    private SurfaceHolder.Callback surfaceCallback;
    private DJICodecManager codecManager;

    private DroneModel(Context context) {
        this.context = context;
    }

    /**
     * 单例模式获取实例
     *
     * @param context 上下文
     * @return 单例模式
     */
    public static DroneModel getInstance(Context context) {
        if (djiInstance == null) {
            synchronized (DroneModel.class) {
                if (djiInstance == null) {
                    djiInstance = new DroneModel(context);
                }
            }
        }
        return djiInstance;
    }

    public void setDjiProductStateCallBack(DJIProductStateCallBack djiProductStateCallBack) {
        this.djiProductStateCallBack = djiProductStateCallBack;
    }

    public void setDjiVideoDataCallBack(DroneH264VideoDataCallBack djiVideoDataCallBack) {
        this.droneH264VideoDataCallBack = djiVideoDataCallBack;
    }

    public void setDroneYUVDataCallBack(DroneYUVDataCallBack droneYUVDataCallBack) {
        this.droneYUVDataCallBack = droneYUVDataCallBack;
    }

    /**
     * 设置无人机摄像头参数
     *
     * @param djiCameraParams 参数列表,仅支持分辨率、帧率等调节,具体请查看实现类
     * @param callback        设置回调
     */
    public void setAirPlaneCameraParam(DJICameraParams djiCameraParams, CommonCallbacks.CompletionCallback callback) {
        final BaseProduct product = MainApplication.getProductInstance();
        if (airPlaneCamera == null && product == null && product.getCamera() == null) {
            return;
        }
        airPlaneCamera.setVideoResolutionAndFrameRate(djiCameraParams.getResolutionAndFrameRate(), callback);
        airPlaneCamera.setVideoStandard(djiCameraParams.getVideoStandard(), callback);
        airPlaneCamera.setFocusMode(djiCameraParams.getFocusMode(), callback);
        airPlaneCamera.setMode(djiCameraParams.getCameraMode(), callback);
        airPlaneCamera.setOrientation(djiCameraParams.getCameraOrientation(), callback);
    }

    /**
     * 预览视频
     *
     * @param surfaceView 需要预览的视图
     */
    public void videoPreview(SurfaceView surfaceView) {
        if (codecManager != null) {
            codecManager.cleanSurface();
            codecManager.destroyCodec();
            codecManager = null;
        }
        SurfaceHolder surfaceHolder = surfaceView.getHolder();
        surfaceCallback = new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                Log.d(TAG, "real onSurfaceTextureAvailable");
                if (codecManager == null) {
                    codecManager = new DJICodecManager(context, holder, surfaceView.getWidth(),
                            surfaceView.getHeight());
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (codecManager != null) {
                    codecManager.cleanSurface();
                    codecManager.destroyCodec();
                    codecManager = null;
                }
                if (airPlaneCamera != null && VideoFeeder.getInstance().provideTranscodedVideoFeed() != null) {
                    VideoFeeder.getInstance().provideTranscodedVideoFeed().removeVideoDataListener(receviedVideoDataListener);
                }
            }
        };
        surfaceHolder.addCallback(surfaceCallback);
    }
	
	/**
	 * 设置是否接收YUV数据,YUV数据是通过大疆编解码后拿到的数据,所以需要调用codecManager来打开相应的开关
	 * 这里要注意的是,必须先执行videoPreview进行预览后,才能调用YUV数据,不然codecManger未初始化
	 * @param {Object} boolean isEnable
	 */
    public void setReceviedYuvData(boolean isEnable) {
        if (codecManager != null) {
            if (isEnable) {
                codecManager.enabledYuvData(true);
                codecManager.setYuvDataCallback((final ByteBuffer yuvFrame, int dataSize, final int width, final
                int height) -> {
                    if (droneYUVDataCallBack != null) {
                        droneYUVDataCallBack.onReceviedYUVData(yuvFrame, dataSize, width, height);
                    }
                });
            } else {
                codecManager.enabledYuvData(false);
                codecManager.setYuvDataCallback(null);
            }
        }
    }


    /**
     * 注册大疆SDK,如果不执行注册,则无法使用大疆的任何接口数据
     */
    public void startSDKRegistration() {
        if (isRegistrationInProgress.compareAndSet(false, true)) {
            AsyncTask.execute(() ->
                    DJISDKManager.getInstance().registerApp(context, new DJISDKManager.SDKManagerCallback() {
                        @Override
                        public void onRegister(DJIError djiError) {
                            if (djiError == DJISDKError.REGISTRATION_SUCCESS) {
                                Logger.error(TAG, DJISDKError.REGISTRATION_SUCCESS.getDescription());
                                DJISDKManager.getInstance().startConnectionToProduct();
                                HandlerUtils.postToMain(() -> loginDJIUserAccount());
                                Logger.info(TAG, "Register SDK Success");
                            } else {
                                Logger.info(TAG, "Register sdk fails, check network is available");
                            }
                            Logger.info(TAG, djiError.getDescription());
                            isRegistrationInProgress.set(false);
                        }

                        @Override
                        public void onProductDisconnect() {
                            Logger.info(TAG, "Product Disconnected");
                            notifyStatusChange(false);
                        }

                        @Override
                        public void onProductConnect(BaseProduct baseProduct) {
                            notifyStatusChange(true);
                            isRegistrationInProgress.set(false);
                            if (baseProduct != null) {
                                MainApplication.updateProduct(baseProduct);
                            }
                        }

                        @Override
                        public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
                                                      BaseComponent newComponent) {
                            if (newComponent != null && oldComponent == null) {
                                Logger.info(TAG, componentKey.name() + ",index:" + newComponent.getIndex());
                            }
                            if (newComponent != null) {
                                newComponent.setComponentListener(isConnect -> {
                                    Logger.info(TAG, " Component " + (isConnect ? "connected" : "disconnected"));
                                    notifyStatusChange(isConnect);
                                });
                            }
                        }
                    }));
        }
    }

    /**
     * 登录大疆账号,国内必须执行登录账号后,方可使用无人机,每3个月登录一次
     */
    private void loginDJIUserAccount() {
        UserAccountManager.getInstance().logIntoDJIUserAccount(context, new CommonCallbacks
                .CompletionCallbackWith() {
            @Override
            public void onSuccess(final UserAccountState userAccountState) {
                Logger.info(TAG, "login success! Account state is:" + userAccountState.name());
            }

            @Override
            public void onFailure(DJIError error) {
                Logger.info(TAG, "login Fail! Error info:" + error.getDescription());
            }
        });

    }


    /**
     * 状态变更方法,根据无人机不同的产品状态来回调给使用者
     *
     * @param isConnect 是否连接
     */
    private void notifyStatusChange(Boolean isConnect) {
        if (djiProductStateCallBack != null) {
            djiProductStateCallBack.onAirPlaneStateChange(isConnect);
        }
        registerDJIVideoDataListener();
        if (isConnect) {
            if (VideoFeeder.getInstance().getPrimaryVideoFeed() != null) {
                VideoFeeder.getInstance().provideTranscodedVideoFeed().addVideoDataListener(receviedVideoDataListener);
            }
        }
    }

    /**
     * 注册大疆无人机视频数据监听事件
     */
    public void registerDJIVideoDataListener() {
        final BaseProduct product = MainApplication.getProductInstance();
        receviedVideoDataListener = (videoBuffer, size) -> {
            Log.d(TAG, "camera recv video data size: " + size);
            if (droneH264VideoDataCallBack != null) {
                droneH264VideoDataCallBack.onReceviedVideoData(videoBuffer, size);
            }
        };
        if (null == product || !product.isConnected()) {
            airPlaneCamera = null;
        } else {
            if (!product.getModel().equals(Model.UNKNOWN_AIRCRAFT)) {
                airPlaneCamera = product.getCamera();
                airPlaneCamera.setMode(SettingsDefinitions.CameraMode.RECORD_VIDEO, djiError -> {
                    if (djiError != null) {
                        Logger.info(TAG, "can't change mode of cam era, error:" + djiError.getDescription());
                    }
                });
                airPlaneCamera.setVideoResolutionAndFrameRate(new ResolutionAndFrameRate(
                        SettingsDefinitions.VideoResolution.RESOLUTION_1920x1080, SettingsDefinitions.VideoFrameRate.FRAME_RATE_30_FPS), new CommonCallbacks.CompletionCallback() {
                    @Override
                    public void onResult(DJIError djiError) {
                        if (djiError != null) {
                            Logger.info(TAG, "can't change mode of cam era, error:" + djiError.getDescription());
                        }
                    }
                });
				
				/**
				 * 这里面需要调用该接口才能正常拿到H264流的回调
				 */
                VideoFeeder.getInstance().provideTranscodedVideoFeed().addVideoDataListener(receviedVideoDataListener);
                //VideoFeeder.getInstance().getPrimaryVideoFeed().addVideoDataListener(receviedVideoDataListener);
            }
        }
    }

    /**
     * 关闭时,应该反注销下视频流的获取
     */
    public void unRegisterDJIVideoDataListener() {
        if (VideoFeeder.getInstance().provideTranscodedVideoFeed() != null) {
            VideoFeeder.getInstance().provideTranscodedVideoFeed().removeVideoDataListener(receviedVideoDataListener);
        }
    }

    /**
     * 大疆产品状态回调
     */
    public interface DJIProductStateCallBack {
        void onAirPlaneStateChange(Boolean isConnect);
    }

    /**
     * 大疆无人机H264视频数据回调
     */
    public interface DroneH264VideoDataCallBack {
        void onReceviedVideoData(byte[] videoBuffer, int size);
    }

    public interface DroneYUVDataCallBack {
        void onReceviedYUVData(ByteBuffer yuvFrame, int dataSize, int width, int height);
    }
}

4. 项目地址

本项目地址(https://github.com/919232176/DroneMSDK),有需要的同学可以根据实际情况去更改哈

你可能感兴趣的:(Android端集成大疆SDK(MSDK))