Skip to content

Commit

Permalink
v2.0 ios (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
laves authored Oct 25, 2023
1 parent f80054a commit ce1b945
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 99 deletions.
11 changes: 0 additions & 11 deletions .github/workflows/ios-demos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions binding/ios/Cobra-iOS.podspec
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
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)
Cobra is a highly-accurate and lightweight voice activity detection (VAD) engine.
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'
Expand Down
67 changes: 51 additions & 16 deletions binding/ios/Cobra.swift
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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)")
Expand All @@ -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<UnsafeMutablePointer<Int8>?>?
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..<messageStackDepth {
messageStack.append(String(cString: messageStackRef!.advanced(by: Int(i)).pointee!))
}

pv_free_error_stack(messageStackRef)

return messageStack
}
}
10 changes: 2 additions & 8 deletions binding/ios/CobraAppTest/CobraAppTest/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 @@ -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)

Expand Down Expand Up @@ -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(cobra)
} 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(cobra)
} catch {
XCTAssert("\(error.localizedDescription)".count == first_error.count)
}
}
}
6 changes: 3 additions & 3 deletions binding/ios/CobraAppTest/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 '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
16 changes: 8 additions & 8 deletions binding/ios/CobraAppTest/Podfile.lock
Original file line number Diff line number Diff line change
@@ -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
19 changes: 15 additions & 4 deletions binding/ios/CobraErrors.swift
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,17 +9,28 @@

public class CobraError: 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 {
return String(describing: type(of: self))
get {
return String(describing: type(of: self))
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions demo/ios/CobraDemo/CobraDemo/ViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion demo/ios/CobraDemo/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 8 additions & 5 deletions demo/ios/CobraDemo/Podfile.lock
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit ce1b945

Please sign in to comment.