ImageCaptureCore: ICCameraFile requestReadData(atOffset:length:completion:) always passes empty Data object to completion block

Originator:mjrusso
Number:rdar://FB7663947 Date Originated:2020-04-14
Status:Investigation complete - Works as currently designed Resolved:2020-04-17
Product:Image I/O Product Version:iPadOS 13.4
Classification: Reproducible:100%
 
Important: this is a regression in iPadOS 13.4. This has worked with previous versions of iPadOS, since beta versions of 13.2.

On iPadOS, ICCameraFile's `requestReadData(atOffset:length:completion:)` method does not read data from the camera file.

When `requestReadData` is called with an offset of 0 and a length corresponding to the size of the file, the completion block is executed with a non-nil, 0 byte Data object. (The error is nil.) The expected result is a Data object with the actual contents of the camera file (and a nil error), not a 0 byte Data object.

The attached project reproduces this sample issue. (There is no app UI; see the messages logged to the console.) This project:

- Instantiates an ICDeviceBrowser, sets its delegate, and starts its browser.
- (User must tap OK at the permission prompt to access files on connected cameras and storage.)
- When an ICCameraDevice (such as an SD card) is connected, the code automatically sets its delgate, and opens a session.
- When the ICCameraDeviceDelegate's `deviceDidBecomeReady(withCompleteContentCatalog:)` delegate method is called, `requestReadData(atOffset:length:completion:)` is called with on the first ICCameraFile object. (Using the first ICCameraFile is arbitrary; the results are the same regardless of which file is chosen.)
- The `requestReadData(atOffset:length:completion:)` completion block is subsequently called with a Data object that is 0 bytes in length.

Example console output:

> [DeviceFinder] started ICDeviceBrowser; connect camera device now
> [CameraDevice] session opened
> [CameraDevice] device ready, with complete catalog of 178 items
> [CameraDevice] about to requestReadData for file '    🅁 |    DSCF2030.RAF | 0 |                ' with size 50544640
> [CameraFile] in requestReadData completion block
> *** [CameraFile] data read via requestReadData is empty!

Instead, the expected output is:

> [DeviceFinder] started ICDeviceBrowser; connect camera device now
> [CameraDevice] session opened
> [CameraDevice] device ready, with complete catalog of 178 items
> [CameraDevice] about to requestReadData for file '    🅁 |    DSCF2030.RAF | 0 |                ' with size 50544640
> [CameraFile] in requestReadData completion block
> *** [CameraFile] data read via requestReadData has 50544640 bytes!

For Open Radar: The minimum code sample to reproduce this is at https://gist.github.com/mjrusso/b223bf8db93e64d858e80b6fb8ab6ef8

Comments

I've received confirmation from Apple that this 4MB read limit has been removed, as of iPadOS 14 beta 1.

My response to Apple:

Thank you for letting me know about the change in read size limits. I’m able to successfully read data in 4 MB chunks.

Can you confirm if the fastest way to read the entire file is to request the next chunk in the completion handler, thus ensuring that there’s at most one 4MB request in flight at a time?

Using that approach (requesting the next chunk in the completion handler), I can read a 50 MB raw image in ~0.9 seconds. [Hardware details included below.]

In comparison, when picking the same SD card using UIDocumentPicker and manually traversing the filesystem to find images, I can read the same ~50 MB raw image in ~0.002 seconds (but, with memory mapped I/O: Data(contentsOf: urlToRawImageFileOnSDCard, options: [.alwaysMapped])).

Is there a way to approximate the performance of using memory mapped I/O with ImageCaptureCore? This is important for my use case: my app is not “importing” images into a library; instead, it’s using the Vision framework to analyze images on a connected camera device, and is greatly improved by the ability to get underlying data into a UIImage as quickly as possible (regardless of where the bytes actually live).

The several-orders-of-magnitude speed difference between UIDocumentPicker and ImageCaptureCore is very significant to app responsiveness and overall utility. Unfortunately, UIDocumentPicker presents user experience challenges (it’s confusing for the user to know what files/folders to select, and it is unreliable: occasionally, enumerating the files/folders on an SD card returns no results, among other issues). ImageCaptureCore has direct camera support, notifications for added/removed devices, etc., but the performance is prohibitive for this use case.

For reference, hardware details:

  • iPad Pro (11”, 1st Generation)
  • Apple USB-C to SD Card Reader (UHS-II)
  • SanDisk 64GB Extreme Pro SDXC (UHS-I, 170 MB/s)

For posterity, when using UIDocumentPicker to read raw images from the filesystem without using memory mapping, it takes ~0.8 seconds, which is close to the performance of ImageCaptureCore’s requestReadData(atOffset:length:completion:).)

Response from Apple:

"Please limit read size requests to < 4MB. We will be documenting this shortly."


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!