2

I'm trying to convert a given audio file in .m4a format to .aiff format, using the answer from this post. I've converted the code to Swift 3.0.

func convertAudio(_ url: URL, outputURL: URL) {
    var error : OSStatus = noErr
    var destinationFile : ExtAudioFileRef? = nil
    var sourceFile : ExtAudioFileRef? = nil

    var srcFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
    var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()

    var audioConverter : AudioConverterRef? = nil

    ExtAudioFileOpenURL(url as CFURL, &sourceFile)

    var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))

    ExtAudioFileGetProperty(sourceFile!, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &srcFormat)

    dstFormat.mSampleRate = 44100  //Set sample rate
    dstFormat.mFormatID = kAudioFormatLinearPCM
    dstFormat.mChannelsPerFrame = 1
    dstFormat.mBitsPerChannel = 16
    dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mFramesPerPacket = 1
    dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger

    //Create destination file
    ExtAudioFileCreateWithURL(outputURL as CFURL, kAudioFileAIFFType, &dstFormat, nil,
                              AudioFileFlags.eraseFile.rawValue, &destinationFile)

    ExtAudioFileSetProperty(sourceFile!, kExtAudioFileProperty_ClientDataFormat, thePropertySize, &dstFormat)
    ExtAudioFileSetProperty(destinationFile!, kExtAudioFileProperty_ClientDataFormat, thePropertySize, &dstFormat)

    var size : UInt32 = UInt32(MemoryLayout.stride(ofValue: audioConverter))

    ExtAudioFileGetProperty(destinationFile!, kExtAudioFileProperty_AudioConverter, &size, &audioConverter)

    var canResume : UInt32 = 0

    size = UInt32(MemoryLayout.stride(ofValue: canResume))

    error = AudioConverterGetProperty(audioConverter!, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume)

    let bufferByteSize : UInt32 = 32768
    var srcBuffer = [UInt8](repeating: 0, count: 32768)
    var sourceFrameOffset : ULONG = 0

    print("Converting audio file")

    while(true){

        var fillBufList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: 2,
                mDataByteSize: UInt32(srcBuffer.count),
                mData: &srcBuffer
            )
        )
        var numFrames : UInt32 = 0

        if(dstFormat.mBytesPerFrame > 0){
            numFrames = bufferByteSize / dstFormat.mBytesPerFrame
        }

        ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)

        if(numFrames == 0){
            error = noErr;
            break;
        }

        sourceFrameOffset += numFrames
        error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
    }

    ExtAudioFileDispose(destinationFile!)
    ExtAudioFileDispose(sourceFile!)
}

The problem is audioConverter seems to be nil at this line

error = AudioConverterGetProperty(audioConverter!, kAudioConverterPropertyCanResumeFromInterruption, &size, &canResume)

And I can't seem to figure out why. What did I miss out?

Community
  • 1
  • 1
Chan Jing Hong
  • 2,251
  • 4
  • 22
  • 41
  • You don't seem to initialize `audioConverter`, thus it has it's default value - `nil` – Cristik Jan 03 '17 at 06:01
  • @Cristik Wouldn't it be set in `ExtAudioFileGetProperty(destinationFile!, kExtAudioFileProperty_AudioConverter, &size, &audioConverter)`? If not then what is the correct way to initialise it? – Chan Jing Hong Jan 03 '17 at 06:03

1 Answers1

6

Skip the AudioConverterGetProperty

You actually are not using it.
The following snippet will convert an audio file to AIFF: it reads the sourceFile in one of the supported formats, creates an AIFF encoder, and loops through it using a bufferByteSize buffer. Errors are mildly handled.

Complete code, swift 3:

