Skip to content

Commit 370e2b9

Browse files
Merge pull request #405 from RunanywhereAI/mac-changes
Mac changes (Changes needed to enable MacOS for distribution)
2 parents 39f4865 + c2dfb51 commit 370e2b9

3 files changed

Lines changed: 34 additions & 13 deletions

File tree

Package.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import Foundation
3737
// ./scripts/build-swift.sh --set-remote (sets useLocalBinaries = false)
3838
//
3939
// =============================================================================
40-
let useLocalBinaries = false // Toggle: true for local dev, false for release
40+
let useLocalBinaries = true // Toggle: true for local dev, false for release
4141

4242
// Version for remote XCFrameworks (used when testLocal = false)
4343
// Updated automatically by CI/CD during releases
@@ -131,6 +131,7 @@ let package = Package(
131131
.product(name: "Sentry", package: "sentry-cocoa"),
132132
.product(name: "StableDiffusion", package: "ml-stable-diffusion"),
133133
"CRACommons",
134+
"RACommonsBinary",
134135
],
135136
path: "sdk/runanywhere-swift/Sources/RunAnywhere",
136137
exclude: ["CRACommons"],
@@ -150,6 +151,8 @@ let package = Package(
150151
dependencies: [
151152
"RunAnywhere",
152153
"ONNXBackend",
154+
"RABackendONNXBinary",
155+
"ONNXRuntimeBinary",
153156
],
154157
path: "sdk/runanywhere-swift/Sources/ONNXRuntime",
155158
exclude: ["include"],
@@ -170,6 +173,7 @@ let package = Package(
170173
dependencies: [
171174
"RunAnywhere",
172175
"LlamaCPPBackend",
176+
"RABackendLlamaCPPBinary",
173177
],
174178
path: "sdk/runanywhere-swift/Sources/LlamaCPPRuntime",
175179
exclude: ["include"],

examples/ios/RunAnywhereAI/RunAnywhereAI.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@
412412
isa = XCBuildConfiguration;
413413
buildSettings = {
414414
ALWAYS_SEARCH_USER_PATHS = NO;
415+
ARCHS = arm64;
415416
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
416417
CLANG_ANALYZER_NONNULL = YES;
417418
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -465,6 +466,7 @@
465466
5480A2132E2F250400337F2F /* Debug */ = {
466467
isa = XCBuildConfiguration;
467468
buildSettings = {
469+
ARCHS = arm64;
468470
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
469471
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
470472
CODE_SIGN_ENTITLEMENTS = RunAnywhereAI/RunAnywhereAI.entitlements;
@@ -475,6 +477,7 @@
475477
ENABLE_PREVIEWS = YES;
476478
GENERATE_INFOPLIST_FILE = YES;
477479
INFOPLIST_KEY_CFBundleDisplayName = RunAnywhere;
480+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
478481
INFOPLIST_KEY_NSCameraUsageDescription = "RunAnywhere AI needs access to your camera for vision language model features to analyze images.";
479482
INFOPLIST_KEY_NSMicrophoneUsageDescription = "RunAnywhere AI needs access to your microphone to transcribe your voice input and provide voice-based interactions.";
480483
INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "RunAnywhere AI uses speech recognition to convert your voice to text for processing.";
@@ -508,6 +511,7 @@
508511
5480A2142E2F250400337F2F /* Release */ = {
509512
isa = XCBuildConfiguration;
510513
buildSettings = {
514+
ARCHS = arm64;
511515
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
512516
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
513517
CODE_SIGN_ENTITLEMENTS = RunAnywhereAI/RunAnywhereAI.entitlements;
@@ -518,6 +522,7 @@
518522
ENABLE_PREVIEWS = YES;
519523
GENERATE_INFOPLIST_FILE = YES;
520524
INFOPLIST_KEY_CFBundleDisplayName = RunAnywhere;
525+
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
521526
INFOPLIST_KEY_NSCameraUsageDescription = "RunAnywhere AI needs access to your camera for vision language model features to analyze images.";
522527
INFOPLIST_KEY_NSMicrophoneUsageDescription = "RunAnywhere AI needs access to your microphone to transcribe your voice input and provide voice-based interactions.";
523528
INFOPLIST_KEY_NSSpeechRecognitionUsageDescription = "RunAnywhere AI uses speech recognition to convert your voice to text for processing.";
@@ -536,6 +541,7 @@
536541
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
537542
MACOSX_DEPLOYMENT_TARGET = 15.5;
538543
MARKETING_VERSION = 0.17.2;
544+
OTHER_LDFLAGS = "-all_load";
539545
PRODUCT_BUNDLE_IDENTIFIER = com.runanywhere.RunAnywhere;
540546
PRODUCT_NAME = "$(TARGET_NAME)";
541547
REGISTER_APP_GROUPS = YES;

sdk/runanywhere-swift/Sources/RunAnywhere/Features/STT/Services/AudioCaptureManager.swift

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,30 @@ public class AudioCaptureManager: ObservableObject {
8484
}
8585

8686
#if os(iOS) || os(tvOS)
87-
// Configure audio session (iOS/tvOS only)
88-
// watchOS is NOT supported - AVAudioEngine inputNode tap does not work on watchOS
8987
let audioSession = AVAudioSession.sharedInstance()
9088
try audioSession.setCategory(.record, mode: .measurement)
9189
try audioSession.setActive(true)
9290
#endif
9391

94-
// Create audio engine (works on all platforms)
9592
let engine = AVAudioEngine()
9693
let inputNode = engine.inputNode
9794

98-
// Get input format
95+
#if os(macOS)
96+
// On macOS there is no AVAudioSession. Preparing the engine before
97+
// reading the input node format establishes the audio-unit graph
98+
// connections and avoids kAudioUnitErr_NoConnection (-10877).
99+
engine.prepare()
100+
#endif
101+
99102
let inputFormat = inputNode.outputFormat(forBus: 0)
103+
104+
guard inputFormat.sampleRate > 0, inputFormat.channelCount > 0 else {
105+
logger.error("No valid audio input device (sampleRate=\(inputFormat.sampleRate), channels=\(inputFormat.channelCount))")
106+
throw AudioCaptureError.noInputDevice
107+
}
108+
100109
logger.info("Input format: \(inputFormat.sampleRate) Hz, \(inputFormat.channelCount) channels")
101110

102-
// Create converter format (16kHz, mono, int16)
103111
guard let outputFormat = AVAudioFormat(
104112
commonFormat: .pcmFormatInt16,
105113
sampleRate: targetSampleRate,
@@ -109,32 +117,26 @@ public class AudioCaptureManager: ObservableObject {
109117
throw AudioCaptureError.formatConversionFailed
110118
}
111119

112-
// Create audio converter
113120
guard let converter = AVAudioConverter(from: inputFormat, to: outputFormat) else {
114121
throw AudioCaptureError.formatConversionFailed
115122
}
116123

117-
// Install tap on input node
118124
inputNode.installTap(onBus: 0, bufferSize: 4096, format: inputFormat) { [weak self] buffer, _ in
119125
guard let self = self else { return }
120126

121-
// Update audio level for visualization
122127
self.updateAudioLevel(buffer: buffer)
123128

124-
// Convert to target format
125129
guard let convertedBuffer = self.convert(buffer: buffer, using: converter, to: outputFormat) else {
126130
return
127131
}
128132

129-
// Convert to Data (int16 PCM)
130133
if let audioData = self.bufferToData(buffer: convertedBuffer) {
131134
DispatchQueue.main.async {
132135
onAudioData(audioData)
133136
}
134137
}
135138
}
136139

137-
// Start engine (remove tap on failure to avoid resource leak)
138140
do {
139141
try engine.start()
140142
} catch {
@@ -177,12 +179,18 @@ public class AudioCaptureManager: ObservableObject {
177179

178180
// MARK: - Private Helpers
179181

180-
/// Converts a PCM buffer to the target format. Internal for unit testing (converter input block single-use behavior).
182+
/// Converts a PCM buffer to the target format. Internal for unit testing.
181183
internal func convert(
182184
buffer: AVAudioPCMBuffer,
183185
using converter: AVAudioConverter,
184186
to format: AVAudioFormat
185187
) -> AVAudioPCMBuffer? {
188+
// The input block returns .endOfStream after providing one buffer.
189+
// On macOS the converter stays in that "finished" state across calls,
190+
// producing empty output for every subsequent buffer. Resetting before
191+
// each conversion clears the state so the next buffer is processed.
192+
converter.reset()
193+
186194
let capacity = AVAudioFrameCount(Double(buffer.frameLength) * (format.sampleRate / buffer.format.sampleRate))
187195

188196
guard let convertedBuffer = AVAudioPCMBuffer(
@@ -260,6 +268,7 @@ public enum AudioCaptureError: LocalizedError {
260268
case permissionDenied
261269
case formatConversionFailed
262270
case engineStartFailed
271+
case noInputDevice
263272

264273
public var errorDescription: String? {
265274
switch self {
@@ -269,6 +278,8 @@ public enum AudioCaptureError: LocalizedError {
269278
return "Failed to convert audio format"
270279
case .engineStartFailed:
271280
return "Failed to start audio engine"
281+
case .noInputDevice:
282+
return "No audio input device available"
272283
}
273284
}
274285
}

0 commit comments

Comments
 (0)