apple/iOS, UIKit, Documentation

SpeechAnalyzer

lgvv 2025. 11. 22. 15:43

SpeechAnalyzer

 

 

음성으로 입력된 오디오 콘텐츠를 다양한 방식으로 분석하고, analysis session을 관리

Actor로 정의되어 있음

 

 

Overview

Speech 프레임워크는 특정 유형의 analysis(분석) 및 transcription(받아쓰기) 기능을 제공하기 위해 analyzer에 추가할 수 있는 여러 모듈을 제공

 

대부분의 사용 사례에서는 음성을 텍스트로 변환하는 SpeechTranscriber 모듈만으로 충분.

 

  • SpeechAnalyzer 역할
    • 관련 모듈을 보관
    • 음성 오디오 입력을 수신
    • 전체 분석 흐름을 제어
  • 각 모듈의 역할
    • 허용되는 입력 형태에 대한 가이던스를 제공
    • 자체 분석 결과 또는 transcription 결과를 출력
  • 비동기 기반으로 흐름 분석
    • Analysis는 비동기로 수행.
    • 입력, 출력, 세션 제어는 서로 분리되어 있고, 일반적으로 사용자가 생성하거나 세션을 생성하는 여러 작업을 통해 이루어짐.
    • Objective-C API가 delegate 통해 결과를 전달하는 방식과 달리 Swift API는 AsnycSequence를 통해 결과를 제공
    • 마찬가지로 음성 입력 역시도 데이터를 넣어주는 AsyncSequence 형태로 제공
  • 입력 처리 제약사항
    • analyzer는 한번에 하나의 입력 시퀀스만 분석할 수 있음.

 

 

Perform analysis

 

오디보 파일 혹은 스트림에 대해서 분석하려면 아래의 단계에 따라서 진행

 

  1. 필요한 모듈을 생성하고 구성
  2. 관련 에셋이 설치되어 있거나 이미 존재하는지 확인. (AssetInventory)
  3. 음성 오디오를 제공할 수 있는 입력 시퀀스를 생성
  4. 생성한 모듈과 입력 시퀀스로 분석기를 생성하고 구성
  5. 오디오를 공급.
  6. 분석을 시작.
  7. 결과를 처리.
  8. 필요 시 분석을 종료.

 

샘플코드

아래는 스텝에 따라 진행되는 샘플 코드

//
//  Transcriber.swift
//  TranscriberExample
//
//  Created by Geon Woo lee on 11/21/25.
//

import Foundation
import Speech

final class Transcriber {
    private var transcriber: SpeechTranscriber!
    private var inputSequence: AsyncStream<AnalyzerInput>!
    private var inputBuilder: AsyncStream<AnalyzerInput>.Continuation!
    
    private var audioFormat: AVAudioFormat!
    private var analyzer: SpeechAnalyzer!
    
    private var lastSampleTime: CMTime?
    
    /// Create and configure the necessary modules.
    func createModules() async {
        // Step 1: Modules
        guard let locale = await SpeechTranscriber.supportedLocale(
            equivalentTo: Locale.current
        ) else {
            return
        }
        let transcriber = SpeechTranscriber(
            locale: locale,
            preset: .transcription
        )
        self.transcriber = transcriber
    }

    /// Ensure the relevant assets are installed or already present. See AssetInventory.
    func ensureAssetsArePresent() async throws {
        // Step 2: Assets
        if let installationRequest = try await AssetInventory.assetInstallationRequest(
            supporting: [transcriber]
        ) {
            try await installationRequest.downloadAndInstall()
        }

    }

    /// Create an input sequence you can use to provide the spoken audio.
    func createInputSequence() {
        // Step 3: Input sequence
        let (inputSequence, inputBuilder) = AsyncStream.makeStream(of: AnalyzerInput.self)
        self.inputSequence = inputSequence
        self.inputBuilder = inputBuilder
    }
    
    /// Create and configure the analyzer with the modules and input sequence.
    func createAnalyzer(with inputSequence: SFSpeechAudioBufferRecognitionRequest) async {
        // Step 4: Analyzer
        let audioFormat = await SpeechAnalyzer.bestAvailableAudioFormat(compatibleWith: [transcriber])
        let analyzer = SpeechAnalyzer(modules: [transcriber])
        
        self.audioFormat = audioFormat
        self.analyzer = analyzer
    }
    
