Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS swift convert mp3 to aac

I'm converting an mp3 to m4a in Swift with code based on this.

It works when I generate a PCM file. When I change the export format to m4a it generates a file but it won't play. Why is it corrupt?

Here is the code so far:

import AVFoundation
import UIKit

class ViewController: UIViewController {

    var rwAudioSerializationQueue:dispatch_queue_t!

    var asset:AVAsset!

    var assetReader:AVAssetReader!

    var assetReaderAudioOutput:AVAssetReaderTrackOutput!

    var assetWriter:AVAssetWriter!

    var assetWriterAudioInput:AVAssetWriterInput!

    var outputURL:NSURL!

    override func viewDidLoad() {
        super.viewDidLoad()

        let rwAudioSerializationQueueDescription = String(self) + " rw audio serialization queue"

        // Create the serialization queue to use for reading and writing the audio data.
        self.rwAudioSerializationQueue = dispatch_queue_create(rwAudioSerializationQueueDescription, nil)

        let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
        let documentsPath = paths[0]

        print(NSBundle.mainBundle().pathForResource("input", ofType: "mp3"))

        self.asset = AVAsset(URL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("input", ofType: "mp3")! ))

        self.outputURL = NSURL(fileURLWithPath: documentsPath + "/output.m4a")

        print(self.outputURL)

      //  [self.asset loadValuesAsynchronouslyForKeys:@[@"tracks"] completionHandler:^{

        self.asset.loadValuesAsynchronouslyForKeys(["tracks"], completionHandler: {

            print("loaded")

            var success = true
            var localError:NSError?

            success = (self.asset.statusOfValueForKey("tracks", error: &localError) == AVKeyValueStatus.Loaded)

            // Check for success of loading the assets tracks.
            //success = ([self.asset statusOfValueForKey:@"tracks" error:&localError] == AVKeyValueStatusLoaded);
            if (success)
            {
                // If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.

                let fm = NSFileManager.defaultManager()
                let localOutputPath = self.outputURL.path
                if (fm.fileExistsAtPath(localOutputPath!)) {
                    do {
                        try fm.removeItemAtPath(localOutputPath!)
                        success = true
                    } catch {

                    }
                }
            }
            if (success) {
                success = self.setupAssetReaderAndAssetWriter()
            }
            if (success) {
                success = self.startAssetReaderAndWriter()
            }
        })
    }

    func setupAssetReaderAndAssetWriter() -> Bool {

        do {
            try self.assetReader = AVAssetReader(asset: self.asset)
        } catch {

        }

        do {
            try self.assetWriter = AVAssetWriter(URL: self.outputURL, fileType: AVFileTypeCoreAudioFormat)
        } catch {

        }

        var assetAudioTrack:AVAssetTrack? = nil
        let audioTracks = self.asset.tracksWithMediaType(AVMediaTypeAudio)

        if (audioTracks.count > 0) {
            assetAudioTrack = audioTracks[0]
        }

        if (assetAudioTrack != nil)
        {
            let decompressionAudioSettings:[String : AnyObject] = [
                AVFormatIDKey:Int(kAudioFormatLinearPCM)
            ]

            self.assetReaderAudioOutput = AVAssetReaderTrackOutput(track: assetAudioTrack!, outputSettings: decompressionAudioSettings)

            self.assetReader.addOutput(self.assetReaderAudioOutput)

            var channelLayout = AudioChannelLayout()
            memset(&channelLayout, 0, sizeof(AudioChannelLayout));
            channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

            /*let compressionAudioSettings:[String : AnyObject] = [
                AVFormatIDKey:Int(kAudioFormatMPEG4AAC) ,
                AVEncoderBitRateKey:128000,
                AVSampleRateKey:44100 ,
               // AVEncoderBitRatePerChannelKey:16,
               // AVEncoderAudioQualityKey:AVAudioQuality.High.rawValue,
                AVNumberOfChannelsKey:2,
                AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout))
            ]

            var outputSettings:[String : AnyObject] = [
                AVFormatIDKey: Int(kAudioFormatLinearPCM),
                AVSampleRateKey: 44100,
                AVNumberOfChannelsKey: 2,
                AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout)),
                AVLinearPCMBitDepthKey: 16,
                AVLinearPCMIsNonInterleaved: false,
                AVLinearPCMIsFloatKey: false,
                AVLinearPCMIsBigEndianKey: false
            ]*/

            let outputSettings:[String : AnyObject] = [
                AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
                AVSampleRateKey: 44100,
                AVNumberOfChannelsKey: 2,
                AVChannelLayoutKey: NSData(bytes:&channelLayout, length:sizeof(AudioChannelLayout))            ]

            self.assetWriterAudioInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: outputSettings)

            self.assetWriter.addInput(self.assetWriterAudioInput)
        }

        return true
    }

    func startAssetReaderAndWriter() -> Bool {

        self.assetWriter.startWriting()

        self.assetReader.startReading()

        self.assetWriter.startSessionAtSourceTime(kCMTimeZero)

        self.assetWriterAudioInput.requestMediaDataWhenReadyOnQueue(self.rwAudioSerializationQueue, usingBlock: {

            while (self.assetWriterAudioInput.readyForMoreMediaData ) {

                var sampleBuffer = self.assetReaderAudioOutput.copyNextSampleBuffer()

                if (sampleBuffer != nil) {
                    self.assetWriterAudioInput.appendSampleBuffer(sampleBuffer!)

                    sampleBuffer = nil

                } else {
                    self.assetWriterAudioInput.markAsFinished()
                    self.assetReader.cancelReading()
                    print("done")
                   break
                }
            }
        })

        return true
    }
}
like image 641
Castles Avatar asked Nov 12 '15 14:11

