2017-02-08 4 views
2

Ich möchte eine iOS-App (mit Swift 3 und Xcode 8.2.1) erstellen, die vom Mikrofon eines iPhone aufzeichnet und speichert den Datensatz in eine .caf Datei.AVAudioFile Init funktioniert auf Simulator aber stürzt auf Gerät


Zuerst habe ich die folgenden Zeilen in die Info.plist Datei von meinem Projekt hinzugefügt:

<key>NSMicrophoneUsageDescription</key> 
<string>Some description to explain why access is required</string> 

Dann habe ich ein einfaches UIViewController erstellt, die eine AVAudioEngine setzt und installieren/Entfernen Sie einen Audio-Tipp, wenn Sie auf die Schaltflächen Record und Stop tippen.

ViewController.swift

import UIKit 
import AVFoundation 

class ViewController: UIViewController { 

    var engine = AVAudioEngine() 
    var file: AVAudioFile? 
    var player = AVAudioPlayerNode() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     guard let url = urlFor(filename: "test.caf") else { return } 
     do { 
      file = try AVAudioFile(forWriting: url, settings: engine.inputNode!.inputFormat(forBus: 0).settings, commonFormat: engine.inputNode!.inputFormat(forBus: 0).commonFormat, interleaved: false) 
     } catch { 
      print("Error: \(error)") 
     } 

     engine.attach(player) 
     engine.connect(player, to: engine.mainMixerNode, format: engine.mainMixerNode.outputFormat(forBus: 0)) 

     do { 
      try engine.start() 
     } catch { 
      print("Error: \(error)") 
     } 
    } 

    @IBAction func record(sender: AnyObject) { 
     engine.inputNode?.installTap(onBus: 0, bufferSize: 1024, format: engine.mainMixerNode.outputFormat(forBus: 0)) { (buffer, time) -> Void in 
      do { 
       try self.file?.write(from: buffer) 
      } catch { 
       print("Error: \(error)") 
      } 
     } 
    } 

    @IBAction func stop(sender: AnyObject) { 
     engine.inputNode?.removeTap(onBus: 0) 
     // Print the url of the saved file 
     let urls = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask) 
     print(urls[urls.endIndex - 1]) 
    } 

    func urlFor(filename: String) -> URL? { 
     // Get url of saved file 
     if let dir = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true).first { 
      let path = dir.appending(filename) 
      return URL(fileURLWithPath: path) 
     } 
     return nil 
    } 

} 

Die App funktioniert auf dem Simulator in Ordnung: Ich habe meine Stimme aufnehmen kann, die URL der aufgenommenen Datei und Test greifen, dass meine Stimme korrekt aufgezeichnet worden. Allerdings stürzt die App ab, sobald ich sie auf einem Gerät starte (getestet mit einem iPhone 6s auf iOS 10.2.1). Die Konsole gibt mir die folgenden Protokolle:

2017-02-08 17:09:14.330151 AudioEngineSimpleApp[7100:2659416] Audio files cannot be non-interleaved. Ignoring setting AVLinearPCMIsNonInterleaved YES. 
2017-02-08 17:09:14.333715 AudioEngineSimpleApp[7100:2659416] [central] 54: ERROR: [0x1b7440c40] >avae> AVAudioFile.mm:160: AVAudioFileImpl: error -54 
2017-02-08 17:09:14.333796 AudioEngineSimpleApp[7100:2659416] [central] 54: ERROR: [0x1b7440c40] >avae> AVAudioFile.mm:122: ReadMagicCookie: error -50 
2017-02-08 17:09:14.334369 AudioEngineSimpleApp[7100:2659416] *** Terminating app due to uncaught exception 'com.apple.coreaudio.avfaudio', reason: 'error -50' 

Dieses Problem scheint AVAudioPlayer working on Simulator but not on Real Device auf den Stack-Überlauf-Thread verwandt zu sein, aber ich kann nicht verstehen, wie seine Antwort in meinen eigenen Code zu implementieren, um mein Problem zu lösen.

Antwort

0

Ich habe meinen Code Refactoring und habe meine alte urlFor(filename: String) -> URL? Methode mit der folgenden url Eigenschaft ersetzt:

let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("test.caf") 

Als Ergebnis arbeitet der folgende Code korrekt auf dem Gerät:

import UIKit 
import AVFoundation 

class ViewController: UIViewController { 

    @IBOutlet weak var recordButton: UIButton! 
    let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("test.caf") 
    //let url = URL(fileURLWithPath: NSTemporaryDirectory().appending("test.caf")) also works 
    var engine = AVAudioEngine() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     recordButton.setTitle("Record", for: .normal) 

     let file: AVAudioFile 
     do { 
      file = try AVAudioFile(forWriting: url, settings: engine.inputNode!.outputFormat(forBus: 0).settings) 
     } catch { 
      print("Error: \(error)") 
      return 
     } 

     engine.inputNode?.installTap(onBus: 0, bufferSize: 1024, format: engine.inputNode!.outputFormat(forBus: 0)) { (buffer, time) -> Void in 
      do { 
       try file.write(from: buffer) 
      } catch { 
       print("Error: \(error)") 
      } 
     } 
    } 

    @IBAction func toggleAction(_ sender: AnyObject) { 
     engine.isRunning ? stopRecording() : startRecording() 
    } 

    func startRecording() { 
     do { 
      // Start engine 
      try engine.start() 

      // Toggle button title 
      recordButton.setTitle("Stop", for: .normal) 
     } catch { 
      print("Error: \(error)") 
     } 
    } 

    func stopRecording() { 
     // Stop engine 
     engine.stop() 

     // Toggle button title 
     recordButton.setTitle("Record", for: .normal) 

     print(url) 
    } 

} 
Verwandte Themen