From 3cc674012782d82b045f5b1f22aa62d9215e27d8 Mon Sep 17 00:00:00 2001 From: Ian Date: Tue, 24 Oct 2023 13:07:39 -0700 Subject: [PATCH 1/3] add binding --- binding/ios/Cobra-iOS.podspec | 6 ++-- binding/ios/Cobra.swift | 67 ++++++++++++++++++++++++++--------- binding/ios/CobraErrors.swift | 19 +++++++--- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/binding/ios/Cobra-iOS.podspec b/binding/ios/Cobra-iOS.podspec index 1a585e37..e44195e3 100644 --- a/binding/ios/Cobra-iOS.podspec +++ b/binding/ios/Cobra-iOS.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |s| s.name = 'Cobra-iOS' s.module_name = 'Cobra' - s.version = '1.2.0' + s.version = '2.0.0' s.license = {:type => 'Apache 2.0'} s.summary = 'iOS binding for Picovoice\'s Cobra voice activity detection (VAD) engine.' - s.description = + s.description = <<-DESC Made in Vancouver, Canada by [Picovoice](https://picovoice.ai) @@ -12,7 +12,7 @@ Pod::Spec.new do |s| DESC s.homepage = 'https://github.com/Picovoice/cobra/tree/master/binding/ios' s.author = { 'Picovoice' => 'hello@picovoice.ai' } - s.source = { :git => "https://github.com/Picovoice/cobra.git", :tag => "Cobra-iOS-v1.2.0" } + s.source = { :git => "https://github.com/Picovoice/cobra.git", :tag => "Cobra-iOS-v2.0.0" } s.ios.deployment_target = '11.0' s.swift_version = '5.0' s.vendored_frameworks = 'lib/ios/PvCobra.xcframework' diff --git a/binding/ios/Cobra.swift b/binding/ios/Cobra.swift index 84184f9e..15dc6693 100644 --- a/binding/ios/Cobra.swift +++ b/binding/ios/Cobra.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 Picovoice Inc. +// Copyright 2021-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 @@ -27,15 +27,24 @@ public class Cobra { /// Cobra version string public static let version = String(cString: pv_cobra_version()) + private static var sdk = "ios" + + public static func setSdk(sdk: String) { + self.sdk = sdk + } + /// Constructor. /// /// - Parameters: /// - accessKey: AccessKey obtained from the Picovoice Console (https://console.picovoice.ai/) /// - Throws: CobraError public init(accessKey: String) throws { + pv_set_sdk(Cobra.sdk) + let status = pv_cobra_init(accessKey, &handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToCobraError(status, "Cobra init failed") + let messageStack = try getMessageStack() + throw pvStatusToCobraError(status, "Cobra init failed", messageStack) } } @@ -60,6 +69,10 @@ public class Cobra { /// - Returns: Probability of voice activity. It is a floating-point number within [0, 1]. /// - Throws: CobraError public func process(pcm: [Int16]) throws -> Float32 { + if handle == nil { + throw CobraInvalidStateError("Unable to process - resources have been released") + } + if pcm.count != Cobra.frameLength { throw CobraInvalidArgumentError( "Frame of audio data must contain \(Cobra.frameLength) samples - given frame contained \(pcm.count)") @@ -68,39 +81,61 @@ public class Cobra { var result: Float32 = 0 let status = pv_cobra_process(self.handle, pcm, &result) if status != PV_STATUS_SUCCESS { - throw pvStatusToCobraError(status, "Cobra process failed") + let messageStack = try getMessageStack() + throw pvStatusToCobraError(status, "Cobra process failed", messageStack) } return result } - private func pvStatusToCobraError(_ status: pv_status_t, _ message: String) -> CobraError { + private func pvStatusToCobraError( + _ status: pv_status_t, + _ message: String, + _ messageStack: [String] = []) -> CobraError { switch status { case PV_STATUS_OUT_OF_MEMORY: - return CobraMemoryError(message) + return CobraMemoryError(message, messageStack) case PV_STATUS_IO_ERROR: - return CobraIOError(message) + return CobraIOError(message, messageStack) case PV_STATUS_INVALID_ARGUMENT: - return CobraInvalidArgumentError(message) + return CobraInvalidArgumentError(message, messageStack) case PV_STATUS_STOP_ITERATION: - return CobraStopIterationError(message) + return CobraStopIterationError(message, messageStack) case PV_STATUS_KEY_ERROR: - return CobraKeyError(message) + return CobraKeyError(message, messageStack) case PV_STATUS_INVALID_STATE: - return CobraInvalidStateError(message) + return CobraInvalidStateError(message, messageStack) case PV_STATUS_RUNTIME_ERROR: - return CobraRuntimeError(message) + return CobraRuntimeError(message, messageStack) case PV_STATUS_ACTIVATION_ERROR: - return CobraActivationError(message) + return CobraActivationError(message, messageStack) case PV_STATUS_ACTIVATION_LIMIT_REACHED: - return CobraActivationLimitError(message) + return CobraActivationLimitError(message, messageStack) case PV_STATUS_ACTIVATION_THROTTLED: - return CobraActivationThrottledError(message) + return CobraActivationThrottledError(message, messageStack) case PV_STATUS_ACTIVATION_REFUSED: - return CobraActivationRefusedError(message) + return CobraActivationRefusedError(message, messageStack) default: let pvStatusString = String(cString: pv_status_to_string(status)) - return CobraError("\(pvStatusString): \(message)") + return CobraError("\(pvStatusString): \(message)", messageStack) + } + } + + private func getMessageStack() throws -> [String] { + var messageStackRef: UnsafeMutablePointer?>? + var messageStackDepth: Int32 = 0 + let status = pv_get_error_stack(&messageStackRef, &messageStackDepth) + if status != PV_STATUS_SUCCESS { + throw pvStatusToCobraError(status, "Unable to get Cobra error state") } + + var messageStack: [String] = [] + for i in 0.. 0 { + messageString += ":" + for i in 0.. Date: Tue, 24 Oct 2023 13:35:25 -0700 Subject: [PATCH 2/3] demos --- .github/workflows/ios-demos.yml | 11 ---- .../CobraAppTest/ViewController.swift | 10 +--- .../CobraAppTestUITests.swift | 53 ++++++------------- binding/ios/CobraAppTest/Podfile | 6 +-- binding/ios/CobraAppTest/Podfile.lock | 16 +++--- demo/ios/CobraDemo/CobraDemo/ViewModel.swift | 4 +- demo/ios/CobraDemo/Podfile | 2 +- demo/ios/CobraDemo/Podfile.lock | 13 +++-- 8 files changed, 39 insertions(+), 76 deletions(-) diff --git a/.github/workflows/ios-demos.yml b/.github/workflows/ios-demos.yml index 3a34b47f..8ea26251 100644 --- a/.github/workflows/ios-demos.yml +++ b/.github/workflows/ios-demos.yml @@ -25,20 +25,9 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Set up Node.js LTS - uses: actions/setup-node@v3 - with: - node-version: lts/* - - name: Install Cocoapods run: gem install cocoapods - - name: Install AppCenter CLI - run: npm install -g appcenter-cli - - - name: Make build dir - run: mkdir ddp - - name: Run Cocoapods run: pod install diff --git a/binding/ios/CobraAppTest/CobraAppTest/ViewController.swift b/binding/ios/CobraAppTest/CobraAppTest/ViewController.swift index 8e615640..f3776308 100644 --- a/binding/ios/CobraAppTest/CobraAppTest/ViewController.swift +++ b/binding/ios/CobraAppTest/CobraAppTest/ViewController.swift @@ -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 @@ -9,10 +9,4 @@ import UIKit -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } - -} +class ViewController: UIViewController { } diff --git a/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift b/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift index 3ab452b8..ead47a74 100644 --- a/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift +++ b/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift @@ -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 @@ -13,15 +13,11 @@ import Cobra class CobraAppTestUITests: XCTestCase { private let accessKey = "{TESTING_ACCESS_KEY_HERE}" - let thresholdString: String = "{PERFORMANCE_THRESHOLD_SEC}" override func setUpWithError() throws { continueAfterFailure = true } - override func tearDownWithError() throws { - } - func testProcess() throws { let cobra: Cobra = try Cobra(accessKey: accessKey) @@ -59,40 +55,21 @@ class CobraAppTestUITests: XCTestCase { } - func testPerformance() throws { - - try XCTSkipIf(thresholdString == "{PERFORMANCE_THRESHOLD_SEC}") - - let performanceThresholdSec = Double(thresholdString) - try XCTSkipIf(performanceThresholdSec == nil) - - let cobra: Cobra = try Cobra(accessKey: accessKey) - - let bundle = Bundle(for: type(of: self)) - let fileURL: URL = bundle.url(forResource: "sample", withExtension: "wav")! - - let data = try Data(contentsOf: fileURL) - let frameLengthBytes = Int(Cobra.frameLength) * 2 - var pcmBuffer = [Int16](repeating: 0, count: Int(Cobra.frameLength)) - - var totalNSec = 0.0 - var results: [Float32] = [] - var index = 44 - while index + frameLengthBytes < data.count { - _ = pcmBuffer.withUnsafeMutableBytes { data.copyBytes(to: $0, from: index..<(index + frameLengthBytes)) } - let before = CFAbsoluteTimeGetCurrent() - let voiceProbability: Float32 = try cobra.process(pcm: pcmBuffer) - let after = CFAbsoluteTimeGetCurrent() - totalNSec += (after - before) - results.append(voiceProbability) - - index += frameLengthBytes + func testMessageStack() throws { + var first_error: String = "" + do { + let cobra: Cobra = try Cobra(accessKey: "invalid") + XCTAssertNil(p) + } catch { + first_error = "\(error.localizedDescription)" + XCTAssert(first_error.count < 1024) } - cobra.delete() - - let totalSec = Double(round(totalNSec * 1000) / 1000) - XCTAssertLessThanOrEqual(totalSec, performanceThresholdSec!) - + do { + let cobra: Cobra = try Cobra(accessKey: "invalid") + XCTAssertNil(p) + } catch { + XCTAssert("\(error.localizedDescription)".count == first_error.count) + } } } diff --git a/binding/ios/CobraAppTest/Podfile b/binding/ios/CobraAppTest/Podfile index 20aa8e16..efbdfe85 100644 --- a/binding/ios/CobraAppTest/Podfile +++ b/binding/ios/CobraAppTest/Podfile @@ -2,13 +2,13 @@ source 'https://cdn.cocoapods.org/' platform :ios, '11.0' target 'CobraAppTest' do - pod 'Cobra-iOS', '~> 1.2.0' + pod 'Cobra-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec' end target 'CobraAppTestUITests' do - pod 'Cobra-iOS', '~> 1.2.0' + pod 'Cobra-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec' end target 'PerformanceTest' do - pod 'Cobra-iOS', '~> 1.2.0' + pod 'Cobra-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec' end diff --git a/binding/ios/CobraAppTest/Podfile.lock b/binding/ios/CobraAppTest/Podfile.lock index 77e936cb..b4487f2c 100644 --- a/binding/ios/CobraAppTest/Podfile.lock +++ b/binding/ios/CobraAppTest/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - Cobra-iOS (1.2.0) + - Cobra-iOS (2.0.0) DEPENDENCIES: - - Cobra-iOS (~> 1.2.0) + - Cobra-iOS (from `https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec`) -SPEC REPOS: - trunk: - - Cobra-iOS +EXTERNAL SOURCES: + Cobra-iOS: + :podspec: https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec SPEC CHECKSUMS: - Cobra-iOS: ff2e2622be1b37cc49935bb400938cb68cca6c18 + Cobra-iOS: c8d7f9052b9b783b9976243e3092c9b9087f63fc -PODFILE CHECKSUM: 350db7fdeda2f30994f4156e5d5c4048c8931cbb +PODFILE CHECKSUM: f2a9737621b3736541f8a3899eeff564bc95141a -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/demo/ios/CobraDemo/CobraDemo/ViewModel.swift b/demo/ios/CobraDemo/CobraDemo/ViewModel.swift index 60b5f300..d0188d71 100644 --- a/demo/ios/CobraDemo/CobraDemo/ViewModel.swift +++ b/demo/ios/CobraDemo/CobraDemo/ViewModel.swift @@ -35,8 +35,8 @@ class ViewModel: ObservableObject { VoiceProcessor.instance.addErrorListener(VoiceProcessorErrorListener(errorCallback)) VoiceProcessor.instance.addFrameListener(VoiceProcessorFrameListener(audioCallback)) - } catch is CobraInvalidArgumentError { - errorMessage = "ACCESS_KEY '\(ACCESS_KEY)' is invalid." + } catch let error as CobraInvalidArgumentError { + errorMessage = error.localizedDescription } catch is CobraActivationError { errorMessage = "ACCESS_KEY activation error." } catch is CobraActivationRefusedError { diff --git a/demo/ios/CobraDemo/Podfile b/demo/ios/CobraDemo/Podfile index 72d6f6ea..566755b9 100644 --- a/demo/ios/CobraDemo/Podfile +++ b/demo/ios/CobraDemo/Podfile @@ -2,6 +2,6 @@ source 'https://cdn.cocoapods.org/' platform :ios, '11.0' target 'CobraDemo' do - pod 'Cobra-iOS','~> 1.2.0' + pod 'Cobra-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec' pod 'ios-voice-processor', '~> 1.1.0' end diff --git a/demo/ios/CobraDemo/Podfile.lock b/demo/ios/CobraDemo/Podfile.lock index 2f5c70f5..1bcd1e21 100644 --- a/demo/ios/CobraDemo/Podfile.lock +++ b/demo/ios/CobraDemo/Podfile.lock @@ -1,20 +1,23 @@ PODS: - - Cobra-iOS (1.2.0) + - Cobra-iOS (2.0.0) - ios-voice-processor (1.1.0) DEPENDENCIES: - - Cobra-iOS (~> 1.2.0) + - Cobra-iOS (from `https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec`) - ios-voice-processor (~> 1.1.0) SPEC REPOS: trunk: - - Cobra-iOS - ios-voice-processor +EXTERNAL SOURCES: + Cobra-iOS: + :podspec: https://raw.githubusercontent.com/Picovoice/cobra/v2.0-ios/binding/ios/Cobra-iOS.podspec + SPEC CHECKSUMS: - Cobra-iOS: ff2e2622be1b37cc49935bb400938cb68cca6c18 + Cobra-iOS: c8d7f9052b9b783b9976243e3092c9b9087f63fc ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1 -PODFILE CHECKSUM: dcc8350eed61872fda34cda940969c6923f2c3e0 +PODFILE CHECKSUM: 4f296c7a7968d696777782481ce2f484ba88f7b4 COCOAPODS: 1.11.3 From 19d384dcbcc4a6aeff69507d1dfdde866b3eca3f Mon Sep 17 00:00:00 2001 From: Ian Lavery Date: Tue, 24 Oct 2023 13:41:57 -0700 Subject: [PATCH 3/3] fix --- .../CobraAppTestUITests/CobraAppTestUITests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift b/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift index ead47a74..79e08cfc 100644 --- a/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift +++ b/binding/ios/CobraAppTest/CobraAppTestUITests/CobraAppTestUITests.swift @@ -59,7 +59,7 @@ class CobraAppTestUITests: XCTestCase { var first_error: String = "" do { let cobra: Cobra = try Cobra(accessKey: "invalid") - XCTAssertNil(p) + XCTAssertNil(cobra) } catch { first_error = "\(error.localizedDescription)" XCTAssert(first_error.count < 1024) @@ -67,7 +67,7 @@ class CobraAppTestUITests: XCTestCase { do { let cobra: Cobra = try Cobra(accessKey: "invalid") - XCTAssertNil(p) + XCTAssertNil(cobra) } catch { XCTAssert("\(error.localizedDescription)".count == first_error.count) }