func convertAudio(_ url: URL, outputURL: URL) {
    var error : OSStatus = noErr
    var destinationFile : ExtAudioFileRef? = nil
    var sourceFile : ExtAudioFileRef? = nil

    var srcFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()
    var dstFormat : AudioStreamBasicDescription = AudioStreamBasicDescription()

    ExtAudioFileOpenURL(url as CFURL, &sourceFile)

    var thePropertySize: UInt32 = UInt32(MemoryLayout.stride(ofValue: srcFormat))

    ExtAudioFileGetProperty(sourceFile!,
        kExtAudioFileProperty_FileDataFormat,
        &thePropertySize, &srcFormat)

    dstFormat.mSampleRate = 44100  //Set sample rate
    dstFormat.mFormatID = kAudioFormatLinearPCM
    dstFormat.mChannelsPerFrame = 1
    dstFormat.mBitsPerChannel = 16
    dstFormat.mBytesPerPacket = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mBytesPerFrame = 2 * dstFormat.mChannelsPerFrame
    dstFormat.mFramesPerPacket = 1
    dstFormat.mFormatFlags = kAudioFormatFlagIsBigEndian |
                             kAudioFormatFlagIsSignedInteger

    // Create destination file
    error = ExtAudioFileCreateWithURL(
        outputURL as CFURL,
        kAudioFileAIFFType,
        &dstFormat,
        nil,
        AudioFileFlags.eraseFile.rawValue,
        &destinationFile)
    reportError(error: error)

    error = ExtAudioFileSetProperty(sourceFile!,
            kExtAudioFileProperty_ClientDataFormat,
            thePropertySize,
            &dstFormat)
    reportError(error: error)

    error = ExtAudioFileSetProperty(destinationFile!,
                                     kExtAudioFileProperty_ClientDataFormat,
                                    thePropertySize,
                                    &dstFormat)
    reportError(error: error)

    let bufferByteSize : UInt32 = 32768
    var srcBuffer = [UInt8](repeating: 0, count: 32768)
    var sourceFrameOffset : ULONG = 0

    while(true){
        var fillBufList = AudioBufferList(
            mNumberBuffers: 1,
            mBuffers: AudioBuffer(
                mNumberChannels: 2,
                mDataByteSize: UInt32(srcBuffer.count),
                mData: &srcBuffer
            )
        )
        var numFrames : UInt32 = 0

        if(dstFormat.mBytesPerFrame > 0){
            numFrames = bufferByteSize / dstFormat.mBytesPerFrame
        }

        error = ExtAudioFileRead(sourceFile!, &numFrames, &fillBufList)
        reportError(error: error)

        if(numFrames == 0){
            error = noErr;
            break;
        }
        
        sourceFrameOffset += numFrames
        error = ExtAudioFileWrite(destinationFile!, numFrames, &fillBufList)
        reportError(error: error)
    }
    
    error = ExtAudioFileDispose(destinationFile!)
    reportError(error: error)
    error = ExtAudioFileDispose(sourceFile!)
    reportError(error: error)
}

Supporting method:

func reportError(error: OSStatus) {
    // Handle error
}

Invocation:

let sourceUrl = URL(string: Bundle.main.path(forResource: "sample", ofType: "mp3")!)
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                .userDomainMask,
                                                true)
let documentsDirectory = URL(string: paths.first!)
let destUrl = documentsDirectory?.appendingPathComponent("converted.aiff")
if let sourceUrl = sourceUrl, let destUrl = destUrl {
    print("from \(sourceUrl.absoluteString) to \(destUrl.absoluteString)")
    convertAudio(sourceUrl, outputURL: destUrl)
}
Community
  • 1
  • 1
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179
  • 3
    Is there any other changes to export to wav other than using `kAudioFileWAVEType`? I'm getting a a nil crash on `error = ExtAudioFileSetProperty(destinationFile!,` when using WAVE type. I need to export m4a to wav or flac. – Marcos Griselli Mar 20 '17 at 21:28
  • 2
    @MarcosGriselli Getting the same kind of error for MP3 conversion. Did you find any solution? – miOS Aug 21 '17 at 10:18
  • @MarcosGriselli I am stuck with the similar issue for CAF format. Any solution so far? – N4SK Jun 03 '21 at 07:45