Nauman Malik
Nauman Malik

Reputation: 1346

How to convert .caf Audio file into .mp4 file in swift

I'm recording audio using device microphone with AVAudioRecorder which return file in .caf format that is playable only in Apple devices but not on Android devices. Since Apple is not supporting .mp3 file so I want to convert it in .mp4 format before uploading to server. Is .mp4 is good for audio only? Can I convert it with AVAssetExportSession ?

Following is audio recorder code:

func setupAudioRecorder ()
    {

    let fileMgr = FileManager.default
    let dirPaths = fileMgr.urls(for:.documentDirectory,
                                in:.userDomainMask)

    let soundFileURL = dirPaths[0].appendingPathComponent("myaudio.caf")

    let recordSettings =
        [AVEncoderAudioQualityKey: AVAudioQuality.min.rawValue,
         AVEncoderBitRateKey: 16,
         AVNumberOfChannelsKey: 2,
         AVSampleRateKey: 44100.0] as [String : Any]

    do {
        try audioSession.setCategory(
            AVAudioSessionCategoryPlayAndRecord)
    } catch let error as NSError {
        print("audioSession error: \(error.localizedDescription)")
    }

    do {
        try audioRecorder = AVAudioRecorder(url: soundFileURL,
                                            settings: recordSettings as [String : AnyObject])
        audioRecorder?.prepareToRecord()
    } catch let error as NSError {
        print("audioSession error: \(error.localizedDescription)")
    }
}

Upvotes: 4

Views: 4909

Answers (3)

laka
laka

Reputation: 796

async mp4 export method

Here is a slightly modified, formatted and shortened variant based on the accepted answer.

It's using an async method, you just pass your .caf file's URL and it returns an URL to the exported .mp4 file:

/// Exports a `.caf` audio file located at `inputAudioURL` to returned `URL` (in temporary directory).
/// - Parameter inputAudioURL: The `.caf` file's `URL`.
/// - Returns: The output `URL` of the exported `.mp4` file or `nil` if export failed.
func exportToMP4(inputAudioURL: URL) async -> URL? {

    // Extracts file name from input URL and appends `.mp4`
    let fileName = inputAudioURL.deletingPathExtension().lastPathComponent.appending(".mp4")

    // Creates output URL for temporary directory.
    guard let outputURL = NSURL.fileURL(withPathComponents: [NSTemporaryDirectory(), fileName]),
          let exportSession = AVAssetExportSession(
            asset: AVAsset(url: inputAudioURL),
            presetName: AVAssetExportPresetHighestQuality
          ) else { return nil }

    // Removes file if it already exists.
    try? FileManager.default.removeItem(at: outputURL)

    exportSession.outputFileType = AVFileType.mp4
    exportSession.outputURL = outputURL

    await exportSession.export()
    if exportSession.status == .completed {
        return outputURL
    } else {
        print("mp4 export did not complete")
        return nil
    }
}

Non async variant

In case your codebase does not support async methods (or you just don't like them for some reason), here is the same using a completion handler:

/// Exports a `.caf` audio file located at `inputAudioURL` to returned `URL` (in temporary directory).
/// - Parameter inputAudioURL: The `.caf` file's `URL`.
/// - Returns: The output `URL` of the exported `.mp4` file or `nil` if export failed.
func exportToMP4(inputAudioURL: URL, didFinish: ((URL?) -> Void)?) {

    // Extracts file name from input URL and appends `.mp4`
    let fileName = inputAudioURL.deletingPathExtension().lastPathComponent.appending(".mp4")

    // Creates output URL for temporary directory.
    guard let outputURL = NSURL.fileURL(withPathComponents: [NSTemporaryDirectory(), fileName]),
          let exportSession = AVAssetExportSession(
            asset: AVAsset(url: inputAudioURL),
            presetName: AVAssetExportPresetHighestQuality
          ) else { didFinish?(nil); return }

    // Removes file if it already exists.
    try? FileManager.default.removeItem(at: outputURL)

    exportSession.outputFileType = AVFileType.mp4
    exportSession.outputURL = outputURL

    exportSession.exportAsynchronously {
        if exportSession.status == .completed {
            didFinish?(outputURL)
        } else {
            print("mp4 export did not complete")
            didFinish?(nil)
        }
    }
}

Upvotes: 1

Nauman Malik
Nauman Malik

Reputation: 1346

After lot of search i am able to convert .caf into .mp4 using this piece of code

    let audioURL = ".caf audio file url"

    let fileMgr = FileManager.default

    let dirPaths = fileMgr.urls(for: .documentDirectory,
                                in: .userDomainMask)

    let outputUrl = dirPaths[0].appendingPathComponent("audiosound.mp4")

    let asset = AVAsset.init(url: audioURL)

    let exportSession = AVAssetExportSession.init(asset: asset, presetName: AVAssetExportPresetHighestQuality)

    // remove file if already exits
    let fileManager = FileManager.default
    do{
        try? fileManager.removeItem(at: outputUrl)

    }catch{
        print("can't")
    }


    exportSession?.outputFileType = AVFileTypeMPEG4

    exportSession?.outputURL = outputUrl

    exportSession?.metadata = asset.metadata

    exportSession?.exportAsynchronously(completionHandler: {
        if (exportSession?.status == .completed)
        {
            print("AV export succeeded.")

           // outputUrl to post Audio on server

        }
        else if (exportSession?.status == .cancelled)
        {
            print("AV export cancelled.")
        }
        else
        {
            print ("Error is \(String(describing: exportSession?.error))")

        }
    })

Upvotes: 14

John Tracid
John Tracid

Reputation: 4046

You can record in MPEG4 AAC directly without addition step for converting. Use AVFormatIDKey with kAudioFormatMPEG4AAC value and reduce sample rate to 8000 or 16000.

Upvotes: 0

Related Questions