Add language identification swiftui demo (#729)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "k2-1024x1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 411 KiB |
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// SherpaOnnxLangID
|
||||
//
|
||||
// Created by knight on 2024/4/1.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject var viewModel = ViewModel()
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("ASR with Next-gen Kaldi")
|
||||
.font(.title)
|
||||
if viewModel.status == .stop {
|
||||
Text("See https://github.com/k2-fsa/sherpa-onnx")
|
||||
Text("Press the Start button to run!")
|
||||
}
|
||||
if viewModel.status == .recording {
|
||||
Text("Stop will show recording language.")
|
||||
}
|
||||
Spacer()
|
||||
Text("Recording language is: \(viewModel.language)")
|
||||
.frame(maxWidth: .infinity)
|
||||
Spacer()
|
||||
Button {
|
||||
toggleRecorder()
|
||||
} label: {
|
||||
Text(viewModel.status == .stop ? "Start" : "Stop")
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
private func toggleRecorder() {
|
||||
Task {
|
||||
await viewModel.toggleRecorder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ContentView()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// SherpaOnnxLangIDApp.swift
|
||||
// SherpaOnnxLangID
|
||||
//
|
||||
// Created by knight on 2024/4/1.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SherpaOnnxLangIDApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
}
|
||||
131
ios-swiftui/SherpaOnnxLangID/SherpaOnnxLangID/ViewModel.swift
Normal file
131
ios-swiftui/SherpaOnnxLangID/SherpaOnnxLangID/ViewModel.swift
Normal file
@@ -0,0 +1,131 @@
|
||||
//
|
||||
// ViewModel.swift
|
||||
// SherpaOnnxLangID
|
||||
//
|
||||
// Created by knight on 2024/4/1.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
|
||||
enum Status {
|
||||
case stop
|
||||
case recording
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class ViewModel:ObservableObject {
|
||||
@Published var status: Status = .stop
|
||||
|
||||
@Published var language: String = ""
|
||||
|
||||
var languageIdentifier: SherpaOnnxSpokenLanguageIdentificationWrapper? = nil
|
||||
var audioEngine: AVAudioEngine? = nil
|
||||
|
||||
var voices: [Float] = []
|
||||
|
||||
init() {
|
||||
initRecorder()
|
||||
initRecognizer()
|
||||
}
|
||||
|
||||
private func initRecognizer() {
|
||||
var config = getLanguageIdentificationTiny()
|
||||
self.languageIdentifier = SherpaOnnxSpokenLanguageIdentificationWrapper(config: &config)
|
||||
}
|
||||
|
||||
private func initRecorder() {
|
||||
print("init recorder")
|
||||
audioEngine = AVAudioEngine()
|
||||
let inputNode = self.audioEngine?.inputNode
|
||||
let bus = 0
|
||||
let inputFormat = inputNode?.outputFormat(forBus: bus)
|
||||
let outputFormat = AVAudioFormat(
|
||||
commonFormat: .pcmFormatFloat32,
|
||||
sampleRate: 16000, channels: 1,
|
||||
interleaved: false)!
|
||||
|
||||
let converter = AVAudioConverter(from: inputFormat!, to: outputFormat)!
|
||||
|
||||
inputNode!.installTap(
|
||||
onBus: bus,
|
||||
bufferSize: 1024,
|
||||
format: inputFormat
|
||||
) {
|
||||
(buffer: AVAudioPCMBuffer, when: AVAudioTime) in
|
||||
var newBufferAvailable = true
|
||||
|
||||
let inputCallback: AVAudioConverterInputBlock = {
|
||||
inNumPackets, outStatus in
|
||||
if newBufferAvailable {
|
||||
outStatus.pointee = .haveData
|
||||
newBufferAvailable = false
|
||||
|
||||
return buffer
|
||||
} else {
|
||||
outStatus.pointee = .noDataNow
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
let convertedBuffer = AVAudioPCMBuffer(
|
||||
pcmFormat: outputFormat,
|
||||
frameCapacity:
|
||||
AVAudioFrameCount(outputFormat.sampleRate)
|
||||
* buffer.frameLength
|
||||
/ AVAudioFrameCount(buffer.format.sampleRate))!
|
||||
|
||||
var error: NSError?
|
||||
let _ = converter.convert(
|
||||
to: convertedBuffer,
|
||||
error: &error, withInputFrom: inputCallback)
|
||||
|
||||
// TODO(fangjun): Handle status != haveData
|
||||
|
||||
let array = convertedBuffer.array()
|
||||
if !array.isEmpty {
|
||||
self.voices.append(contentsOf: array)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleRecorder() async{
|
||||
if status == .stop {
|
||||
await startRecorder()
|
||||
} else {
|
||||
await stopRecorder()
|
||||
}
|
||||
}
|
||||
|
||||
private func startRecorder() async {
|
||||
await MainActor.run {
|
||||
self.language = ""
|
||||
}
|
||||
if !self.voices.isEmpty {
|
||||
self.voices = []
|
||||
}
|
||||
do {
|
||||
try self.audioEngine?.start()
|
||||
status = .recording
|
||||
print("started")
|
||||
} catch let error as NSError {
|
||||
print("Got an error starting audioEngine: \(error.domain), \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func stopRecorder() async {
|
||||
audioEngine?.stop()
|
||||
print("stopped, and begin identify language")
|
||||
await self.identify()
|
||||
status = .stop
|
||||
}
|
||||
|
||||
private func identify() async {
|
||||
let result = self.languageIdentifier? .decode(samples: self.voices)
|
||||
if let language = result?.lang {
|
||||
await MainActor.run {
|
||||
self.language = language
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user