최근 제가 만들고 있는 서비스에 STT 기능이 잘 수행되지 않는 문제를 겪었습니다. 이를 파악해보고자 실제 녹음 파일을 텍스트로 변환해 문제의 원인을 실험하는 작업을 진행했습니다.
STT란?
STT(Speech-to-Text)는 음성 신호를 텍스트로 변환하는 기술로, 음성 인식 기술이라고도 합니다. 음성 데이터를 입력으로 받아들이고, 이를 처리하여 사람이 읽을 수 있는 문자 형태의 텍스트로 변환하는 것이 주된 목적입니다.
Speech Framework란?
Speech 프레임워크를 활용하여 실시간 또는 녹음된 오디오에서 텍스트로 변환하는 기능을 추가할 수 있습니다. 이 프레임워크는 키보드의 받아쓰기 기능과 유사하지만, 더 다양한 애플리케이션 시나리오에서 활용 가능합니다. 예를 들어, 음성 명령 인식, 텍스트 받아쓰기, 다국어 음성 인식 등을 손쉽게 구현할 수 있습니다.
SFSpeechRecognizer란?
SFSpeechRecognizer는 음성 인식 서비스의 가용성을 확인하고 음성 인식 프로세스를 시작하는데 사용되는 객체입니다. 음성 인식 서비스를 사용하기 위한 권한 요청, 인식 과정에서 사용할 언어 지정, 새로운 음성 인식 작업 시작과 같은 역할을 수행합니다.
음성 인식 프로세스를 시작하려면 다음과 같은 단계를 수행해야 합니다.
1) 음성 인식을 사용할 수 있는 권한을 요청합니다.
2) SFSpeechRecognizer 객체를 생성합니다.
3) SFSpeechRecognizer 객체의 “isAvailable” 속성을 사용해 서비스의 가용성을 확인합니다.
4) 오디오 콘텐츠를 준비합니다.
5) SFSpeechRecognitionRequest를 상속받는 인식 요청 객체를 생성합니다.
6) recognitionTask(with:delegate:) 또는 recognitionTask(with:resultHandler:) 메서드를 호출하여 인식 프로세스를 시작합니다.
오디오 파일을 이용해 STT 구현해보기
1. 권한 설정하기
Speech Recignition을 사용하기 위해서는 권한 설정이 필요합니다. 따라서
Privacy – Speech Recognition Usage Description을 설정해줍니다.
음성 인식 프로세스는 사용자의 음성을 캡처하고 해당 데이터를 Apple의 서버로 전송하여 처리하는 것을 포함합니다. 이 과정에서 캡처된 오디오는 민감한 사용자 데이터에 해당하기 때문에 이를 보호하기 위해서 사용자로부터 반드시 허가를 받아야 합니다.
2. 음성 파일 가져오기
음성 파일을 가져오기 위해서는 .fileImpoter를 통해서 파일 불러오기를 실행해야 합니다.
func fileImporter(
isPresented: Binding<Bool>,
allowedContentTypes: [UTType],
allowsMultipleSelection: Bool,
onCompletion: @escaping (Result<[URL], any Error>) -> Void
) -> some View
다음과 같은 파라미터를 갖습니다.
isPresented: 인터페이스를 표시할 지 여부를 나타내는 바인딩입니다.
allowedContentTypes: 가져올 수 있는 지원되는 콘텐츠 유형의 목록입니다.
allowsMultipleSelection: 인터페이스에서 사용자가 여러 파일을 선택할 수 있는지 여부를 설정합니다.
onCompletion: 작업이 성공했거나 실패했을 때 호출되는 콜백입니다. URL에 접근하거나 북마크를 만들려면 “startAccessingSecurityScopedResource” 메서드를 호출하고 접근을 해제하려면 “stopAccessingSecurityScopedResource” 메서드를 호출합니다.
struct ContentView: View {
@State private var showFileImporter: Bool = false
@State private var path = URL(string: "")
var body: some View {
VStack {
Button {
showFileImporter = true
} label: {
Text("파일 열기")
}
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.item]) { result in
switch result {
case .success(let url):
let gotAccessURL = url.startAccessingSecurityScopedResource()
if !gotAccessURL { return }
path = url
print(url)
case .failure(let failure):
print(failure)
}
}
}
.padding()
}
}
버튼을 클릭하면 다음과 같은 파일을 선택할 수 있는 화면이 나타납니다.
3. 권한 확인하기
func startTTS() {
SFSpeechRecognizer.requestAuthorization { authStatus in
DispatchQueue.main.async {
if authStatus == .authorized {
print("음성 인식 권한이 부여되었습니다.")
} else {
print("음성 인식 권한이 거부되었습니다.")
}
}
}
}
SFSpeechRecognizer를 사용하기 위해서는 먼저 권한 확인이 필요합니다. 따라서 requestAuthorization 메서드를 통해 권한을 확인합니다.
4. 음성 파일 번역하기
func transcribeFile(url: URL) {
guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ko_KR")) else {
return
}
if !recognizer.isAvailable {
return
}
let request = SFSpeechURLRecognitionRequest(url: url)
recognizer.recognitionTask(with: request) { (result, error) in
guard let result = result else {
print(error!)
return
}
if result.isFinal {
transcript = result.bestTranscription.formattedString
}
}
}
권한을 확인해서 사용할 수 있는 권한이라면 앞서 가져온 파일 url을 통해 번역을 할 수 있습니다.
SFSpeechRecognizer 객체를 한국어 로케일을 사용하여 생성합니다. guard let 구문을 통해 만약 생성되지 않은 경우는 해당 메서드를 종료합니다.
그리고 음성 인식기가 사용 가능한 상태인지 확인하는 절차를 갖습니다.
“SFSpeechURLRecognitionRequest”를 통해 주어진 URL에서 음성을 인식하기 위한 객체를 생성합니다.
recognizer.recognitionTask(with: request) { (result, error) in 메서드를 통해 실제 음성 인식 작업을 진행합니다.
struct ContentView: View {
@State private var showFileImporter: Bool = false
@State private var path = URL(string: "")
@State private var transcript = ""
var body: some View {
VStack {
Text("TTS: \(transcript)")
Button {
showFileImporter = true
} label: {
Text("파일 열기")
}
.fileImporter(isPresented: $showFileImporter, allowedContentTypes: [.item]) { result in
switch result {
case .success(let url):
let gotAccessURL = url.startAccessingSecurityScopedResource()
if !gotAccessURL { return }
path = url
print(url)
case .failure(let failure):
print(failure)
}
}
Button {
startTTS()
} label: {
Text("변환 시작")
}
}
.padding()
}
func startTTS() {
SFSpeechRecognizer.requestAuthorization { authStatus in
DispatchQueue.main.async {
if authStatus == .authorized {
print("음성 인식 권한이 부여되었습니다.")
transcribeFile(url: path!)
} else {
print("음성 인식 권한이 거부되었습니다.")
}
}
}
}
func transcribeFile(url: URL) {
guard let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "ko_KR")) else {
return
}
if !recognizer.isAvailable {
return
}
let request = SFSpeechURLRecognitionRequest(url: url)
recognizer.recognitionTask(with: request) { (result, error) in
guard let result = result else {
print(error!)
return
}
if result.isFinal {
transcript = result.bestTranscription.formattedString
}
}
}
}
실행해보면 음성파일이 텍스트로 바뀌는 것을 확인할 수 있습니다.