在iOS 10中使异步调用有效同步的最佳方法

时间:2017-02-08 18:08:08

标签: ios asynchronous audio swift3 ios10

为了更好地解释我的情况我尝试制作一个应用程序,当按下按钮时会发出ping噪音,然后立即录制并转录用户的声音。

对于使用系统声音服务的ping声音,使用AudioToolbox录制音频,并使用语音工具包转录它。

我认为我的问题的关键在于异步系统声音服务播放功能的时间:

    //Button pressed function

    let audiosession = AVAudioSession.sharedInstance()

    let filename = "Ping"
    let ext = "wav"

    if let soundUrl = Bundle.main.url(forResource: filename, withExtension: ext){

        var soundId: SystemSoundID = 0
        AudioServicesCreateSystemSoundID(soundUrl as CFURL, &soundId)

        AudioServicesAddSystemSoundCompletion(soundId, nil, nil, {(soundid,_) -> Void in 
        AudioServicesDisposeSystemSoundID(soundid)
        print("Sound played!")}, nil)

        AudioServicesPlaySystemSound(soundId)
    }

    do{
        try audiosession.setCategory(AVAudioSessionCategoryRecord)
        try audiosession.setMode(AVAudioSessionModeMeasurement)
        try audiosession.setActive(true, with: .notifyOthersOnDeactivation)
        print("Changing modes!")

    }catch{
        print("error with audio session")
    }

    recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

    guard let inputNode = audioEngine.inputNode else{
        fatalError("Audio engine has no input node!")
    }

    guard let recognitionRequest = recognitionRequest else{
        fatalError("Unable to create a speech audio buffer recognition request object")
    }

    recognitionRequest.shouldReportPartialResults = true

    recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, delegate: self)

    let recordingFormat = inputNode.outputFormat(forBus: 0)
    inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in
        self.recognitionRequest?.append(buffer)
    }

    audioEngine.prepare()

    do{
        try audioEngine.start()
        delegate?.didStartRecording()

    }catch{
        print("audioEngine couldn't start because of an error")
    }

运行此代码时会发生什么,它会记录语音并成功转录。但是ping永远不会播放。我在那里的两个(非错误)打印语句按顺序激活:

  1. 改变模式!
  2. 播放声音!
  3. 根据我的理解,没有播放ping声音的原因是因为它实际完成时我已经将音频会话类别从播放更改为记录。为了验证这是真的,我尝试删除除声音服务ping之外的所有内容,它会按预期播放声音。

    所以我的问题是绕过AudioServicesPlaySystemSound调用的异步性质的最佳方法是什么?我尝试过将self传递给完成函数,所以我可以让它在我的类中触发一个函数然后运行记录块。但是,我还没有弄清楚如何将self转换为UnsafeMutableRawPointer,因此它可以作为clientData传递。此外,即使我知道如何做到这一点,我也不确定它是否是一个好主意或该参数的预期用途。

    或者,我可以依靠通知中心之类的东西来解决这个问题。但再一次,这似乎是解决问题的一种非常笨重的方式,我最后会后悔。

    有谁知道处理这种情况的正确方法是什么?

    更新

    根据Gruntcake的要求,这是我尝试在完成区中访问自己。

    首先,我创建一个userData常量,它是对self的一个UnsafeMutableRawPointer:

        var me = self
        let userData = withUnsafePointer(to: &me) { ptr in
            return unsafeBitCast(ptr, to: UnsafeMutableRawPointer.self)
    

    接下来,我在回调块中使用该常量,并尝试从中访问self:

        AudioServicesAddSystemSoundCompletion(soundId, nil, nil, {(sounded,me) -> Void in 
            AudioServicesDisposeSystemSoundID(sounded)
            let myself = Unmanaged<myclassname>.fromOpaque(me!).takeRetainedValue()
            myself.doOtherStuff()
            print("Sound played!")}, userData)
    

1 个答案:

答案 0 :(得分:1)

您尝试在完成块中调用doOtherStuff()是一种正确的方法(唯一的另一种是通知,这是唯一的两个选项)

在这种情况下使它复杂化的是从Obj-C到Swift的桥接是必要的。这样做的代码是:

let myData = unsafeBitCast(self, UnsafeMutablePointer<Void>.self)
AudioServicesAddSystemSoundCompletion(YOUR_SOUND_ID, CFRunLoopGetMain(), kCFRunLoopDefaultMode,{ (mSound, mVoid) in
        let me = unsafeBitCast(mVoid, YOURCURRENTCLASS.self)
        //me it is your current object so if yo have a variable like
        // var someVar you can do
        print(me.someVar)
    }, myData)

信用:此代码取自对此问题的回答,但不是接受的答案:

How do I implement AudioServicesSystemSoundCompletionProc in Swift?