Skip to content

Commit

Permalink
v2.0 ios (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
laves committed Nov 25, 2023
1 parent c5660b0 commit dc1e894
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 78 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ios-perf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ jobs:
device: [ios-perf]
include:
- device: ios-perf
initPerformanceThresholdSec: 1.5
procPerformanceThresholdSec: 0.2
initPerformanceThresholdSec: 2.5
procPerformanceThresholdSec: 0.5

steps:
- name: Checkout
Expand Down
10 changes: 5 additions & 5 deletions binding/ios/Cheetah-iOS.podspec
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
Pod::Spec.new do |s|
s.name = 'Cheetah-iOS'
s.module_name = 'Cheetah'
s.version = '1.1.0'
s.version = '2.0.0'
s.license = {:type => 'Apache 2.0'}
s.summary = 'iOS SDK for Picovoice\'s Cheetah speech-to-text engine.'
s.description =
s.description =
<<-DESC
Cheetah is an on-device streaming speech-to-text engine.
Cheetah is:
- Private, all voice processing runs locally.
- Accurate
Expand All @@ -23,8 +23,8 @@ Pod::Spec.new do |s|
DESC
s.homepage = 'https://github.com/Picovoice/cheetah/tree/master/binding/ios'
s.author = { 'Picovoice' => '[email protected]' }
s.source = { :git => "https://github.com/Picovoice/cheetah.git", :tag => "Cheetah-iOS-v1.1.0" }
s.ios.deployment_target = '9.0'
s.source = { :git => "https://github.com/Picovoice/cheetah.git", :tag => "Cheetah-iOS-v2.0.0" }
s.ios.deployment_target = '13.0'
s.swift_version = '5.0'
s.vendored_frameworks = 'lib/ios/PvCheetah.xcframework'
s.source_files = 'binding/ios/*.{swift}'
Expand Down
88 changes: 61 additions & 27 deletions binding/ios/Cheetah.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Picovoice Inc.
// Copyright 2022-2023 Picovoice Inc.
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
Expand All @@ -23,6 +23,12 @@ public class Cheetah {
/// Current Cheetah version.
public static let version = String(cString: pv_cheetah_version())

private static var sdk = "ios"

public static func setSdk(sdk: String) {
self.sdk = sdk
}

/// Constructor.
///
/// - Parameters:
Expand Down Expand Up @@ -52,13 +58,18 @@ public class Cheetah {
throw CheetahInvalidArgumentError("EndpointDuration must be a non-negative number.")
}

pv_set_sdk(Cheetah.sdk)

let status = pv_cheetah_init(
accessKey,
modelPathArg,
endpointDuration,
enableAutomaticPunctuation,
&handle)
try checkStatus(status, "Cheetah init failed")
if status != PV_STATUS_SUCCESS {
let messageStack = try getMessageStack()
throw pvStatusToCheetahError(status, "Cheetah init failed", messageStack)
}
}

deinit {
Expand Down Expand Up @@ -113,17 +124,20 @@ public class Cheetah {

if pcm.count != Cheetah.frameLength {
throw CheetahInvalidArgumentError(
"Frame of audio data must contain \(Cheetah.frameLength) samples
- given frame contained \(pcm.count)")
"Frame of audio data must contain \(Cheetah.frameLength) samples" +
" - given frame contained \(pcm.count)")
}

var cPartialTranscript: UnsafeMutablePointer<Int8>?
var endPoint = false
let status = pv_cheetah_process(self.handle, pcm, &cPartialTranscript, &endPoint)
try checkStatus(status, "Cheetah process failed")
if status != PV_STATUS_SUCCESS {
let messageStack = try getMessageStack()
throw pvStatusToCheetahError(status, "Cheetah process failed", messageStack)
}

let transcript = String(cString: cPartialTranscript!)
cPartialTranscript?.deallocate()
pv_cheetah_transcript_delete(cPartialTranscript!)

return (transcript, endPoint)
}
Expand All @@ -139,10 +153,13 @@ public class Cheetah {

var cFinalTranscript: UnsafeMutablePointer<Int8>?
let status = pv_cheetah_flush(self.handle, &cFinalTranscript)
try checkStatus(status, "Cheetah flush failed")
if status != PV_STATUS_SUCCESS {
let messageStack = try getMessageStack()
throw pvStatusToCheetahError(status, "Cheetah flush failed", messageStack)
}

let transcript = String(cString: cFinalTranscript!)
cFinalTranscript?.deallocate()
pv_cheetah_transcript_delete(cFinalTranscript!)

return transcript
}
Expand All @@ -161,42 +178,59 @@ public class Cheetah {
}

throw CheetahIOError(
"Could not find file at path '\(filePath)'. If this is a packaged asset,
ensure you have added it to your xcode project."
"Could not find file at path '\(filePath)'. If this is a packaged asset, " +
"ensure you have added it to your xcode project."
)
}

private func checkStatus(_ status: pv_status_t, _ message: String) throws {
if status == PV_STATUS_SUCCESS {
return
}

private func pvStatusToCheetahError(
_ status: pv_status_t,
_ message: String,
_ messageStack: [String] = []) -> CheetahError {
switch status {
case PV_STATUS_OUT_OF_MEMORY:
throw CheetahMemoryError(message)
return CheetahMemoryError(message, messageStack)
case PV_STATUS_IO_ERROR:
throw CheetahIOError(message)
return CheetahIOError(message, messageStack)
case PV_STATUS_INVALID_ARGUMENT:
throw CheetahInvalidArgumentError(message)
return CheetahInvalidArgumentError(message, messageStack)
case PV_STATUS_STOP_ITERATION:
throw CheetahStopIterationError(message)
return CheetahStopIterationError(message, messageStack)
case PV_STATUS_KEY_ERROR:
throw CheetahKeyError(message)
return CheetahKeyError(message, messageStack)
case PV_STATUS_INVALID_STATE:
throw CheetahInvalidStateError(message)
return CheetahInvalidStateError(message, messageStack)
case PV_STATUS_RUNTIME_ERROR:
throw CheetahRuntimeError(message)
return CheetahRuntimeError(message, messageStack)
case PV_STATUS_ACTIVATION_ERROR:
throw CheetahActivationError(message)
return CheetahActivationError(message, messageStack)
case PV_STATUS_ACTIVATION_LIMIT_REACHED:
throw CheetahActivationLimitError(message)
return CheetahActivationLimitError(message, messageStack)
case PV_STATUS_ACTIVATION_THROTTLED:
throw CheetahActivationThrottledError(message)
return CheetahActivationThrottledError(message, messageStack)
case PV_STATUS_ACTIVATION_REFUSED:
throw CheetahActivationRefusedError(message)
return CheetahActivationRefusedError(message, messageStack)
default:
let pvStatusString = String(cString: pv_status_to_string(status))
throw CheetahError("\(pvStatusString): \(message)")
return CheetahError("\(pvStatusString): \(message)", messageStack)
}
}

private func getMessageStack() throws -> [String] {
var messageStackRef: UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>?
var messageStackDepth: Int32 = 0
let status = pv_get_error_stack(&messageStackRef, &messageStackDepth)
if status != PV_STATUS_SUCCESS {
throw pvStatusToCheetahError(status, "Unable to get Cheetah error state")
}

var messageStack: [String] = []
for i in 0..<messageStackDepth {
messageStack.append(String(cString: messageStackRef!.advanced(by: Int(i)).pointee!))
}

pv_free_error_stack(messageStackRef)

return messageStack
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -601,7 +601,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
Expand All @@ -626,7 +626,7 @@
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -656,7 +656,7 @@
INFOPLIST_KEY_UIMainStoryboardFile = Main;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
10 changes: 2 additions & 8 deletions binding/ios/CheetahAppTest/CheetahAppTest/ViewController.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Picovoice Inc.
// Copyright 2022-2023 Picovoice Inc.
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
Expand All @@ -9,10 +9,4 @@

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
}

}
class ViewController: UIViewController { }
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Picovoice Inc.
// Copyright 2022-2023 Picovoice Inc.
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
Expand All @@ -19,14 +19,6 @@ class CheetahDemoUITests: XCTestCase {

let modelURL = Bundle(for: CheetahDemoUITests.self).url(forResource: "cheetah_params", withExtension: "pv")!

override func setUp() {
super.setUp()
}

override func tearDown() {
super.tearDown()
}

override func setUpWithError() throws {
continueAfterFailure = true
}
Expand Down Expand Up @@ -102,4 +94,37 @@ class CheetahDemoUITests: XCTestCase {
func testVersion() throws {
XCTAssertGreaterThan(Cheetah.version, "")
}

func testMessageStack() throws {
var first_error: String = ""
do {
let cheetah = try Cheetah.init(accessKey: "invalid", modelURL: modelURL)
XCTAssertNil(cheetah)
} catch {
first_error = "\(error.localizedDescription)"
XCTAssert(first_error.count < 1024)
}

do {
let cheetah = try Cheetah.init(accessKey: "invalid", modelURL: modelURL)
XCTAssertNil(cheetah)
} catch {
XCTAssert("\(error.localizedDescription)".count == first_error.count)
}
}

func testProcessMessageStack() throws {
let cheetah = try Cheetah(accessKey: accessKey, modelURL: modelURL)
cheetah.delete()

var testPcm: [Int16] = []
testPcm.reserveCapacity(Int(Cheetah.frameLength))

do {
let res = try cheetah.process(testPcm)
XCTAssertNil(res)
} catch {
XCTAssert("\(error.localizedDescription)".count > 0)
}
}
}
8 changes: 4 additions & 4 deletions binding/ios/CheetahAppTest/Podfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
source 'https://cdn.cocoapods.org/'
platform :ios, '9.0'
platform :ios, '13.0'

target 'CheetahAppTest' do
pod 'Cheetah-iOS', '~> 1.1.0'
pod 'Cheetah-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cheetah/v2.0-ios/binding/ios/Cheetah-iOS.podspec'
end

target 'CheetahAppTestUITests' do
pod 'Cheetah-iOS', '~> 1.1.0'
pod 'Cheetah-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cheetah/v2.0-ios/binding/ios/Cheetah-iOS.podspec'
end

target 'PerformanceTest' do
pod 'Cheetah-iOS', '~> 1.1.0'
pod 'Cheetah-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cheetah/v2.0-ios/binding/ios/Cheetah-iOS.podspec'
end
16 changes: 8 additions & 8 deletions binding/ios/CheetahAppTest/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
PODS:
- Cheetah-iOS (1.1.0)
- Cheetah-iOS (2.0.0)

DEPENDENCIES:
- Cheetah-iOS (~> 1.1.0)
- Cheetah-iOS (from `https://raw.githubusercontent.com/Picovoice/cheetah/v2.0-ios/binding/ios/Cheetah-iOS.podspec`)

SPEC REPOS:
trunk:
- Cheetah-iOS
EXTERNAL SOURCES:
Cheetah-iOS:
:podspec: https://raw.githubusercontent.com/Picovoice/cheetah/v2.0-ios/binding/ios/Cheetah-iOS.podspec

SPEC CHECKSUMS:
Cheetah-iOS: 6fb7be693878f5b1dec0ea5b6534fbba30954afc
Cheetah-iOS: d98a5edcbf3b74dda6027aeac6a8c0f5997a47a2

PODFILE CHECKSUM: be70602bbfa54a30183877dfe83545b7f63d2887
PODFILE CHECKSUM: 813d31bb6f15922d6ea3eef044c962b3c3c8dfbe

COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
15 changes: 12 additions & 3 deletions binding/ios/CheetahErrors.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2022 Picovoice Inc.
// Copyright 2022-2023 Picovoice Inc.
// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
// file accompanying this source.
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
Expand All @@ -9,13 +9,22 @@

public class CheetahError: LocalizedError {
private let message: String
private let messageStack: [String]

public init (_ message: String) {
public init (_ message: String, _ messageStack: [String] = []) {
self.message = message
self.messageStack = messageStack
}

public var errorDescription: String? {
return message
var messageString = message
if messageStack.count > 0 {
messageString += ":"
for i in 0..<messageStack.count {
messageString += "\n [\(i)] \(messageStack[i])"
}
}
return messageString
}

public var name: String {
Expand Down
2 changes: 1 addition & 1 deletion demo/ios/CheetahDemo/CheetahDemo/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ViewModel: ObservableObject {

state = UIState.READY
} catch let error as CheetahInvalidArgumentError {
errorMessage = "\(error.localizedDescription)\nEnsure your AccessKey '\(ACCESS_KEY)' is valid."
errorMessage = "\(error.localizedDescription)"
} catch is CheetahActivationError {
errorMessage = "ACCESS_KEY activation error"
} catch is CheetahActivationRefusedError {
Expand Down
Loading

0 comments on commit dc1e894

Please sign in to comment.