Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v2.0 ios #177

Merged
merged 3 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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