    /// Supply audio.
    func supplyAudio(_ audio: Data, to analyzer: SFSpeechRecognizer) {
        // Step 5: Supply audio
        Task {
            
//            while /* audio remains */ {
//                /* Get some audio */
//                /* Convert to audioFormat */
//                let pcmBuffer = /* an AVAudioPCMBuffer containing some converted audio */
//                let input = AnalyzerInput(buffer: pcmBuffer)
//                inputBuilder.yield(input)
//            }
            
            inputBuilder.finish()
        }

    }

    /// Start analysis.
    func stratAnalysis(using recognizer: SFSpeechRecognizer) async throws {
        // Step 6: Perform analysis
        let lastSampleTime = try await analyzer.analyzeSequence(inputSequence)
        self.lastSampleTime = lastSampleTime
    }

    /// Act on results.
    func actOnResults(_ results: [SFSpeechRecognitionResult]) {
        // Step 7: Act on results
        Task {
            do {
                for try await result in transcriber.results {
                    let bestTranscription = result.text // an AttributedString
                    let plainTextBestTranscription = String(bestTranscription.characters) // a String
                    print(plainTextBestTranscription)
                }
            } catch {
                /* Handle error */
            }
        }
    }

    /// Finish analysis when desired.
    func finishAnalysis() async throws {
        // Step 8: Finish analysis
        if let lastSampleTime {
            try await analyzer.finalizeAndFinish(through: lastSampleTime)
        } else {
            await analyzer.cancelAndFinishNow()
        }
    }
}

 

 

 

 

Analyze audio files (오디오 파일 분석)

 

AVAudioFile 객체로 표현된 하나 이상의 오디오 파일을 분석하기 위해서는

analyzeSequence(from:) 또는 start(inputAudioFile:finishAfterFile:)와 같은 메서드를 호출

또는 파일 파라미터를 갖는 초기화 메서드 중 하나를 사용해 analyzer를 통해 생성할 수 있음.

 

analyzeSequence(from:) 또는 start(inputAudioFile:finishAfterFile:) 메서드는 파일을 자동으로 지원되는 오디오 형식으로 변환하고, 파일 전체를 처리

 

하나의 파일 분석 후 세션을 종료하려면 finishAfterFile 매개변수에 true를 전달하거나 제공된 finish 메서드 중 하나를 호출.

 

그렇지 않으면 기본적으로 분석기는 결과 스트림을 종료하지 않고, 추가 오디오 파일이나 버퍼를 기다림.

분석 세션은 각 파일 후 오디오 타임라인을 초기화하지 않으며, 다음 오디오는 이전 파일이 끝난 직후에 이어진 것으로 간주.

 

 

 

 

Analyze audio buffers (오디오 버퍼 분석)

 

오디오 버퍼를 직접 분석하려면 버퍼를 지원되는 오디오 형식으로 변환해야 하며, 변환은 즉시(on-the-fly) 또는 사전에 미리 수행할 수 있음.

 

bestAvailableAudioFormat(compatibleWith:) 또는 개별 모듈의 availableCompatibleAudioFormats 메서드를 사용하여 변환할 형식을 선택할 수 있음.

 

각 오디오 버퍼에 대해 AnalyzerInput 객체를 생성하고, 이를 사용자가 만든 입력 시퀀스에 추가

생성한 입력 시퀀스를 analyzeSequence(_:), start(inputSequence:) 또는 분석기 초기화 메서드의 유사한 파라미터에 전달

 

오디오 스트림의 일부를 건너뛰고 싶다면, 입력 시퀀스에서 건너뛰고자 하는 버퍼를 제외하면 됨.

이후 나중에 분석을 재개할 때, 각 모듈의 결과가 건너뛴 오디오를 고려하도록 시간 코드를 맞출 수 있음.

 

이를 위해, 이후 버퍼가 오디오 스트림 내에서 시작되는 시간 코드를 해당 AnalyzerInput 객체의 bufferStartTime 파라미터로 전달.

 

 

Analyze autonomously (자율적으로 분석하기)

 

