Swift: Retrieve audio file marker list from url?

I just want to get a list of the markers in an audio file. I thought this would be an easy common task that wouldn’t be too difficult. However, I can barely find any example code or documentation, so I ended up with this:

private func getMarkers(_ url: CFURL) -> AudioFileMarkerList {

  var file: AudioFileID?
  var size: UInt32 = 0
  var markers = AudioFileMarkerList()

  AudioFileOpenURL(url, .readPermission, kAudioFileWAVEType, &file)
  AudioFileGetPropertyInfo(file!, kAudioFilePropertyMarkerList, &size, nil)
  AudioFileGetProperty(file!, kAudioFilePropertyMarkerList, &size, &markers)

  return markers
}

Sadly, this doesn’t work: error: memory read failed for 0x0.

  • storing data locally on the iphone
  • Thread 1 : EXC_BAD_ACCESS (Code = 1, address = 0x30000008)
  • Uploading files over HTTP fails on iOS 8.0.0 GM Safari
  • NSURLRequest : Post data and read the posted page
  • PHAsset Location — GPS MetaData, what's wrong?
  • How to check whether now date is during 9:00-18:00
  • I just can’t figure out the problem. I checked the url and the size (which are both valid), but it always fails to retrieve the markers. Any help with this would be fantastic!

    EDIT:
    This sort of works, but all the data is completely wrong, and I can’t understand how a single audio file can have multiple AudioFileMarkerLists of markers:

    private func getMarkers(_ url: CFURL) -> [AudioFileMarkerList] {
    
      var file: AudioFileID?
      var size: UInt32 = 0
    
      AudioFileOpenURL(url, .readPermission, kAudioFileWAVEType, &file)
      AudioFileGetPropertyInfo(file!, kAudioFilePropertyMarkerList, &size, nil)
    
      let length = NumBytesToNumAudioFileMarkers(Int(size))
      var markers = [AudioFileMarkerList](repeating: AudioFileMarkerList(), count: length)
      AudioFileGetProperty(file!, kAudioFilePropertyMarkerList, &size, &markers)
      return markers
    }
    

    EDIT 2: According to most answers I’ve seen so far, this should work, but it returns an empty array:

    private func getMarkers(_ url: CFURL) -> [AudioFileMarkerList] {
    
      var file: AudioFileID?
      var size: UInt32 = 0
    
      AudioFileOpenURL(url, .readPermission, kAudioFileWAVEType, &file)
      AudioFileGetPropertyInfo(file!, kAudioFilePropertyMarkerList, &size, nil)
      let length = NumBytesToNumAudioFileMarkers(Int(size))
    
      var markers = [AudioFileMarkerList]()
      markers.reserveCapacity(length)
      AudioFileGetProperty(file!, kAudioFilePropertyMarkerList, &size, &markers)
    
      return markers
    
    }
    

    EDIT 3:
    I got rid of a bunch of error checking and useful stuff from Ryan’s code for anyone wanting to quickly try and find the problem:

    private func getMarkers(_ url: CFURL) -> [AudioFileMarker]? {
    
        var file: AudioFileID?
        var size: UInt32 = 0
        var markers: [AudioFileMarker] = []
    
        AudioFileOpenURL(url, .readPermission, kAudioFileWAVEType, &file)
    
        AudioFileGetPropertyInfo(file!, kAudioFilePropertyMarkerList, &size, nil)
    
        let length = NumBytesToNumAudioFileMarkers(Int(size))
    
        let data = UnsafeMutablePointer<AudioFileMarkerList>.allocate(capacity: length)
    
        AudioFileGetProperty(file!, kAudioFilePropertyMarkerList, &size, data)
    
        markers.append(data.pointee.mMarkers)
    
        data.deallocate(capacity: length)
    
        return markers
    }
    

    I just hope Apple actually tested AudioFileMarkerList in the first place.

    EDIT 4:
    SOLVED thanks to Rhythmic Fistman and Ryan Francesconi! Final result:

    private func getMarkers(_ url: CFURL) -> [AudioFileMarker]? {
    
      var file: AudioFileID?
      var size: UInt32 = 0
      var markerList: [AudioFileMarker] = []
    
      AudioFileOpenURL(url, .readPermission, kAudioFileWAVEType, &file)
    
      AudioFileGetPropertyInfo(file!, kAudioFilePropertyMarkerList, &size, nil)
    
      let length = NumBytesToNumAudioFileMarkers(Int(size))
    
      let data = UnsafeMutablePointer<AudioFileMarkerList>.allocate(capacity: length)
    
      AudioFileGetProperty(file!, kAudioFilePropertyMarkerList, &size, data)
    
      let markers = UnsafeBufferPointer<AudioFileMarker>(start: &data.pointee.mMarkers, count: length)
      for marker in markers {
        markerList.append(marker)
      }
    
      data.deallocate(capacity: length)
    
      return markerList
    }
    

    2 Solutions Collect From Internet About “Swift: Retrieve audio file marker list from url?”

    Looks like you need to use UnsafeBufferPointer to access variable length arrays (like mMarkers). So instead of

    out.append(markerList.mMarkers)
    

    which only adds the first element, do this

    let markersBuffer = UnsafeBufferPointer<AudioFileMarker>(start: &data.pointee.mMarkers,
                                                   count: Int(data.pointee.mNumberMarkers))
    
    for marker in markersBuffer {
        markers.append(marker)
    }
    

    Modeled on this answer

    EDIT: Simplest solution is to use AudioKit’s version of EZAudioFile.markers. Note this is not the same as the original EZAudio framework as I had added this marker code to AudioKit’s version only.

    import AudioKit
    ...
    
    if let file = EZAudioFile(url: url) {
        if let markers = file.markers as? [EZAudioFileMarker] {
            for m in markers {
                Swift.print("NAME: \(m.name) FRAME: \(m.framePosition)")
            }
        }
    }
    

    If you REALLY want to try in Swift, it would look something like the below. I’m not an expert in this, but as far as I can tell, there is some issue translating the AudioFileMarkerList struct to Swift. This may be solvable, but it seems to me it’s best to just use Objective C to accomplish these calls. Here is the almost finished function in Swift. I recommend using AudioKit to accomplish what you need as I have added the marker code to EZAudioFile there. Check: https://github.com/AudioKit/AudioKit/blob/master/AudioKit/Common/Internals/EZAudio/EZAudioFile.m

    But for the record here is the Swift code in progress! Note it’s hard coded to WAVE files for the moment… Perhaps someone else can finish this?

    class func getAudioFileMarkers(_ url: URL) -> [AudioFileMarker]? {
        Swift.print("getAudioFileMarkers() \(url)")
    
        var err: OSStatus = noErr
        var audioFileID: AudioFileID?
    
        err = AudioFileOpenURL(url as CFURL,
                               .readPermission,
                               kAudioFileWAVEType,
                               &audioFileID)
    
        if err != noErr {
            Swift.print("AudioFileOpenURL FAILED, Error: \(err)")
            return nil
        }
    
        guard audioFileID != nil else {
            return nil
        }
    
        Swift.print("audioFileID: \(audioFileID)")
    
        var outSize: UInt32 = 0
        var writable: UInt32 = 0
    
        err = AudioFileGetPropertyInfo(audioFileID!, kAudioFilePropertyMarkerList, &outSize, &writable)
        if err != noErr {
            Swift.print("AudioFileGetPropertyInfo kAudioFilePropertyMarkerList FAILED, Error: \(err)")
            return nil
        }
    
        Swift.print("outSize: \(outSize), writable: \(writable)")
    
        guard outSize != 0 else { return nil }
    
        let length = NumBytesToNumAudioFileMarkers( Int(outSize) )
    
        Swift.print("Found \(length) markers")
    
        let theData = UnsafeMutablePointer<AudioFileMarkerList>.allocate(capacity: length)
    
        if length == 0 {
            return nil
        }
    
        // pull marker list
        err = AudioFileGetProperty(audioFileID!, kAudioFilePropertyMarkerList, &outSize, theData)
    
        if err != noErr {
            Swift.print("AudioFileGetProperty kAudioFilePropertyMarkerList FAILED, Error: \(err)")
            return nil
        }
    
        let markerList: AudioFileMarkerList = theData.pointee
    
        Swift.print("markerList.mMarkers: \(markerList.mMarkers)")
        // this is only showing up as a single AudioFileMarker, not an array of them.
        // I DON'T KNOW WHY. It works in Obj-C. I'm obviously missing something, or there is a problem in translation
    
        var out = [AudioFileMarker]()
    
        let mirror = Mirror(reflecting: markerList.mMarkers)
        for m in mirror.children {
            Swift.print( "label: \(m.label) value: \(m.value)" )
        }
    
        // for now just append the first one.
        // :(
        out.append(markerList.mMarkers)
    
        // done with this now
        theData.deallocate(capacity: length)
    
        return out
    }