Convert AVAsset to Data *without* touching disk?

I've got an AVAsset that I'd like to encrypt before saving to disk. Most suggestions for converting AVAsset -> Data recommend using AVAssetExportSession, but that's not an option for this use case, as the spec requires that the asset not touch the disk while unencrypted. For example, there should be no scenario where crashing the app leaves the asset accessible without decryption. That also means I can't rely on disk-level encryption, since the asset must be encrypted regardless of the user's passcode settings and lock/unlock state.


Given these constraints, what's the best way to convert the AVAsset to Data?

Replies

You don't say what kind of data you're writing (video?) or what format/encoding, but I think the answer you're looking for is AVAssetReader, with some kind of AVAssetReaderOutput to grab sample buffers for encryption. Instead of using an AVAssetWriter for the output side, you can accumulate encrypted data in a Data object, or write it incrementally to disk (though with a memory mapped Data object, those are basically the same thing).

Thanks for the leads, I'd poked around a little with AVAssetReader but wasn't sure it would do what I wanted. I'll take a closer look.


I didn't specify format/encoding because I'm not too picky about it. Ideally, I'd like to be able to specify a standard file format instead of raw buffers, so there's no need to "remember" bitrate/stereo/interleaving/etc, but beyond that the solution can be flexible. Compressed formats like AAC are slightly preferable, but uncompressed formats are also an option if that path is significantly easier.

For anyone else looking to do the same thing (creating a valid audio file without touching disk), once the data buffers are extracted using AVAssetReader, it's easiest to put them into a CAF file.


Refer to Apple's CAF specification: https://developer.apple.com/library/content/documentation/MusicAudio/Reference/CAFSpec/CAF_spec/CAF_spec.html#//apple_ref/doc/uid/TP40001862-CH210-TPXREF101


The AudioToolbox framework has a CAFFile.h header with all of the relevant structs. So it's just a matter of reading the spec, populating the structs, then sticking them into Data objects something like this:

return Data(bytes: &fileHeader, count: MemoryLayout<CAFFileHeader>.size) +
            Data(bytes: &audioDescriptionChunkHeader, count: MemoryLayout<CAFChunkHeader>.size) +
            Data(bytes: &audioDescriptionChunkData, count: MemoryLayout<CAFAudioDescription>.size) +
            Data(bytes: &audioDataChunkHeader, count: MemoryLayout<CAFChunkHeader>.size) +
            Data(bytes: &audioDataChunkData.mEditCount, count: MemoryLayout<UInt32>.size) +
            data



Things to keep in mind:

when initializing the structs, everything needs to be bigEndian, including the "strings" (represented in CAFFile.h as UInt32), ex:

var audioDescriptionChunkHeader = CAFChunkHeader(
            mChunkType: kCAF_StreamDescriptionChunkID.bigEndian,
            mChunkSize: Int64(MemoryLayout.size).bigEndian
        )
  • the kAudioFormat constants like kAudioFormatLinearPCM are internally represented with the same 4-letter codes you'll need for mFormatID, so you can just pass those in for that argument
  • for PCM, mBytesPerPacket is
    UInt32(bitDepth/8 * numChannels).bigEndian

and mBitsPerChannel is just the bitDepth, no need to divide it by the number of channels