Castles


People also ask

Can MP3 be converted to AAC?

How to convert a MP3 to a AAC file? Choose the MP3 file that you want to convert. Select AAC as the the format you want to convert your MP3 file to. Click "Convert" to convert your MP3 file.

Does converting MP3 to AAC improve quality?

Although AAC has better sound quality than MP3, converting an MP3 to AAC will not improve sound quality. This is because conversion of data from a lower quality to a higher quality requires a high quality of sound in the file to begin with.


2 Answers

Updated the source code in the question to Swift 4 and wrapped it in a class. Credit goes to Castles and Rythmic Fistman for original source code and answer. Left author's comments, added a few assertion's and print statements for debugging. Tested on iOS.

The bit rate for the output file is hardcoded at 96kb/s, you can easily override this value. Most of the audio files I'm converting are 320kb/s, so I'm using this class to compress the files for offline storage. Compression results at the bottom of this answer.

Usage:

let inputFilePath = URL(fileURLWithPath: "/path/to/file.mp3")
let outputFileURL = URL(fileURLWithPath: "/path/to/output/compressed.mp4")

if let audioConverter = AVAudioFileConverter(inputFileURL: inputFilePath, outputFileURL: outputFileURL) {
    audioConverter.convert()
}

Class

import AVFoundation

final class AVAudioFileConverter {

  var rwAudioSerializationQueue: DispatchQueue!
  var asset:AVAsset!
  var assetReader:AVAssetReader!
  var assetReaderAudioOutput:AVAssetReaderTrackOutput!
  var assetWriter:AVAssetWriter!
  var assetWriterAudioInput:AVAssetWriterInput!
  var outputURL:URL
  var inputURL:URL

  init?(inputFileURL: URL, outputFileURL: URL) {
    inputURL = inputFileURL
    outputURL = outputFileURL

    if (FileManager.default.fileExists(atPath: inputURL.absoluteString)) {
      print("Input file does not exist at file path \(inputURL.absoluteString)")
      return nil
    }
  }

  func convert() {
    let rwAudioSerializationQueueDescription = " rw audio serialization queue"
    // Create the serialization queue to use for reading and writing the audio data.
    rwAudioSerializationQueue = DispatchQueue(label: rwAudioSerializationQueueDescription)
    assert(rwAudioSerializationQueue != nil, "Failed to initialize Dispatch Queue")

    asset = AVAsset(url: inputURL)
    assert(asset != nil, "Error creating AVAsset from input URL")
    print("Output file path -> ", outputURL.absoluteString)

    asset.loadValuesAsynchronously(forKeys: ["tracks"], completionHandler: {
      var success = true
      var localError:NSError?
      success = (self.asset.statusOfValue(forKey: "tracks", error: &localError) == AVKeyValueStatus.loaded)
      // Check for success of loading the assets tracks.
      if (success) {
        // If the tracks loaded successfully, make sure that no file exists at the output path for the asset writer.
        let fm = FileManager.default
        let localOutputPath = self.outputURL.path
        if (fm.fileExists(atPath: localOutputPath)) {
          do {
            try fm.removeItem(atPath: localOutputPath)
            success = true
          } catch {
            print("Error trying to remove output file at path -> \(localOutputPath)")
          }
        }
      }

      if (success) {
        success = self.setupAssetReaderAndAssetWriter()
      } else {
        print("Failed setting up Asset Reader and Writer")
      }
      if (success) {
        success = self.startAssetReaderAndWriter()
        return
      } else {
        print("Failed to start Asset Reader and Writer")
      }

    })
  }

