diff --git a/.github/workflows/ios-demos.yml b/.github/workflows/ios-demos.yml new file mode 100644 index 00000000..8a111f4f --- /dev/null +++ b/.github/workflows/ios-demos.yml @@ -0,0 +1,52 @@ +name: iOS Demos + +on: + workflow_dispatch: + push: + branches: [ main ] + paths: + - 'demo/ios/EagleDemo/**' + - '.github/workflows/ios-demos.yml' + pull_request: + branches: [ main, 'v[0-9]+.[0-9]+' ] + paths: + - 'demo/ios/EagleDemo/**' + - '.github/workflows/ios-demos.yml' + +defaults: + run: + working-directory: demo/ios/EagleDemo + +jobs: + build: + runs-on: macos-latest + + steps: + - 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 + + - name: Build + run: xcrun xcodebuild build + -configuration Debug + -workspace EagleDemo.xcworkspace + -sdk iphoneos + -scheme EagleDemo + -derivedDataPath ddp + CODE_SIGNING_ALLOWED=NO \ No newline at end of file diff --git a/binding/ios/Eagle-iOS.podspec b/binding/ios/Eagle-iOS.podspec index f1035802..54c1fe4f 100644 --- a/binding/ios/Eagle-iOS.podspec +++ b/binding/ios/Eagle-iOS.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Eagle-iOS' s.module_name = 'Eagle' - s.version = '0.1.0' + s.version = '0.2.0' s.license = {:type => 'Apache 2.0'} s.summary = 'iOS binding for Picovoice\'s Eagle speaker recognition engine' s.description = @@ -10,8 +10,8 @@ Pod::Spec.new do |s| DESC s.homepage = 'https://github.com/Picovoice/eagle/tree/master/binding/ios' s.author = { 'Picovoice' => 'hello@picovoice.ai' } - s.source = { :git => "https://github.com/Picovoice/eagle.git", :tag => "Eagle-iOS-v0.1.0" } - s.ios.deployment_target = '11.0' + s.source = { :git => "https://github.com/Picovoice/eagle.git", :tag => "Eagle-iOS-v0.2.0" } + s.ios.deployment_target = '13.0' s.swift_version = '5.0' s.vendored_frameworks = 'lib/ios/PvEagle.xcframework' s.resources = 'lib/common/eagle_params.pv' diff --git a/binding/ios/Eagle.swift b/binding/ios/Eagle.swift index 04c7d476..6a6fa94e 100644 --- a/binding/ios/Eagle.swift +++ b/binding/ios/Eagle.swift @@ -57,6 +57,8 @@ public class Eagle: EagleBase { speakerCount = speakerProfiles.count + pv_set_sdk(Eagle.sdk) + let status = pv_eagle_init( accessKey, modelPathArg, @@ -64,7 +66,8 @@ public class Eagle: EagleBase { speakerHandles, &handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToEagleError(status, "Eagle init failed") + let messageStack = try getMessageStack() + throw pvStatusToEagleError(status, "Eagle init failed", messageStack) } } @@ -116,7 +119,8 @@ public class Eagle: EagleBase { scores.baseAddress) if status != PV_STATUS_SUCCESS { - throw pvStatusToEagleError(status, "Eagle process failed") + let messageStack = try getMessageStack() + throw pvStatusToEagleError(status, "Eagle process failed", messageStack) } return Array(scores) @@ -137,7 +141,8 @@ public class Eagle: EagleBase { let status = pv_eagle_reset(handle) if status != PV_STATUS_SUCCESS { - throw pvStatusToEagleError(status, "Eagle reset failed") + let messageStack = try getMessageStack() + throw pvStatusToEagleError(status, "Eagle reset failed", messageStack) } } } diff --git a/binding/ios/EagleAppTest/EagleAppTest.xcodeproj/project.pbxproj b/binding/ios/EagleAppTest/EagleAppTest.xcodeproj/project.pbxproj index f6f85f27..2cfa4235 100644 --- a/binding/ios/EagleAppTest/EagleAppTest.xcodeproj/project.pbxproj +++ b/binding/ios/EagleAppTest/EagleAppTest.xcodeproj/project.pbxproj @@ -674,7 +674,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -704,7 +704,7 @@ INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -726,7 +726,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 65723695GD; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -751,7 +751,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 65723695GD; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -776,7 +776,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 65723695GD; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -803,7 +803,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 65723695GD; GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/binding/ios/EagleAppTest/EagleAppTest/ViewController.swift b/binding/ios/EagleAppTest/EagleAppTest/ViewController.swift index e5abedaa..9917f3ba 100644 --- a/binding/ios/EagleAppTest/EagleAppTest/ViewController.swift +++ b/binding/ios/EagleAppTest/EagleAppTest/ViewController.swift @@ -9,10 +9,4 @@ import UIKit -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - } - -} +class ViewController: UIViewController { } diff --git a/binding/ios/EagleAppTest/EagleAppTestUITests/EagleAppTestUITests.swift b/binding/ios/EagleAppTest/EagleAppTestUITests/EagleAppTestUITests.swift index 2e7bd362..6a35c8f9 100644 --- a/binding/ios/EagleAppTest/EagleAppTestUITests/EagleAppTestUITests.swift +++ b/binding/ios/EagleAppTest/EagleAppTestUITests/EagleAppTestUITests.swift @@ -123,4 +123,84 @@ class EagleAppTestUITests: BaseTest { XCTAssertLessThan(scores.max()!, 0.5) eagle.delete() } + + func testMessageStack() throws { + let enrollUrls = enrollUrls() + + let eagleProfiler = try EagleProfiler(accessKey: accessKey) + for url in enrollUrls { + let pcm = try readPcmFromFile(testAudioURL: url) + (_, _) = try eagleProfiler.enroll(pcm: pcm) + } + + let profile = try eagleProfiler.export() + eagleProfiler.delete() + + var first_error: String = "" + do { + let eagle = try Eagle(accessKey: "invalid", speakerProfiles: [profile]) + XCTAssertNil(eagle) + } catch { + first_error = "\(error.localizedDescription)" + XCTAssert(first_error.count < 1024) + } + + do { + let eagle = try Eagle(accessKey: "invalid", speakerProfiles: [profile]) + XCTAssertNil(eagle) + } catch { + XCTAssert("\(error.localizedDescription)".count == first_error.count) + } + } + + func testEnrollExportMessageStack() throws { + let e = try EagleProfiler.init(accessKey: accessKey) + e.delete() + + var testPcm: [Int16] = [] + testPcm.reserveCapacity(Int(Eagle.frameLength)) + + do { + let (res, _) = try e.enroll(pcm: testPcm) + XCTAssert(res == -1) + } catch { + XCTAssert("\(error.localizedDescription)".count > 0) + XCTAssert("\(error.localizedDescription)".count < 1024) + } + + do { + let res = try e.export() + XCTAssertNil(res) + } catch { + XCTAssert("\(error.localizedDescription)".count > 0) + XCTAssert("\(error.localizedDescription)".count < 1024) + } + } + + func testProcessMessageStack() throws { + let enrollUrls = enrollUrls() + + let eagleProfiler = try EagleProfiler(accessKey: accessKey) + for url in enrollUrls { + let pcm = try readPcmFromFile(testAudioURL: url) + (_, _) = try eagleProfiler.enroll(pcm: pcm) + } + + let profile = try eagleProfiler.export() + eagleProfiler.delete() + + let e = try Eagle.init(accessKey: accessKey, speakerProfiles: [profile]) + e.delete() + + var testPcm: [Int16] = [] + testPcm.reserveCapacity(Int(Eagle.frameLength)) + + do { + let res = try e.process(pcm: testPcm) + XCTAssert(res.count == -1) + } catch { + XCTAssert("\(error.localizedDescription)".count > 0) + XCTAssert("\(error.localizedDescription)".count < 1024) + } + } } diff --git a/binding/ios/EagleAppTest/Podfile b/binding/ios/EagleAppTest/Podfile index 25cfc8b2..ac899177 100644 --- a/binding/ios/EagleAppTest/Podfile +++ b/binding/ios/EagleAppTest/Podfile @@ -1,14 +1,14 @@ source 'https://cdn.cocoapods.org/' -platform :ios, '11.0' +platform :ios, '13.0' target 'EagleAppTest' do - pod 'Eagle-iOS', '~> 0.1.0' + pod 'Eagle-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec' end target 'EagleAppTestUITests' do - pod 'Eagle-iOS', '~> 0.1.0' + pod 'Eagle-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec' end target 'PerformanceTest' do - pod 'Eagle-iOS', '~> 0.1.0' + pod 'Eagle-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec' end diff --git a/binding/ios/EagleAppTest/Podfile.lock b/binding/ios/EagleAppTest/Podfile.lock index f886e1cd..1849c16a 100644 --- a/binding/ios/EagleAppTest/Podfile.lock +++ b/binding/ios/EagleAppTest/Podfile.lock @@ -1,16 +1,16 @@ PODS: - - Eagle-iOS (0.1.0) + - Eagle-iOS (0.2.0) DEPENDENCIES: - - Eagle-iOS (~> 0.1.0) + - Eagle-iOS (from `https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec`) -SPEC REPOS: - trunk: - - Eagle-iOS +EXTERNAL SOURCES: + Eagle-iOS: + :podspec: https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec SPEC CHECKSUMS: - Eagle-iOS: 2d510466a68b22ba137dd2b39086268c4130c321 + Eagle-iOS: 155eb54e73e37533a0accf3ea5fa1a37d8c999ad -PODFILE CHECKSUM: a3a1c94c7c11252c2152ed23f2cead73c1cf3762 +PODFILE CHECKSUM: 6ecb4b66367607455bddf4dcad4ec1f8c70935a7 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/binding/ios/EagleBase.swift b/binding/ios/EagleBase.swift index be5274e2..330659f2 100644 --- a/binding/ios/EagleBase.swift +++ b/binding/ios/EagleBase.swift @@ -9,6 +9,12 @@ public class EagleBase { /// Eagle/EagleProfiler version public static let version = String(cString: pv_eagle_version()) + internal static var sdk = "ios" + + public static func setSdk(sdk: String) { + self.sdk = sdk + } + /// Given a path, return the full path to the resource. /// /// - Parameters: @@ -31,34 +37,38 @@ public class EagleBase { /// - Parameters: /// - status: C enum value. /// - message: message to include with the EagleError. + /// - messageStack: Error stack returned from Eagle. /// - Returns: An EagleError. - internal func pvStatusToEagleError(_ status: pv_status_t, _ message: String) -> EagleError { + internal func pvStatusToEagleError( + _ status: pv_status_t, + _ message: String, + _ messageStack: [String] = []) -> EagleError { switch status { case PV_STATUS_OUT_OF_MEMORY: - return EagleMemoryError(message) + return EagleMemoryError(message, messageStack) case PV_STATUS_IO_ERROR: - return EagleIOError(message) + return EagleIOError(message, messageStack) case PV_STATUS_INVALID_ARGUMENT: - return EagleInvalidArgumentError(message) + return EagleInvalidArgumentError(message, messageStack) case PV_STATUS_STOP_ITERATION: - return EagleStopIterationError(message) + return EagleStopIterationError(message, messageStack) case PV_STATUS_KEY_ERROR: - return EagleKeyError(message) + return EagleKeyError(message, messageStack) case PV_STATUS_INVALID_STATE: - return EagleInvalidStateError(message) + return EagleInvalidStateError(message, messageStack) case PV_STATUS_RUNTIME_ERROR: - return EagleRuntimeError(message) + return EagleRuntimeError(message, messageStack) case PV_STATUS_ACTIVATION_ERROR: - return EagleActivationError(message) + return EagleActivationError(message, messageStack) case PV_STATUS_ACTIVATION_LIMIT_REACHED: - return EagleActivationLimitError(message) + return EagleActivationLimitError(message, messageStack) case PV_STATUS_ACTIVATION_THROTTLED: - return EagleActivationThrottledError(message) + return EagleActivationThrottledError(message, messageStack) case PV_STATUS_ACTIVATION_REFUSED: - return EagleActivationRefusedError(message) + return EagleActivationRefusedError(message, messageStack) default: let pvStatusString = String(cString: pv_status_to_string(status)) - return EagleError("\(pvStatusString): \(message)") + return EagleError("\(pvStatusString): \(message)", messageStack) } } @@ -84,4 +94,22 @@ public class EagleBase { return EagleProfilerEnrollFeedback.AUDIO_OK } } + + internal 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 pvStatusToEagleError(status, "Unable to get Eagle error state") + } + + var messageStack: [String] = [] + for i in 0.. 0 { + messageString += ":" + for i in 0.. 0.1.0' + pod 'Eagle-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec' pod 'ios-voice-processor', '~> 1.0.3' end diff --git a/demo/ios/EagleDemo/Podfile.lock b/demo/ios/EagleDemo/Podfile.lock index 1eee8489..652806f8 100644 --- a/demo/ios/EagleDemo/Podfile.lock +++ b/demo/ios/EagleDemo/Podfile.lock @@ -1,20 +1,23 @@ PODS: - - Eagle-iOS (0.1.0) + - Eagle-iOS (0.2.0) - ios-voice-processor (1.0.3) DEPENDENCIES: - - Eagle-iOS (~> 0.1.0) + - Eagle-iOS (from `https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec`) - ios-voice-processor (~> 1.0.3) SPEC REPOS: trunk: - - Eagle-iOS - ios-voice-processor +EXTERNAL SOURCES: + Eagle-iOS: + :podspec: https://raw.githubusercontent.com/Picovoice/eagle/v0.2-ios/binding/ios/Eagle-iOS.podspec + SPEC CHECKSUMS: - Eagle-iOS: 2d510466a68b22ba137dd2b39086268c4130c321 + Eagle-iOS: 155eb54e73e37533a0accf3ea5fa1a37d8c999ad ios-voice-processor: 65b25a8db69ea25ffba0eeef37bae71a982f34cc -PODFILE CHECKSUM: bd16b739f521d3070da2020a2446d0cabb973dd0 +PODFILE CHECKSUM: 04795797e862a461d1bfc97cddf986962ba2c8c2 -COCOAPODS: 1.12.1 +COCOAPODS: 1.11.3 diff --git a/lib/ios/PvEagle.xcframework/ios-arm64/PvEagle.framework/Headers/picovoice.h b/lib/ios/PvEagle.xcframework/ios-arm64/PvEagle.framework/Headers/picovoice.h index 886558d1..3f13d705 100644 --- a/lib/ios/PvEagle.xcframework/ios-arm64/PvEagle.framework/Headers/picovoice.h +++ b/lib/ios/PvEagle.xcframework/ios-arm64/PvEagle.framework/Headers/picovoice.h @@ -77,6 +77,8 @@ PV_API pv_status_t pv_get_error_stack( */ PV_API void pv_free_error_stack(char **message_stack); +PV_API void pv_set_sdk(const char *sdk); + #ifdef __cplusplus } diff --git a/lib/ios/PvEagle.xcframework/ios-arm64_x86_64-simulator/PvEagle.framework/Headers/picovoice.h b/lib/ios/PvEagle.xcframework/ios-arm64_x86_64-simulator/PvEagle.framework/Headers/picovoice.h index 886558d1..3f13d705 100644 --- a/lib/ios/PvEagle.xcframework/ios-arm64_x86_64-simulator/PvEagle.framework/Headers/picovoice.h +++ b/lib/ios/PvEagle.xcframework/ios-arm64_x86_64-simulator/PvEagle.framework/Headers/picovoice.h @@ -77,6 +77,8 @@ PV_API pv_status_t pv_get_error_stack( */ PV_API void pv_free_error_stack(char **message_stack); +PV_API void pv_set_sdk(const char *sdk); + #ifdef __cplusplus }