일반적으로는 analyzeSequence(_:) 또는 analyzeSequence(from:) 메서드를 사용해 분석을 수행하는 것이 좋음.

이러한 메서드는 Swift의 구조적 동시성(Structured Concurrency)과 잘 어울림.

 

Analyzer가 자체적으로 관리하는 작업 내에서 새로운 오디오 입력이 도착하는 즉시 자동으로 분석을 진행하도록 하고 싶을 수도 있음.

이 기능을 사용하려면 입력 시퀀스나 파일 파라미터가 포함된 초기화 메서드 중 하나를 사용해 Analyzer를 생성하거나 start(inputSequence:) 또는 start(inputAudioFile:finishAfterFile:) 메서드를 호출

 

입력이 끝날 때 분석을 종료하려면 finalizeAndFinishThroughEndOfInput()을 호출

현재 입력 분석을 끝내고 다른 입력에 대한 분석을 새로 시작하려면 start 계열 메서드를 다시 호출하면 됨.

 

 

결과 처리와 타이밍 제어 (Control processing and timing of results)

 

각 모듈은 주기적으로 결과를 전달하지만, 외부 신호나 특정 타이밍에 맞춰 결과의 처리 및 전달을 수동으로 동기화할 수도 있음.

 

특정 시간 코드(time-code)까지의 결과를 전달하려면 finalize(through:)를 호출.

더 이상 필요하지 않은 시간 이전의 결과 처리를 취소하려면 cancelAnalysis(before:)를 호출.

 

 

 

반응성 향상하기 (Improve responsiveness)

 

기본적으로 analyzer 모듈들은 필요한 시스템 리소스를 지연 로드(lazy load하며 해제될 때 해당 리소스를 언로드(unload)함.

 

모듈을 설정한 후 analyzer를 미리 준비(preheat)하여 시스템 리소스를 선제적으로 로드하려면 prepareToAnalyze(in:)를 호출.

이 작업은 모듈이 초기 결과를 반환하는 속도를 향상시킬 수 있음.

 

다른 분석기 인스턴스가 나중에 재사용할 수 있도록 리소스 언로드를 지연하거나 방지하려면, SpeechAnalyzer.Options.ModelRetention 옵션을 선택하고 해당 옵션이 포함된 SpeechAnalyzer.Options 객체로 analyzer를 생성

 

분석 작업의 우선순위를 설정하려면, 우선순위 값이 지정된 SpeechAnalyzer.Options 객체를 사용해 analyzer를 생성

 

또한, 일부 모듈에서는 반응성을 향상시키는 추가 옵션을 제공하기도 함.

 

 

 

분석 종료하기 (Finish analysis)

 

분석 세션을 종료하려면, 분석기의 finish 계열 메서드나 관련 파라미터를 사용하거나, 분석기 인스턴스를 해제(deallocate)해야 함.

 

분석 세션이 finished 상태로 전환되면 다음과 같은 변화가 발생.

  • 분석기는 입력 시퀀스로부터 더 이상 새로운 입력을 받지 않음.
  • 대부분의 메서드는 더 이상 동작하지 않으며, 특히 새로운 입력 시퀀스나 모듈을 받아들이지 않음.
  • 모듈의 결과 스트림은 종료되며, 모듈은 추가 결과를 발행하지 않음.
  • 단, 앱은 이미 발행된 결과들에 대해서는 계속 반복(iterate)하여 읽을 수 있음.

 

 

참고 (Note)

 

AsyncStream.Continuation.finish()처럼 입력 시퀀스를 종료하는 메서드를 통해 입력 스트림을 끝낼 수는 있지만,

입력 시퀀스를 종료하는 것만으로는 분석 세션이 finished 상태가 되지 않음.

입력 시퀀스가 끝난 후에도, 다른 입력 시퀀스를 제공하여 세션을 계속 진행할 수 있음.

 

즉, 분석 세션의 종료와 스트림의 종료를 다르게 봐야 한다는 의미.

 

 

에러 처리하기 (Respond to errors)

 

분석기 또는 모듈의 결과 스트림에서 오류가 발생, 분석 세션은 앞서 설명한 것처럼 finished 상태로 전환

이때 동일한 오류(또는 CancellationError)가 대기 중인 모든 메서드와 결과 스트림에서 다시 throw