  func setupAssetReaderAndAssetWriter() -> Bool {
    do {
      assetReader = try AVAssetReader(asset: asset)
    } catch {
      print("Error Creating AVAssetReader")
    }

    do {
      assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.m4a)
    } catch {
      print("Error Creating AVAssetWriter")
    }

    var assetAudioTrack:AVAssetTrack? = nil
    let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)

    if (audioTracks.count > 0) {
      assetAudioTrack = audioTracks[0]
    }

    if (assetAudioTrack != nil) {

      let decompressionAudioSettings:[String : Any] = [
        AVFormatIDKey:Int(kAudioFormatLinearPCM)
      ]

      assetReaderAudioOutput = AVAssetReaderTrackOutput(track: assetAudioTrack!, outputSettings: decompressionAudioSettings)
      assert(assetReaderAudioOutput != nil, "Failed to initialize AVAssetReaderTrackOutout")
      assetReader.add(assetReaderAudioOutput)

      var channelLayout = AudioChannelLayout()
      memset(&channelLayout, 0, MemoryLayout<AudioChannelLayout>.size);
      channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

      let outputSettings:[String : Any] = [
        AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
        AVSampleRateKey: 44100,
        AVEncoderBitRateKey: 96000,
        AVNumberOfChannelsKey: 2,
        AVChannelLayoutKey: NSData(bytes:&channelLayout, length:MemoryLayout<AudioChannelLayout>.size)]

      assetWriterAudioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: outputSettings)
      assert(rwAudioSerializationQueue != nil, "Failed to initialize AVAssetWriterInput")
      assetWriter.add(assetWriterAudioInput)

    }
    print("Finsihed Setup of AVAssetReader and AVAssetWriter")
    return true
  }

  func startAssetReaderAndWriter() -> Bool {
    print("STARTING ASSET WRITER")
    assetWriter.startWriting()
    assetReader.startReading()
    assetWriter.startSession(atSourceTime: kCMTimeZero)

    assetWriterAudioInput.requestMediaDataWhenReady(on: rwAudioSerializationQueue, using: {

      while(self.assetWriterAudioInput.isReadyForMoreMediaData ) {
        var sampleBuffer = self.assetReaderAudioOutput.copyNextSampleBuffer()
        if(sampleBuffer != nil) {
          self.assetWriterAudioInput.append(sampleBuffer!)
          sampleBuffer = nil
        } else {
          self.assetWriterAudioInput.markAsFinished()
          self.assetReader.cancelReading()
          self.assetWriter.finishWriting {
            print("Asset Writer Finished Writing")
          }
          break
        }
      }
    })
    return true
  }
}

Input File: 17.3 MB

// generated with afinfo on mac
File:           D290A73C37B777F1.mp3
File type ID:   MPG3
Num Tracks:     1
----
Data format:     2 ch,  44100 Hz, '.mp3' (0x00000000) 0 bits/channel, 0 bytes/packet, 1152 frames/packet, 0 bytes/frame
                no channel layout.
estimated duration: 424.542025 sec
audio bytes: 16981681
audio packets: 16252
bit rate: 320000 bits per second
packet size upper bound: 1052
maximum packet size: 1045
audio data file offset: 322431
optimized
audio 18720450 valid frames + 576 priming + 1278 remainder = 18722304
----

Output File: 5.1 MB

// generated with afinfo on Mac
File:           compressed.m4a
File type ID:   m4af
Num Tracks:     1
----
Data format:     2 ch,  44100 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
Channel layout: Stereo (L R)
estimated duration: 424.542041 sec
audio bytes: 5019294
audio packets: 18286
bit rate: 94569 bits per second
packet size upper bound: 763
maximum packet size: 763
audio data file offset: 44
not optimized
audio 18722304 valid frames + 2112 priming + 448 remainder = 18724864
format list:
[ 0] format:    2 ch,  44100 Hz, 'aac ' (0x00000000) 0 bits/channel, 0 bytes/packet, 1024 frames/packet, 0 bytes/frame
Channel layout: Stereo (L R)
----
like image 169
brightintro Avatar answered Oct 07 '22 07:10

brightintro


update

You're creating a caf file instead of an m4a.

Replace AVFileTypeCoreAudioFormat with AVFileTypeAppleM4A in

AVAssetWriter(URL: self.outputURL, fileType: AVFileTypeCoreAudioFormat)

Call self.assetWriter.finishWritingWithCompletionHandler() when you've finished.

like image 4
Rhythmic Fistman Avatar answered Oct 07 '22 08:10

Rhythmic Fistman