簡體   English   中英

如果用戶已授予麥克風訪問權限,則無法在藍牙上收聽 iOS 應用程序

[英]Cannot listen to iOS app on Bluetooth if user has granted microphone access

我正在開發一個 iOS 14 應用程序,它播放音頻文件的片段供用戶模仿。 如果用戶願意,應用程序可以記錄用戶的反應並立即播放。 用戶還可以導出包含原始片段以及用戶響應的錄音。

我正在使用 AudioKit 4.11

因為用戶可能永遠不想利用應用程序的錄制功能,所以應用程序最初采用音頻 session 類別 of.playback。 如果用戶想要使用錄音功能,應用程序會觸發標准的 Apple 對話框以請求麥克風訪問權限,如果獲得許可,則將 session 類別切換為.playAndRecord。

我發現當 session 類別是.playback 並且用戶尚未授予麥克風權限時,我可以在藍牙揚聲器上或在我的 Jabra Elite 65t 藍牙耳塞上收聽應用程序的 output,當應用程序在真正的 iPhone。 在下面的示例中,這是應用程序首次運行並且用戶僅點擊“播放聲音”或“停止”的情況。

但是,一旦我點擊“播放聲音並記錄響應”並授予麥克風訪問權限,我就無法在藍牙設備上收聽應用程序的 output,無論當時適用的 session 類別是否為.playback(點擊“播放聲音並記錄響應”)或 .playAndRecord(點擊“播放聲音”后)-除非我隨后將 go 設置為手機的隱私設置並將麥克風訪問切換為關閉。 只能通過手機的揚聲器或插入的耳機進行播放。

