2017-02-08 9 views
2

Я хочу создать приложение iOS (используя Swift 3 и Xcode 8.2.1), который записывает с микрофона iPhone и сохраняет запись в файл .caf.AVAudioFile init работает на симуляторе, но сбой на устройстве


Во-первых, я добавил следующие строки в Info.plist файл моего проекта:

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

Затем я создал простой UIViewController, который устанавливает AVAudioEngine и установить/удалите звуковой отвод при нажатии на Record и Stop кнопок.

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 
    } 

} 

приложение отлично работает на тренажере: я могу записать свой голос, захватить URL записанного файла и тестирования, что мой голос был записан правильно. Тем не менее, приложение вылетает, как только я запускаю его на устройстве (тестируется с iPhone 6s на iOS 10.2.1). Консоль дает мне следующие журналы:

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' 

Эта проблема, как представляется, связано с переполнением стека нити AVAudioPlayer working on Simulator but not on Real Device, но я не могу понять, как реализовать свой ответ на мой собственный код для того, чтобы решить мою проблему.

ответ

0

Я переработан мой код и заменить мой старый urlFor(filename: String) -> URL? метод со следующим свойством: url

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

В результате, следующий код работает на устройстве:

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

} 
Смежные вопросы