Josh
Josh

Reputation: 2587

How to store data from an UnsafeMutablePointer in the iOS file system

I am reading data from an MFi external device into a buffer using a 3rd party SDK "sessionController". See below:

    let handle: UInt64 = self.sessionController.openFile(file.path, mode: openMode)
    if handle == 0 {
        //Error
        return
    }

    let c: UInt64 = file.size

    var bytesArray: [UInt8] = [UInt8](fileData)
    let bufferPointer: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(c))

    bufferPointer.initialize(repeating: 0, count: Int(c))

    defer {
        bufferPointer.deinitialize(count: Int(c))
        bufferPointer.deallocate()
    }

    var sum: UInt32 = 0
    let singleSize: UInt32 = 8 << 20

    while sum < c {
        let read = self.sessionController.readFile(handle, data: bufferPointer, len: singleSize)
        if read == 0 {
            //There was an error
            return
        }
        sum += read
    }

    let newPointer : UnsafeRawPointer = UnsafeRawPointer(bufferPointer)

    fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("test.MOV")

    fileData = Data(bytes: newPointer, count: Int(c))

    try! fileData.write(to: fileURL)

    //Now use this fileURL to watch video in an AVPlayer... 
    //AVPlayer(init: fileURL)

For some reason the data stored at the fileURL becomes corrupted (I think) and I am unable to play the video file. I think I am not doing something correctly with Unsafe Swift but I am not sure what. How can I make sure that I have properly read the data from the device into memory, and then taken that data from memory and stored it on the hard drive at the fileURL? What am I doing wrong here? The video will not play in AVPlayer given the fileURL.

Upvotes: 1

Views: 1195

Answers (1)

Martin R
Martin R

Reputation: 540005

The main error is here:

let read = self.sessionController.readFile(handle, data: bufferPointer, len: singleSize)

If you read in multiple chunks then the second and all subsequent reads will overwrite the data read previously. So that should probably be

let read = self.sessionController.readFile(handle, data: bufferPointer + sum, len: singleSize)

Note also that the file size is defined as UInt64, but the variable sum (which holds the total number of bytes read so far) is an UInt32. This will lead to problems if there is more than 4GB data.

But generally I would avoid to read the complete data into a memory buffer. You already read in chunks, so you can write the data immediately to the destination file. Here is how that could look like:

// Output file:
let fileURL = ...
let fileHandle = try FileHandle(forWritingTo: fileURL)
defer { fileHandle.closeFile() }

// Buffer:
let bufferSize = 1024 * 1024 // Choose some buffer size
var buffer = Data(count: bufferSize)

// Read/write loop:
let fileSize: UInt64 = file.size
var remainingToRead = fileSize
while remainingToRead > 0 {
    let read = buffer.withUnsafeMutableBytes { bufferPointer in
        self.sessionController.readFile(handle, data: bufferPointer, len: UInt32(min(remainingToRead, UInt64(bufferSize))))
    }
    if read == 0 {
        return // Read error 
    }
    remainingToRead -= UInt64(read)
    fileHandle.write(buffer)
}

Note also that the data is read directly into a Data value, instead of reading it into allocated memory and then copying it to another Data.

Upvotes: 3

Related Questions