在設置 session 類別 of.playAndRecord 時,我嘗試調用 .allowBluetoothA2DP 選項。 Apple 的建議意味着這應該允許我在上述情況下通過藍牙收聽我的應用程序的聲音(參見https://developer.apple.com/documentation/avfoundation/avaudiosession/categoryoptions/1771735-allowbluetootha2dp )。 但是,我還沒有發現這種情況。

下面的代碼代表一個可運行的應用程序(盡管需要 AudioKit 4.11 的存在),它以簡化的形式說明了問題。 此處未顯示的唯一元素是我添加到 info.plist 的 NSMicrophoneUsageDescription,以及我導入到項目中的文件“blues.mp3”。

內容視圖:

import SwiftUI
import AudioKit
import AVFoundation

struct ContentView: View {

private var pr = PlayerRecorder()

var body: some View {
    VStack{
        Text("Play sound").onTapGesture{
            pr.setupforPlay()
            pr.playSound()
        }
        .padding()
        Text("Play sound and record response").onTapGesture{
            if recordingIsAllowed() {
                pr.activatePlayAndRecord()
                pr.startSoundAndResponseRecording()
            }
        }
        .padding()
        Text("Stop").onTapGesture{
            pr.stop()
        }
        .padding()
        
    }
}

func recordingIsAllowed() -> Bool {
    
    var retval = false
    AVAudioSession.sharedInstance().requestRecordPermission { granted in
        retval = granted
    }
    return retval
}

}

播放器記錄器:

import Foundation
import AudioKit

class PlayerRecorder {

private var mic: AKMicrophone!
private var micBooster: AKBooster!
private var mixer: AKMixer!
private var outputBooster: AKBooster!
private var player: AKPlayer!
private var playerBooster: AKBooster!
private var recorder: AKNodeRecorder!
private var soundFile: AKAudioFile!
private var twentySecondTimer = Timer()

init() {
    AKSettings.defaultToSpeaker = true
    AKSettings.disableAudioSessionDeactivationOnStop = true
    AKSettings.notificationsEnabled = true
}

func activatePlayAndRecord() {
    do {
        try AKManager.shutdown()
    } catch {
        print("Shutdown failed")
    }
    setupForPlayAndRecord()
}


func playSound() {
    do {
        soundFile = try AKAudioFile(readFileName: "blues.mp3")
    } catch {
        print("Failed to open sound file")
    }
    do {
        try player.load(audioFile: soundFile!)
    } catch {
        print("Player failed to load sound file")
    }
    if micBooster != nil{
        micBooster.gain = 0.0
    }
    player.play()
}


func setupforPlay() {
    do {
        try AKSettings.setSession(category: .playback)
    } catch {
        print("Failed to set session category to .playback")
    }
    mixer = AKMixer()
    outputBooster = AKBooster(mixer)
    player = AKPlayer()
    playerBooster = AKBooster(player)
    playerBooster >>> mixer
    AKManager.output = outputBooster
    if !AKManager.engine.isRunning {
        try? AKManager.start()
    }
}


func setupForPlayAndRecord() {
    AKSettings.audioInputEnabled = true
    do {
        try AKSettings.setSession(category: .playAndRecord)
        /*  Have tried the following instead of the line above, but without success
         let options: AVAudioSession.CategoryOptions = [.allowBluetoothA2DP]
         try AKSettings.setSession(category: .playAndRecord, options: options.rawValue)
         
         Have also tried:
         try AKSettings.setSession(category: .multiRoute)
         */
    } catch {
        print("Failed to set session category to .playAndRecord")
    }
    
    mic = AKMicrophone()
    micBooster = AKBooster(mic)
    mixer = AKMixer()
    outputBooster = AKBooster(mixer)
    player = AKPlayer()
    playerBooster = AKBooster(player)
    mic >>> micBooster
    micBooster >>> mixer
    playerBooster >>> mixer
    AKManager.output = outputBooster
    micBooster.gain = 0.0
    outputBooster.gain = 1.0
    if !AKManager.engine.isRunning {
        try? AKManager.start()
    }
}

func startSoundAndResponseRecording() {
    // Start player and recorder. After 20 seconds, call a function that stops the player
    // (while allowing recording to continue until user taps Stop button).
    
    activatePlayAndRecord()
    playSound()
    
    // Force removal of any tap not previously removed with stop() call for recorder
    var mixerNode: AKNode?
    mixerNode = mixer
    for i in 0..<8 {
        mixerNode?.avAudioUnitOrNode.removeTap(onBus: i)
    }
    
    do {
        recorder = try? AKNodeRecorder(node: mixer)
        try recorder.record()
    } catch {
        print("Failed to start recorder")
    }
    twentySecondTimer = Timer.scheduledTimer(timeInterval: 20.0, target: self, selector: #selector(stopPlayerOnly), userInfo: nil, repeats: false)
}


func stop(){
    twentySecondTimer.invalidate()
    if player.isPlaying {
        player.stop()
    }
    if recorder != nil {
        if recorder.isRecording {
            recorder.stop()
        }
    }
    if AKManager.engine.isRunning {
        do {
            try AKManager.stop()
        } catch {
            print("Error occurred while stopping engine.")
        }
    }
    print("Stopped")
}

@objc func stopPlayerOnly () {
    player.stop()
    if !mic.isStarted {
        mic.start()
    }
    if !micBooster.isStarted {
        micBooster.start()
    }
    mic.volume = 1.0
    micBooster.gain = 1.0
    outputBooster.gain = 0.0
}
}

setupForPlayAndRecord() 開頭附近的三行代碼解決了這個問題:

func setupForPlayAndRecord() {
    AKSettings.audioInputEnabled = true
    // Adding the following three lines solves the problem
    AKSettings.useBluetooth = true
    let categoryOptions: AVAudioSession.CategoryOptions = [.allowBluetoothA2DP]
    AKSettings.bluetoothOptions = categoryOptions
    do {
        try AKSettings.setSession(category: .playAndRecord)
    } catch {
        print("Failed to set session category to .playAndRecord")
    }
    mic = AKMicrophone()
    micBooster = AKBooster(mic)
    mixer = AKMixer()
    outputBooster = AKBooster(mixer)
    player = AKPlayer()
    playerBooster = AKBooster(player)
    mic >>> micBooster
    micBooster >>> mixer
    playerBooster >>> mixer
    AKManager.output = outputBooster
    micBooster.gain = 0.0
    outputBooster.gain = 1.0
    if !AKManager.engine.isRunning {
        try? AKManager.start()
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM