You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Swift下DJI无人机照片下载求助:错误码-1007及方案合理性疑问

关于DJI无人机画面获取与下载错误的解决方案

一、先拍照再下载是不是最简单的方式?

其实不是——如果你的需求是获取无人机的最新画面(实时或近实时),直接获取相机的实时预览流(Live View)会更高效,不需要额外拍照和下载,也能避免存储占用和延迟。

具体来说,你可以通过DJICamerastartH264Stream(with:)方法获取H264编码的实时流,然后解码成UIImage;或者用更简便的DJIVideoPreviewer直接显示预览,再从预览中截取画面(如果需要UIImage的话)。这种方式的优势是:

  • 几乎无延迟,实时获取画面
  • 不需要消耗无人机的SD卡存储空间
  • 不需要等待拍照和下载的耗时

当然,如果你的需求是获取已拍摄的高清照片,那拍照后下载是合理的,但如果只是要最新画面,实时流是更优解。

二、错误Code -1007的排查与解决

错误Settings parameters operation failed. (Code : -1007)通常意味着当前相机状态不允许执行你要的操作,常见原因和排查步骤:

  • 相机当前处于忙碌状态:比如正在录像、连拍,或者正在处理之前的拍照请求。切换到mediaDownload模式前,确保相机处于空闲状态(可以通过camera?.mode检查当前模式)。
  • SD卡异常:SD卡未插入、损坏,或者正在被写入数据。可以通过camera?.mediaManager?.sdCardState检查SD卡状态,确保是ready
  • 设备连接不稳定:无人机与遥控器、遥控器与手机的连接中断或信号弱,导致指令无法正常传递。检查连接状态,确保信号正常。
  • 权限问题:部分DJI机型需要在DJI Fly/App中开启相关权限,或者你的App没有正确申请DJI SDK的权限(比如存储权限)。

三、你的下载代码的问题与优化

先看代码里的几个明显问题:

  1. 多余的错误判断:在setModeelse块里,你又写了if error != nil,但这里error已经是nil(因为进入了else分支),这个判断完全无效。
  2. 不必要的延迟DispatchQueue.main.asyncAfter(deadline: .now() + 1)完全没必要,反而可能导致文件列表还没刷新完成就开始下载,引发错误。
  3. 分块数据未拼接file.fetchData(withOffset:)是分块返回数据的,你现在只把每次的data直接转成UIImage,这会导致图片不完整(因为每次返回的是部分数据),需要把所有分块数据拼接完成后再转UIImage。
  4. 循环下载所有JPEG:你要的是最新画面,应该只下载最新的那张照片,而不是所有JPEG,这样更高效。

优化后的下载代码示例

@IBAction func downloadLatestPhoto(_ sender: UIButton) {
    guard let camera = self.fetchCamera() else {
        self.status.text = "Failed to get camera instance"
        return
    }
    
    // 先检查相机当前状态,确保可以切换到mediaDownload模式
    if camera.mode != .mediaDownload {
        camera.setMode(.mediaDownload, withCompletion: { [weak self] error in
            guard let self = self else { return }
            if let error = error {
                self.status.text = "Failed to switch mode: \(error.localizedDescription)"
                return
            }
            self.refreshAndDownloadLatestPhoto(camera: camera)
        })
    } else {
        self.refreshAndDownloadLatestPhoto(camera: camera)
    }
}

private func refreshAndDownloadLatestPhoto(camera: DJICamera) {
    guard let mediaManager = camera.mediaManager else {
        self.status.text = "Media manager not available"
        return
    }
    
    // 先刷新SD卡文件列表
    mediaManager.refreshFileList(completion: { [weak self] error in
        guard let self = self else { return }
        if let error = error {
            self.status.text = "Failed to refresh file list: \(error.localizedDescription)"
            return
        }
        
        // 获取SD卡中的所有文件,按创建时间倒序,取最新的JPEG
        guard let allFiles = mediaManager.sdCardFileListSnapshot() else {
            self.status.text = "No files on SD card"
            return
        }
        
        let jpegFiles = allFiles.filter { $0.mediaType == .JPEG }
            .sorted { $0.timeCreated > $1.timeCreated }
        
        guard let latestPhoto = jpegFiles.first else {
            self.status.text = "No JPEG photos found"
            return
        }
        
        self.status.text = "Downloading latest photo (created at \(latestPhoto.timeCreated))"
        
        // 拼接分块数据
        var downloadedData = Data()
        latestPhoto.fetchData(withOffset: 0, update: DispatchQueue.main) { data, isComplete, error in
            if let error = error {
                self.status.text = "Download failed: \(error.localizedDescription)"
                return
            }
            
            if let data = data {
                downloadedData.append(data)
            }
            
            if isComplete {
                // 数据下载完成,转成UIImage
                if let image = UIImage(data: downloadedData) {
                    self.status.text = "Latest photo downloaded successfully!"
                    // 这里可以处理你的image,比如显示或存储
                    self.handleDownloadedImage(image)
                } else {
                    self.status.text = "Failed to convert data to UIImage"
                }
            }
        }
    })
}

private func handleDownloadedImage(_ image: UIImage) {
    // 在这里添加你的图片处理逻辑,比如显示到UIImageView
}

四、实时获取画面的简化方案(替代拍照下载)

如果你的需求是获取最新的实时画面,推荐用DJIVideoPreviewer来实现,示例代码如下:

import DJISDK

class ViewController: UIViewController {
    var videoPreviewer: DJIVideoPreviewer!
    var camera: DJICamera?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 初始化视频预览器
        videoPreviewer = DJIVideoPreviewer.instance()
        videoPreviewer.setView(self.view) // 设置预览显示的View
        videoPreviewer.start()
        
        // 获取相机实例
        camera = self.fetchCamera()
        camera?.setVideoFeedCallback { [weak self] videoData in
            // 将视频数据传递给预览器
            self?.videoPreviewer.push(videoData)
        }
        
        // 如果需要截取预览画面为UIImage
        // 可以在需要的时候调用:
        // let snapshot = videoPreviewer.snapshot()
        // if let image = snapshot {
        //     // 处理截图
        // }
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        videoPreviewer.stop()
    }
}

这种方式可以实时显示无人机画面,并且随时截取UIImage,比拍照下载高效得多。

内容的提问来源于stack exchange,提问作者Sam

火山引擎 最新活动