dji-sdk / Mobile-SDK-Android

DJI Mobile SDK for Android: http://developer.dji.com/mobile-sdk/
Other
994 stars 581 forks source link

Thumbnail, Preview and Media fetch fails unexpectedly #1183

Open dkapur17 opened 2 years ago

dkapur17 commented 2 years ago

On the DJI Mini 2, the calls to fetchPreview, fetchThumbnail and fetchFileByteData often fail, yielding one of the two errors:

  1. Media download result: the server aborts the downloading
  2. Execution of the process resulted in a timeout

Since DJI Mini's firmware doesn't support the setMode method on the camera, I'm using the setFlatMode. My mode changes are as such:

To capture a shot

  1. Exit Playback Mode (if needed)
  2. Set Flat Mode to PHOTO_SINGLE
  3. Call startShootPhoto

To fetch asset (thumbnail, preview or media)

  1. Exit Playback Mode (if needed)
  2. Refresh the file list on the SD card
  3. Fetch File List Snapshot
  4. Sort List by capture taken
  5. Pick the required file by index
  6. Enter Playback Mode
  7. Initiate download/fetch of asset
  8. Exit Playback Mode

I'm unsure if the modes are correct for the different stages because the documentation doesn't make this clear. What's interesting is that these routines work sometimes, but then after one failure, they just keep failing which makes me believe that the failure causes them to end up in some mode that causes the failure again, and so it's a feedback loop. However, I'm unable to figure out what that mode is since I make sure to exit playback mode in case the download/fetch of the asset fails or succeeds, and even make sure to exit playback mode at the start of both the routines just in case.

Any help is appreciated.

DJI-William commented 2 years ago

The step you are capturing shot is correct but fetch asset is wrong. You need to enterPlayback first and then refreh and read those assets. You can only refresh when you are in the playback mode.

  1. Call enterPlaybck.
  2. Refresh the file list on the SD card
  3. Fetch File List Snapshot
  4. Pick the required file by index
  5. Initiate download/fetch of asset
  6. Exit Playback Mode
dkapur17 commented 2 years ago

Hey @DJI-William, thanks for your reply!

I have placed debug statements after every call to the SDK, so I know exactly which call is failing, and the one to refresh the list on the SD card seems to be working as expected when the camera is not in playback mode. The failure at all times occurs in the fetch step (which I call when the camera is in playback mode). I will however try your suggestion and get back to you ASAP.

dkapur17 commented 2 years ago

Hey @DJI-William. I tried your proposed solution, but it still results in the error "Media download result: the server aborts the downloading". I've attached below my routine for fetching and returning the media by index. It is in Kotlin, but I've added comments to each of the steps to make it understandable:


// Storing the index in n
var n =  idx

// Put the camera into playback mode and wait for coroutine to complete
val enterPlaybackError = suspendCoroutine<DJIError?> { cont ->
    drone!!.camera!!.enterPlayback { error ->
        cont.resume(error)
    }
}

// If there is an error in entering playback mode, return
if(enterPlaybackError != null)
    return CommandCompleted(false, "Enter Playback Error: " + enterPlaybackError.description)

// Refresh the file list on the SD Card and wait for the coroutine to complete
val refreshError = suspendCoroutine<DJIError?> { cont ->
    drone!!.camera!!.mediaManager!!.refreshFileListOfStorageLocation(SettingsDefinitions.StorageLocation.SDCARD) { error ->
        cont.resume(error)
    }
}

// If there is an error in refreshing the file list, return
if(refreshError != null)
    return CommandCompleted(false, "Unable to refresh file list: " + refreshError.description)

// If after refreshing, the state is still not UP_TO_DATE, something went wrong, so return
if(drone!!.camera!!.mediaManager!!.sdCardFileListState != MediaManager.FileListState.UP_TO_DATE)
    return CommandCompleted(false, "Unable to refresh file list")

// Get the snapshot of all files
val mediaFiles = drone!!.camera!!.mediaManager!!.sdCardFileListSnapshot

// If the snapshot is null, return
if(mediaFiles == null)
    return CommandCompleted(false, "Unable to fetch media files")

// Sort media files by time
mediaFiles!!.sortByDescending { it.timeCreated }

// Validate the provided index
if(n >= mediaFiles.size)
    call.respond(CommandCompleted(false, "Index n ($n) exceeds total number of files in storage (${mediaFiles.size})"))

// Access the target file
val targetFile = mediaFiles[n]

