iOS swift将mp3转换为aac

时间:2015-11-12 14:32:18

标签: ios swift avfoundation

我在Swift中将 mp3 转换为 m4a ,代码基于this

我生成PCM文件时有效。当我将导出格式更改为m4a时,它会生成一个文件,但它不会播放。它为什么腐败?

以下是目前的代码:

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
}


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
}

2 个答案:

答案 0 :(得分:3)

更新

您正在创建caf文件,而不是m4a

AVFileTypeCoreAudioFormat替换为

中的AVFileTypeAppleM4A
AVAssetWriter(URL: self.outputURL, fileType: AVFileTypeCoreAudioFormat)

完成后请致电self.assetWriter.finishWritingWithCompletionHandler()

答案 1 :(得分:2)

将问题中的源代码更新为Swift 4并将其包装在一个类中。对于原始源代码和答案,归功于城堡 Rythmic Fistman 。左边的作者的评论,添加了一些断言和打印语句进行调试。在iOS上测试。

输出文件的比特率以96kb / s硬编码,您可以轻松覆盖此值。我转换的大多数音频文件都是320kb / s,所以我使用这个类来压缩文件以进行离线存储。压缩结果在这个答案的底部。

用法:

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()
}

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
  }
}

输入文件: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
----

输出文件: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)
----