Skip to content

Commit

Permalink
iOS vp update (#565)
Browse files Browse the repository at this point in the history
  • Loading branch information
laves authored Aug 3, 2023
1 parent 074fd07 commit 8ae2594
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 95 deletions.
16 changes: 8 additions & 8 deletions binding/ios/Rhino-iOS.podspec
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
Pod::Spec.new do |s|
s.name = 'Rhino-iOS'
s.module_name = 'Rhino'
s.version = '2.2.1'
s.version = '2.2.2'
s.license = {:type => 'Apache 2.0'}
s.summary = 'iOS SDK for Picovoice\'s Rhino Speech-to-Intent engine'
s.description =
s.description =
<<-DESC
Rhino is Picovoice's Speech-to-Intent engine. It directly infers intent from spoken commands within a given context of
interest, in real-time. For example, given a spoken command *"Can I have a small double-shot espresso?"*, Rhino infers that the user wants to order a drink and emits the following inference result:
```json
{
"type": "espresso",
"size": "small",
"numberOfShots": "2"
}
```
Rhino is:
Rhino is:
* using deep neural networks trained in real-world environments.
* compact and computationally-efficient, making it perfect for IoT.
* self-service. Developers and designers can train custom models using [Picovoice Console](https://picovoice.ai/console/).
DESC
s.homepage = 'https://github.com/Picovoice/rhino/tree/master/binding/ios'
s.author = { 'Picovoice' => '[email protected]' }
s.source = { :git => "https://github.com/Picovoice/rhino.git", :tag => "Rhino-iOS-v2.2.1" }
s.source = { :git => "https://github.com/Picovoice/rhino.git", :tag => "Rhino-iOS-v2.2.2" }
s.ios.deployment_target = '11.0'
s.swift_version = '5.0'
s.vendored_frameworks = 'lib/ios/PvRhino.xcframework'
Expand All @@ -35,6 +35,6 @@ Pod::Spec.new do |s|
}
s.source_files = 'binding/ios/*.{swift}'
s.exclude_files = 'binding/ios/RhinoAppTest/**'
s.dependency 'ios-voice-processor', '~> 1.0.2'

s.dependency 'ios-voice-processor', '~> 1.1.0'
end
6 changes: 3 additions & 3 deletions binding/ios/RhinoAppTest/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ source 'https://cdn.cocoapods.org/'
platform :ios, '11.0'

target 'RhinoAppTest' do
pod 'Rhino-iOS', '~> 2.2.1'
pod 'Rhino-iOS', '~> 2.2.2'
end

target 'RhinoAppTestUITests' do
pod 'Rhino-iOS', '~> 2.2.1'
pod 'Rhino-iOS', '~> 2.2.2'
end

target 'PerformanceTest' do
pod 'Rhino-iOS', '~> 2.2.1'
pod 'Rhino-iOS', '~> 2.2.2'
end
16 changes: 8 additions & 8 deletions binding/ios/RhinoAppTest/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
PODS:
- ios-voice-processor (1.0.3)
- Rhino-iOS (2.2.1):
- ios-voice-processor (~> 1.0.2)
- ios-voice-processor (1.1.0)
- Rhino-iOS (2.2.2):
- ios-voice-processor (~> 1.1.0)

DEPENDENCIES:
- Rhino-iOS (~> 2.2.1)
- Rhino-iOS (~> 2.2.2)

SPEC REPOS:
trunk:
- ios-voice-processor
- Rhino-iOS

SPEC CHECKSUMS:
ios-voice-processor: 65b25a8db69ea25ffba0eeef37bae71a982f34cc
Rhino-iOS: c6671667cfda310b8367e7c3611f247cb256f10e
ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1
Rhino-iOS: 0fad86b28d35f67ccb6bd0a2efbbcc0d88b05124

PODFILE CHECKSUM: 851b1a06103d4995d5b7532f8391f06cb5afe66b
PODFILE CHECKSUM: 05ba209bb437f842984821a9bdca751766241044

COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
124 changes: 68 additions & 56 deletions binding/ios/RhinoManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,17 @@

import ios_voice_processor

public enum RhinoManagerError: Error {
case recordingDenied
case objectDisposed
}

/// High-level iOS binding for Rhino Speech-to-Intent engine. It handles recording
/// audio from microphone, processes it in real-time using Rhino, and notifies the client
/// when an intent is inferred from the spoken command.
public class RhinoManager {
private var onInferenceCallback: ((Inference) -> Void)?
private var processErrorCallback: ((Error) -> Void)?

private var rhino: Rhino?

private var started = false
private var stop = false
private var frameListener: VoiceProcessorFrameListener?
private var errorListener: VoiceProcessorErrorListener?

private var isListening = false

public var contextInfo: String {
get {
Expand Down Expand Up @@ -53,7 +49,7 @@ public class RhinoManager {
/// - onInferenceCallback: It is invoked upon completion of intent inference.
/// - processErrorCallback: Invoked if an error occurs while processing frames.
/// If missing, error will be printed to console.
/// - Throws: RhinoManagerError
/// - Throws: RhinoError
public init(
accessKey: String,
contextPath: String,
Expand All @@ -63,8 +59,35 @@ public class RhinoManager {
requireEndpoint: Bool = true,
onInferenceCallback: ((Inference) -> Void)?,
processErrorCallback: ((Error) -> Void)? = nil) throws {
self.onInferenceCallback = onInferenceCallback
self.processErrorCallback = processErrorCallback
self.errorListener = VoiceProcessorErrorListener({ error in
guard let callback = processErrorCallback else {
print("\(error.errorDescription)")
return
}
callback(RhinoError(error.errorDescription))
})

self.frameListener = VoiceProcessorFrameListener({ frame in
guard let rhino = self.rhino else {
return
}

do {
let isFinalized: Bool = try rhino.process(pcm: frame)
if isFinalized {
let inference: Inference = try rhino.getInference()
onInferenceCallback?(inference)
try self.stop()
}
} catch {
guard let callback = processErrorCallback else {
print("\(error)")
return
}
callback(error)
}
})

self.rhino = try Rhino(
accessKey: accessKey,
contextPath: contextPath,
Expand All @@ -74,13 +97,18 @@ public class RhinoManager {
}

deinit {
self.delete()
if self.rhino != nil {
self.rhino!.delete()
self.rhino = nil
}
}

/// Stops recording and releases Rhino resources
public func delete() {
if self.started {
self.stop = true
///
/// - Throws: RhinoError if there was an error stopping RhinoManager
public func delete() throws {
if isListening {
try stop()
}

if self.rhino != nil {
Expand All @@ -93,61 +121,45 @@ public class RhinoManager {
/// from the spoken command. Once the inference is finalized it will invoke the user
/// provided callback and terminates recording audio.
///
/// - Throws: AVAudioSession, AVAudioEngine errors. Additionally RhinoManagerError if
/// microphone permission is not granted or Rhino has been disposed.
/// - Throws: RhinoError if there was an error starting RhinoManager
public func process() throws {
if self.started {
guard !isListening else {
return
}

if rhino == nil {
throw RhinoManagerError.objectDisposed
throw RhinoInvalidStateError("Rhino has been deleted.")
}

// Only check if it's denied, permission will be automatically asked.
guard try VoiceProcessor.shared.hasPermissions() else {
throw RhinoManagerError.recordingDenied
}
VoiceProcessor.instance.addErrorListener(errorListener!)
VoiceProcessor.instance.addFrameListener(frameListener!)

let dispatchQueue = DispatchQueue(label: "RhinoManagerWatcher", qos: .background)
dispatchQueue.async {
while !self.stop {
usleep(10000)
}
VoiceProcessor.shared.stop()

self.started = false
self.stop = false
do {
try VoiceProcessor.instance.start(
frameLength: Rhino.frameLength,
sampleRate: Rhino.sampleRate
)
} catch {
throw RhinoError(error.localizedDescription)
}

try VoiceProcessor.shared.start(
frameLength: Rhino.frameLength,
sampleRate: Rhino.sampleRate,
audioCallback: self.audioCallback
)

self.started = true
isListening = true
}

/// Callback to run after after voice processor processes frames.
private func audioCallback(pcm: [Int16]) {
guard self.rhino != nil else {
private func stop() throws {
guard isListening else {
return
}

do {
let isFinalized: Bool = try self.rhino!.process(pcm: pcm)
if isFinalized {
let inference: Inference = try self.rhino!.getInference()
self.onInferenceCallback?(inference)
self.stop = true
}
} catch {
if self.processErrorCallback != nil {
self.processErrorCallback!(error)
} else {
print("\(error)")
VoiceProcessor.instance.removeErrorListener(errorListener!)
VoiceProcessor.instance.removeFrameListener(frameListener!)

if VoiceProcessor.instance.numFrameListeners == 0 {
do {
try VoiceProcessor.instance.stop()
} catch {
throw RhinoError(error.localizedDescription)
}
}
isListening = false
}
}
4 changes: 2 additions & 2 deletions demo/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
source 'https://cdn.cocoapods.org/'
platform :ios, '11.0'

target 'RhinoDemo' do
pod 'Rhino-iOS', '~> 2.2.1'
target 'RhinoDemo' do
pod 'Rhino-iOS', '~> 2.2.2'
end
16 changes: 8 additions & 8 deletions demo/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
PODS:
- ios-voice-processor (1.0.3)
- Rhino-iOS (2.2.1):
- ios-voice-processor (~> 1.0.2)
- ios-voice-processor (1.1.0)
- Rhino-iOS (2.2.2):
- ios-voice-processor (~> 1.1.0)

DEPENDENCIES:
- Rhino-iOS (~> 2.2.1)
- Rhino-iOS (~> 2.2.2)

SPEC REPOS:
trunk:
- ios-voice-processor
- Rhino-iOS

SPEC CHECKSUMS:
ios-voice-processor: 65b25a8db69ea25ffba0eeef37bae71a982f34cc
Rhino-iOS: c6671667cfda310b8367e7c3611f247cb256f10e
ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1
Rhino-iOS: 0fad86b28d35f67ccb6bd0a2efbbcc0d88b05124

PODFILE CHECKSUM: cc62b80891d9f6f30ca908bf7d14844eceab0f7a
PODFILE CHECKSUM: 51a859aec88810117dab1a555c9742561c3aa12d

COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
46 changes: 36 additions & 10 deletions demo/ios/RhinoDemo/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//

import SwiftUI

import ios_voice_processor
import Rhino

struct SheetView: View {
Expand Down Expand Up @@ -72,6 +74,11 @@ struct ContentView: View {

self.buttonLabel = "START"
}
},
processErrorCallback: { error in
DispatchQueue.main.async {
errorMessage = "\(error)"
}
})
self.contextInfo = self.rhinoManager.contextInfo
} catch let error as RhinoInvalidArgumentError {
Expand All @@ -90,6 +97,22 @@ struct ContentView: View {
}
}

func startListening() {
self.result = ""
if self.rhinoManager == nil {
initRhino()
}

do {
if self.rhinoManager != nil {
try self.rhinoManager.process()
self.buttonLabel = " ... "
}
} catch {
errorMessage = "\(error)"
}
}

var body: some View {
NavigationView {
VStack {
Expand All @@ -110,20 +133,23 @@ struct ContentView: View {
Spacer()
Button {
if self.buttonLabel == "START" {
self.result = ""
if self.rhinoManager == nil {
initRhino()
}
guard VoiceProcessor.hasRecordAudioPermission else {
VoiceProcessor.requestRecordAudioPermission { isGranted in
guard isGranted else {
DispatchQueue.main.async {
self.errorMessage = "Demo requires microphone permission"
}
return
}

do {
if self.rhinoManager != nil {
try self.rhinoManager.process()
self.buttonLabel = " ... "
DispatchQueue.main.async {
self.startListening()
}
}
} catch {
errorMessage = "\(error)"
return
}

startListening()
} else {
self.buttonLabel = "START"
}
Expand Down

0 comments on commit 8ae2594

Please sign in to comment.