// Create a byte buffer to push the media file bytes into
val fullMediaBuffer = ByteBuffer.allocate(targetFile.fileSize.toInt())

// Run the fetchFileByteData coroutine and wait for it to complete
// downloadError is a String if success, or a DJIError if fail

// Error seems to occur here, since the coroutine returns a DJIError object on failure
val downloadError = suspendCoroutine { cont ->
    targetFile.fetchFileByteData(0, object: DownloadListener<String> {
        override fun onStart() {
            return
        }

        override fun onRateUpdate(p0: Long, p1: Long, p2: Long) {
            return
        }

        // On receiving each chunk of the media file, place it into the allocated buffer
        override fun onRealtimeDataUpdate(
            p0: ByteArray?,
            p1: Long,
            p2: Boolean
        ) {
            if(p0 != null)
                fullMediaBuffer.put(p0)
            return
        }

        override fun onProgress(p0: Long, p1: Long) {
            return
        }

        // If success, then return a string
        override fun onSuccess(p0: String?) {
            cont.resume(p0)
        }
        // If failure, then return a DJIError
        override fun onFailure(p0: DJIError?) {
            cont.resume(p0)
        }
    })
}

// Exit playback mode and wait for coroutine to complete
val exitPlaybackError = suspendCoroutine<DJIError?> { cont ->
    drone!!.camera!!.exitPlayback { error ->
        cont.resume(error)
    }
}

// If there is an error in exiting the playback mode, log the error
if(exitPlaybackError != null)
    Log.d("DIAGNOSIS", exitPlaybackError.description)

// Check for status of download completion

// In case of error, return
if(downloadError is DJIError)
    return CommandCompleted(false, "Error in downloading: " + downloadError.description)
// In case of success, convert the media in a base64 encoded string and return
else if(downloadError is String) {
    val fullImageBytes = fullMediaBuffer.array()
    val fullImageString = Base64.encodeToString(fullImageBytes, Base64.NO_WRAP)
    return fullImageString
}
// Otherwise, return
else
    CommandCompleted(false, "Error in downloading")

This routine works sometimes but doesn't work other times, and I can't figure out what is causing this inconsistency. Please let me know if something is unclear in the code. Really appreciate your help!

dkapur17 commented 2 years ago

@DJI-William I think I've narrowed down the problem. It seems to be in the refresh step. I notice that whenever I get these errors, the mediaFiles list (fetched using drone!!.camera!!.mediaManager!!.sdCardFileListSnapshot) has just one item in it. This seems to be due to some problem in the call to refreshFileListOfStorageLocation(). Looks like this call runs without throwing an exception, but it doesn't update the snapshot itself (or updates it to something incorrect). I inspected the single item in the list and it is a file with the name DJI_0000.jpg, which I think must be some kind of error indication, since no such file exists in the SD Card. Is there any way to make this call work more consistently, or am I missing something?

DJI-William commented 2 years ago

I don't think your code is wrong. Anyway, Have you upgrade your aircraft firmware to the latest. Also try format your SD card first and test again. What happens if you use DJI FLY?

dkapur17 commented 2 years ago

Yes, the firmware is up to date (01.05.0000). I can see all the images I've captured in the DJIFly app without any issues.

I noticed that the problem crops up when I call the routine for taking the image and then immediately call any of the fetch routines, it leads to an execution timeout/download abort. If I give it say 10 seconds and then call the fetch routine then it seems to run successfully. I haven't run enough tests to say for sure, but I noticed this pattern. Could it be because even after calling the startPhotoShoot method, the actual procedure of capturing the image and then writing it to the SD Card is not completed when I call the fetch routine, which is why it fails? If this is the case, is there some functionality in the SDK using which I can wait until the image is saved to the SD Card before returning from the capture routine?

DJI-William commented 2 years ago

Of cource, after calling the startPhotoShoot, it does not mean the shoot action is completed. You should call setNewGeneratedMediaFileInfoCallback to setup a media file generated listener. When this callback has a return, it means the shoot action is finished and a new media file is generated. Then you can go enterPlayback and refresh the list.

brien-crean commented 1 year ago

@dkapur17 did you ever figure out a workaround for this issue? I'm seeing the same issue with my app. I have the same issue with refreshFileListOfStorageLocation() intermittently failing. Also sometimes, even before I get that far enterPlayback sometimes fails to complete

https://github.com/dji-sdk/Mobile-SDK-Android/issues/1188#issuecomment-1539153390