You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

visionOS 2.4模拟器中SFSpeechRecognizer语音识别失效求解决方案

visionOS 2.4模拟器中SFSpeechRecognizer语音识别失败求助

已知iOS 17+模拟器的SFSpeechRecognizer存在问题,我在visionOS 2.4模拟器中也遇到了语音无法被识别的情况(推测因复用iOS框架导致)。由于没有Apple Vision Pro实体设备,无法进行真机测试,特向大家寻求该模拟器问题的解决办法或建议。

我使用的环境是Swift 6 + Xcode 16.3,以下是控制台日志及相关代码:

控制台日志

BACKGROUND SPATIAL TAP (hit BackgroundTapPlane) //signal to start recording
SpeechToTextManager.startRecording() called
[0x15388a900|InputElement #0|Initialize] Number of channels = 0 in AudioChannelLayout does not match number of channels = 2 in stream format.
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
iOSSimulatorAudioDevice-22270-1: Abandoning I/O cycle because reconfig pending
SpeechToTextManager.startRecording() completed successfully and recording is active.
GameManager.onTapToggle received. speechToTextManager.isAvailable: true, speechToTextManager.isRecording: true
GameManager received tap toggle callback. Tapped Object: None
BACKGROUND SPATIAL TAP (hit BackgroundTapPlane) // signal to stop recording
GESTURE MANAGER - User is already recording, stopping recording
SpeechToTextManager.stopRecording() called
GameManager.onTapToggle received. speechToTextManager.isAvailable: true, speechToTextManager.isRecording: false
Audio data size: 134400 bytes
Recognition task error: No speech detected <--- 

相关代码

private(set) var isRecording: Bool = false
private var audioSessionConfigured = false
 
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
private let audioProcessor = AudioProcessingActor()
 
@MainActor
func startRecording() async throws {
    logger.debug("SpeechToTextManager.startRecording() called")
 
    guard !isRecording else {
        logger.warning("Cannot start recording: Already recording.")
        throw AppError.alreadyRecording
    }
 
    currentTranscript = ""
    processingError = nil
    audioBuffer = Data()
    isRecording = true
 
    do {
        try await configureAudioSession()
 
        try await Task.detached { [weak self] in
            guard let self = self else {
                throw AppError.internalError(description: "SpeechToTextManager instance deallocated during recording setup.")
            }
 
            try await self.audioProcessor.configureAudioEngine()
 
            let (recognizer, request) = try await MainActor.run { () -> (SFSpeechRecognizer, SFSpeechAudioBufferRecognitionRequest) in
                guard let result = self.createRecognitionRequest() else {
                    throw AppError.configurationError(description: "Speech recognition not available or SFSpeechRecognizer initialization failed.")
                }
                return result
            }
 
            await MainActor.run {
                self.recognitionRequest = request
            }
 
            await MainActor.run {
                self.recognitionTask = recognizer.recognitionTask(with: request) { [weak self] result, error in
                    guard let self = self else { return }
 
                    if let error = error {
                        // WE ENTER INTO THIS BLOCK, ALWAYS
                        self.logger.error("Recognition task error: \(error.localizedDescription)")
                        self.processingError = .speechRecognitionError(description: error.localizedDescription)
                        return
                    }
                . . .
                }
            }
            . . .
        }.value
    } catch {
       . . .
    }
}
 
@MainActor
func stopRecording() {
    logger.debug("SpeechToTextManager.stopRecording() called")
    
    guard isRecording else {
        logger.debug("Not recording, nothing to do")
        return
    }
    
    isRecording = false
    
    Task.detached { [weak self] in
        guard let self = self else { return }
        
        await self.audioProcessor.stopEngine()
        
        let finalBuffer = await self.audioProcessor.getAudioBuffer()
        
        await MainActor.run {
            self.recognitionRequest?.endAudio()
            self.recognitionTask?.cancel()
        }
        . . .
    }
}

private func configureAudioSession() async throws {
    guard !audioSessionConfigured else { return }
    
    logger.debug("Configuring audio session for speech recognition...")
    do {
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.playAndRecord, mode: .default, options: [.duckOthers, .defaultToSpeaker])
        try audioSession.setActive(true)
        audioSessionConfigured = true
        logger.info("Audio session configured successfully")
    } catch {
        logger.error("Failed to configure audio session: \(error.localizedDescription)")
        throw AppError.audioSessionError(description: "Failed to configure audio session: \(error.localizedDescription)")
    }
}

private actor AudioProcessingActor {
    private let audioEngine = AVAudioEngine()
    private var audioBuffer = Data()
    
    func configureAudioEngine() async throws {
        let inputNode = audioEngine.inputNode
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        
        // Install tap for audio buffer collection
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
            // Create a task to handle the buffer on the actor
            Task { [weak self] in
                guard let self = self else { return }
                // Convert buffer to Data and append to audioBuffer
                let channelData = buffer.floatChannelData?[0]
                let frameLength = UInt32(buffer.frameLength)
                
                let data = Data(bytes: channelData!, count: Int(frameLength) * MemoryLayout<Float>.size)
                // Mutate audioBuffer within actor context
                await self.appendToBuffer(data)
            }
        }
        
        audioEngine.prepare()
    }

    private func appendToBuffer(_ data: Data) {
        audioBuffer.append(data)
    }
    
    func startEngine() throws {
        try audioEngine.start()
    }
    
    func stopEngine() {
        audioEngine.stop()
        audioEngine.inputNode.removeTap(onBus: 0)
    }
    . . .
}

内容的提问来源于stack exchange,提问作者cadburia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 01:58:16