diff --git a/.github/workflows/coreaudio-rs.yml b/.github/workflows/coreaudio-rs.yml index 3039d5af0..15224a42e 100644 --- a/.github/workflows/coreaudio-rs.yml +++ b/.github/workflows/coreaudio-rs.yml @@ -58,11 +58,29 @@ jobs: with: toolchain: stable - name: Add iOS targets - run: rustup target add aarch64-apple-ios x86_64-apple-ios - - name: Install cargo lipo - run: cargo install cargo-lipo + run: rustup target add aarch64-apple-ios aarch64-apple-ios-sim - name: Build iphonesimulator feedback example - run: cd examples/ios && xcodebuild ONLY_ACTIVE_ARCH=NO ARCHS=x86_64 -scheme coreaudio-ios-example -configuration Debug -derivedDataPath build -sdk iphonesimulator + run: cd examples/apple && xcodebuild ONLY_ACTIVE_ARCH=NO ARCHS=arm64 -scheme coreaudio-ios-example -configuration Debug -derivedDataPath build -sdk iphonesimulator + + tvos-build: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: rust-src + - name: Build for tvOS simulator + run: cd examples/apple && cargo +nightly build -Zbuild-std --target aarch64-apple-tvos-sim --release + - name: Build for tvOS device + run: cd examples/apple && cargo +nightly build -Zbuild-std --target aarch64-apple-tvos --release + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: Add iOS targets + run: rustup target add aarch64-apple-ios aarch64-apple-ios-sim + - name: Build appletvsimulator example + run: cd examples/apple && xcodebuild ONLY_ACTIVE_ARCH=NO ARCHS=arm64 -scheme coreaudio-tvos-example -configuration Debug -derivedDataPath build -sdk appletvsimulator # Build the docs with all features to make sure docs.rs will work. macos-docs: diff --git a/.gitignore b/.gitignore index 2cc3e6778..4b04bd7af 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ target/ .cargo/ .DS_Store llvm/ + +# Xcode build artifacts +examples/*/build/ +examples/*/Cargo.lock diff --git a/examples/ios/Cargo.toml b/examples/apple/Cargo.toml similarity index 88% rename from examples/ios/Cargo.toml rename to examples/apple/Cargo.toml index bcb4c86b5..73f03ddbb 100644 --- a/examples/ios/Cargo.toml +++ b/examples/apple/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "coreaudio-ios-example" +name = "coreaudio-apple-example" version = "0.1.0" authors = ["Michael Hills "] edition = "2018" [lib] -name = "coreaudio_ios_example" +name = "coreaudio_apple_example" crate-type = ["staticlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/ios/ios-src/AppDelegate.h b/examples/apple/apple-src/AppDelegate.h similarity index 100% rename from examples/ios/ios-src/AppDelegate.h rename to examples/apple/apple-src/AppDelegate.h diff --git a/examples/ios/ios-src/AppDelegate.m b/examples/apple/apple-src/AppDelegate.m similarity index 55% rename from examples/ios/ios-src/AppDelegate.m rename to examples/apple/apple-src/AppDelegate.m index d215a9f4d..447edd9d8 100644 --- a/examples/ios/ios-src/AppDelegate.m +++ b/examples/apple/apple-src/AppDelegate.m @@ -1,14 +1,17 @@ // // AppDelegate.m -// coreaudio-ios-example +// coreaudio-apple-example // // Created by Michael Hills on 2/10/20. // #import "AppDelegate.h" +#if TARGET_OS_TV +#import "ViewController.h" +#endif @import AVFoundation; -void rust_ios_main(void); +void rust_apple_main(void); @interface AppDelegate () @@ -21,27 +24,40 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. - + +#if TARGET_OS_TV + // tvOS requires programmatic window creation (no storyboards) + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; +#endif + NSError *error; BOOL success; - + // It is necessary to access the sharedInstance so that calls to AudioSessionGetProperty // will work. AVAudioSession *session = AVAudioSession.sharedInstance; - // Setting up the category is not necessary, but generally advised. + +#if TARGET_OS_TV + // tvOS only supports Playback category (no microphone input, no DefaultToSpeaker) + success = [session setCategory:AVAudioSessionCategoryPlayback error:&error]; +#else + // iOS: Setting up the category is not necessary, but generally advised. // Since this demo records and plays, lets use AVAudioSessionCategoryPlayAndRecord. // Also default to speaker as defaulting to the phone earpiece would be unusual. success = [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error]; +#endif if (success) { - NSLog(@"Calling rust_ios_main()"); - rust_ios_main(); + NSLog(@"Calling rust_apple_main()"); + rust_apple_main(); } else { - NSLog(@"Failed to configure audio session category"); + NSLog(@"Failed to configure audio session category: %@", error); } - + return YES; } diff --git a/examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard b/examples/apple/apple-src/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from examples/ios/ios-src/Base.lproj/LaunchScreen.storyboard rename to examples/apple/apple-src/Base.lproj/LaunchScreen.storyboard diff --git a/examples/ios/ios-src/Base.lproj/Main.storyboard b/examples/apple/apple-src/Base.lproj/Main.storyboard similarity index 100% rename from examples/ios/ios-src/Base.lproj/Main.storyboard rename to examples/apple/apple-src/Base.lproj/Main.storyboard diff --git a/examples/ios/ios-src/Info.plist b/examples/apple/apple-src/Info-iOS.plist similarity index 100% rename from examples/ios/ios-src/Info.plist rename to examples/apple/apple-src/Info-iOS.plist diff --git a/examples/apple/apple-src/Info-tvOS.plist b/examples/apple/apple-src/Info-tvOS.plist new file mode 100644 index 000000000..e69d2e21c --- /dev/null +++ b/examples/apple/apple-src/Info-tvOS.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + UIApplicationSupportsIndirectInputEvents + + + diff --git a/examples/ios/ios-src/ViewController.h b/examples/apple/apple-src/ViewController.h similarity index 100% rename from examples/ios/ios-src/ViewController.h rename to examples/apple/apple-src/ViewController.h diff --git a/examples/ios/ios-src/ViewController.m b/examples/apple/apple-src/ViewController.m similarity index 100% rename from examples/ios/ios-src/ViewController.m rename to examples/apple/apple-src/ViewController.m diff --git a/examples/ios/ios-src/main.m b/examples/apple/apple-src/main.m similarity index 100% rename from examples/ios/ios-src/main.m rename to examples/apple/apple-src/main.m diff --git a/examples/apple/build_rust_deps.sh b/examples/apple/build_rust_deps.sh new file mode 100755 index 000000000..8114d3b88 --- /dev/null +++ b/examples/apple/build_rust_deps.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +PATH=$PATH:$HOME/.cargo/bin + +if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then + # Assume we're in Xcode, which means we're probably cross-compiling. + # In this case, we need to add an extra library search path for build scripts and proc-macros, + # which run on the host instead of the target. + # (macOS Big Sur does not have linkable libraries in /usr/lib/.) + export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}" +fi + +# Detect which platform we're building for based on SDKROOT +# SDKROOT is set by Xcode and contains the SDK path (e.g., iphoneos, iphonesimulator, appletvos, appletvsimulator) +case "${SDKROOT:-}" in + *appletvos*|*appletvsimulator*) + # Build for tvOS (requires nightly toolchain and -Zbuild-std since tvOS is a Tier 3 target) + cargo +nightly build -Zbuild-std --target aarch64-apple-tvos-sim --release + cargo +nightly build -Zbuild-std --target aarch64-apple-tvos --release + ;; + *) + # Default: Build for iOS (stable toolchain) + # Device (arm64) + cargo build --target aarch64-apple-ios --release + # Simulator (arm64) + cargo build --target aarch64-apple-ios-sim --release + ;; +esac diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj b/examples/apple/coreaudio-ios-example.xcodeproj/project.pbxproj similarity index 69% rename from examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj rename to examples/apple/coreaudio-ios-example.xcodeproj/project.pbxproj index a706f377e..7b5bcaaff 100644 --- a/examples/ios/coreaudio-ios-example.xcodeproj/project.pbxproj +++ b/examples/apple/coreaudio-ios-example.xcodeproj/project.pbxproj @@ -13,6 +13,11 @@ 57AB5B09252769700040DE8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 57AB5B01252769700040DE8C /* Main.storyboard */; }; 57AB5B0A252769700040DE8C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B03252769700040DE8C /* main.m */; }; 57AB5B0B252769700040DE8C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B04252769700040DE8C /* AppDelegate.m */; }; + /* tvOS target build files */ + TVOS0001252767460040DE8C /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AB5AF2252767460040DE8C /* AVFoundation.framework */; }; + TVOS0002252769700040DE8C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5AFE252769700040DE8C /* ViewController.m */; }; + TVOS0003252769700040DE8C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B03252769700040DE8C /* main.m */; }; + TVOS0004252769700040DE8C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 57AB5B04252769700040DE8C /* AppDelegate.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -21,12 +26,20 @@ containerPortal = 57AB5AC2252762C00040DE8C /* Project object */; proxyType = 1; remoteGlobalIDString = 57AB5AE9252766240040DE8C; - remoteInfo = cargo_ios; + remoteInfo = cargo_apple; + }; + TVOS0010252766820040DE8C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57AB5AC2252762C00040DE8C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 57AB5AE9252766240040DE8C; + remoteInfo = cargo_apple; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "coreaudio-ios-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + TVOS0020252762C10040DE8C /* coreaudio-tvos-example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "coreaudio-tvos-example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 57AB5AF2252767460040DE8C /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 57AB5AFD252769700040DE8C /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 57AB5AFE252769700040DE8C /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; @@ -34,7 +47,8 @@ 57AB5B02252769700040DE8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 57AB5B03252769700040DE8C /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 57AB5B04252769700040DE8C /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 57AB5B05252769700040DE8C /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 57AB5B05252769700040DE8C /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + TVOS0030252769700040DE8C /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 57AB5B06252769700040DE8C /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -47,13 +61,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + TVOS0040252762C10040DE8C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + TVOS0001252767460040DE8C /* AVFoundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 57AB5AC1252762C00040DE8C = { isa = PBXGroup; children = ( - 57AB5AFC252769700040DE8C /* ios-src */, + 57AB5AFC252769700040DE8C /* apple-src */, 57AB5ACB252762C10040DE8C /* Products */, 57AB5AF1252767460040DE8C /* Frameworks */, ); @@ -63,6 +85,7 @@ isa = PBXGroup; children = ( 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */, + TVOS0020252762C10040DE8C /* coreaudio-tvos-example.app */, ); name = Products; sourceTree = ""; @@ -75,7 +98,7 @@ name = Frameworks; sourceTree = ""; }; - 57AB5AFC252769700040DE8C /* ios-src */ = { + 57AB5AFC252769700040DE8C /* apple-src */ = { isa = PBXGroup; children = ( 57AB5AFD252769700040DE8C /* AppDelegate.h */, @@ -84,28 +107,29 @@ 57AB5AFE252769700040DE8C /* ViewController.m */, 57AB5AFF252769700040DE8C /* LaunchScreen.storyboard */, 57AB5B01252769700040DE8C /* Main.storyboard */, - 57AB5B05252769700040DE8C /* Info.plist */, + 57AB5B05252769700040DE8C /* Info-iOS.plist */, + TVOS0030252769700040DE8C /* Info-tvOS.plist */, 57AB5B03252769700040DE8C /* main.m */, ); - path = "ios-src"; + path = "apple-src"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXLegacyTarget section */ - 57AB5AE9252766240040DE8C /* cargo_ios */ = { + 57AB5AE9252766240040DE8C /* cargo_apple */ = { isa = PBXLegacyTarget; buildArgumentsString = build_rust_deps.sh; - buildConfigurationList = 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_ios" */; + buildConfigurationList = 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_apple" */; buildPhases = ( ); - buildToolPath = /bin/sh; + buildToolPath = /bin/bash; buildWorkingDirectory = .; dependencies = ( ); - name = cargo_ios; + name = cargo_apple; passBuildSettingsInEnvironment = 1; - productName = cargo_ios; + productName = cargo_apple; }; /* End PBXLegacyTarget section */ @@ -128,6 +152,24 @@ productReference = 57AB5ACA252762C10040DE8C /* coreaudio-ios-example.app */; productType = "com.apple.product-type.application"; }; + TVOS0050252762C10040DE8C /* coreaudio-tvos-example */ = { + isa = PBXNativeTarget; + buildConfigurationList = TVOS0060252762C30040DE8C /* Build configuration list for PBXNativeTarget "coreaudio-tvos-example" */; + buildPhases = ( + TVOS0070252762C10040DE8C /* Sources */, + TVOS0040252762C10040DE8C /* Frameworks */, + TVOS0080252762C10040DE8C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + TVOS0011252766820040DE8C /* PBXTargetDependency */, + ); + name = "coreaudio-tvos-example"; + productName = "coreaudio-tvos-example"; + productReference = TVOS0020252762C10040DE8C /* coreaudio-tvos-example.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -139,6 +181,9 @@ 57AB5AC9252762C10040DE8C = { CreatedOnToolsVersion = 12.0.1; }; + TVOS0050252762C10040DE8C = { + CreatedOnToolsVersion = 12.0.1; + }; 57AB5AE9252766240040DE8C = { CreatedOnToolsVersion = 12.0.1; }; @@ -158,7 +203,8 @@ projectRoot = ""; targets = ( 57AB5AC9252762C10040DE8C /* coreaudio-ios-example */, - 57AB5AE9252766240040DE8C /* cargo_ios */, + TVOS0050252762C10040DE8C /* coreaudio-tvos-example */, + 57AB5AE9252766240040DE8C /* cargo_apple */, ); }; /* End PBXProject section */ @@ -173,6 +219,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + TVOS0080252762C10040DE8C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -186,14 +239,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + TVOS0070252762C10040DE8C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + TVOS0003252769700040DE8C /* main.m in Sources */, + TVOS0004252769700040DE8C /* AppDelegate.m in Sources */, + TVOS0002252769700040DE8C /* ViewController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 57AB5AEF252766820040DE8C /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = 57AB5AE9252766240040DE8C /* cargo_ios */; + target = 57AB5AE9252766240040DE8C /* cargo_apple */; targetProxy = 57AB5AEE252766820040DE8C /* PBXContainerItemProxy */; }; + TVOS0011252766820040DE8C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 57AB5AE9252766240040DE8C /* cargo_apple */; + targetProxy = TVOS0010252766820040DE8C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -333,14 +401,15 @@ buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = "ios-src/Info.plist"; + INFOPLIST_FILE = "apple-src/Info-iOS.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = target/universal/debug; - OTHER_LDFLAGS = "-lcoreaudio_ios_example"; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/target/aarch64-apple-ios/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/target/aarch64-apple-ios-sim/release"; + OTHER_LDFLAGS = "-lcoreaudio_apple_example"; OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-ios-example"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -353,14 +422,15 @@ buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = "ios-src/Info.plist"; + INFOPLIST_FILE = "apple-src/Info-iOS.plist"; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = target/universal/release; - OTHER_LDFLAGS = "-lcoreaudio_ios_example"; + "LIBRARY_SEARCH_PATHS[sdk=iphoneos*]" = "$(SRCROOT)/target/aarch64-apple-ios/release"; + "LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]" = "$(SRCROOT)/target/aarch64-apple-ios-sim/release"; + OTHER_LDFLAGS = "-lcoreaudio_apple_example"; OTHER_LIBTOOLFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-ios-example"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -368,6 +438,51 @@ }; name = Release; }; + /* tvOS target configurations */ + TVOS0090252762C30040DE8C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "apple-src/Info-tvOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=appletvos*]" = "$(SRCROOT)/target/aarch64-apple-tvos/release"; + "LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/target/aarch64-apple-tvos-sim/release"; + OTHER_LDFLAGS = "-lcoreaudio_apple_example"; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-tvos-example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.0; + }; + name = Debug; + }; + TVOS0091252762C30040DE8C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = "apple-src/Info-tvOS.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + "LIBRARY_SEARCH_PATHS[sdk=appletvos*]" = "$(SRCROOT)/target/aarch64-apple-tvos/release"; + "LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*]" = "$(SRCROOT)/target/aarch64-apple-tvos-sim/release"; + OTHER_LDFLAGS = "-lcoreaudio_apple_example"; + OTHER_LIBTOOLFLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = "coreaudio-rs.coreaudio-tvos-example"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.0; + }; + name = Release; + }; 57AB5AEB252766240040DE8C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -414,7 +529,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_ios" */ = { + TVOS0060252762C30040DE8C /* Build configuration list for PBXNativeTarget "coreaudio-tvos-example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + TVOS0090252762C30040DE8C /* Debug */, + TVOS0091252762C30040DE8C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57AB5AEA252766240040DE8C /* Build configuration list for PBXLegacyTarget "cargo_apple" */ = { isa = XCConfigurationList; buildConfigurations = ( 57AB5AEB252766240040DE8C /* Debug */, diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/apple/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to examples/apple/coreaudio-ios-example.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/apple/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from examples/ios/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to examples/apple/coreaudio-ios-example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-ios-example.xcscheme b/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-ios-example.xcscheme new file mode 100644 index 000000000..71bb8e421 --- /dev/null +++ b/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-ios-example.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-tvos-example.xcscheme b/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-tvos-example.xcscheme new file mode 100644 index 000000000..bd887e778 --- /dev/null +++ b/examples/apple/coreaudio-ios-example.xcodeproj/xcshareddata/xcschemes/coreaudio-tvos-example.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/apple/src/feedback.rs b/examples/apple/src/feedback.rs new file mode 100644 index 000000000..886c5d605 --- /dev/null +++ b/examples/apple/src/feedback.rs @@ -0,0 +1,120 @@ +//! Audio example for iOS and tvOS. +//! +//! Both platforms play a 440Hz sine wave (A4 note) to demonstrate audio output. + +extern crate coreaudio; + +use std::f64::consts::PI; + +use coreaudio::audio_unit::audio_format::LinearPcmFlags; +use coreaudio::audio_unit::render_callback::{self, data}; +use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; +use objc2_audio_toolbox::kAudioUnitProperty_StreamFormat; +use objc2_core_audio_types::AudioStreamBasicDescription; + +type S = f32; +const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; + +struct SineWaveGenerator { + time: f64, + sample_rate: f64, + /// Generated frequency in Hz + freq: f64, + /// Magnitude of generated signal + volume: f64, +} + +impl SineWaveGenerator { + fn new(sample_rate: f64, freq: f64, volume: f64) -> Self { + SineWaveGenerator { + time: 0., + sample_rate, + freq, + volume, + } + } +} + +impl Iterator for SineWaveGenerator { + type Item = S; + fn next(&mut self) -> Option { + self.time += 1. / self.sample_rate; + let output = ((self.freq * self.time * PI * 2.).sin() * self.volume) as S; + Some(output) + } +} + +pub fn run_example() -> Result<(), coreaudio::Error> { + let mut audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?; + + // Read device sample rate off the output stream + let id = kAudioUnitProperty_StreamFormat; + let asbd: AudioStreamBasicDescription = + audio_unit.get_property(id, Scope::Output, Element::Output)?; + let sample_rate = asbd.mSampleRate; + + println!("Sample rate: {}", sample_rate); + + // iOS/tvOS don't let you reconfigure an "initialized" audio unit, so uninitialize it + audio_unit.uninitialize()?; + + let format_flag = match SAMPLE_FORMAT { + SampleFormat::F32 => LinearPcmFlags::IS_FLOAT, + SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => { + LinearPcmFlags::IS_SIGNED_INTEGER + } + SampleFormat::I24 => { + unimplemented!("Not implemented for I24") + } + }; + + let out_stream_format = StreamFormat { + sample_rate, + sample_format: SAMPLE_FORMAT, + flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, + channels: 2, + }; + + println!("output={:#?}", &out_stream_format); + println!("output_asbd={:#?}", &out_stream_format.to_asbd()); + + let id = kAudioUnitProperty_StreamFormat; + audio_unit.set_property( + id, + Scope::Input, + Element::Output, + Some(&out_stream_format.to_asbd()), + )?; + + // Create sine wave generator: 440Hz (A4 note) at 30% volume + let mut samples = SineWaveGenerator::new(sample_rate, 440., 0.3); + + type Args = render_callback::Args>; + + println!("set_render_callback"); + audio_unit.set_render_callback(move |args: Args| { + let Args { + num_frames, + mut data, + .. + } = args; + + for i in 0..num_frames { + let sample = samples.next().unwrap(); + for channel in data.channels_mut() { + channel[i] = sample; + } + } + Ok(()) + })?; + + audio_unit.initialize()?; + audio_unit.start()?; + + println!("Audio started - playing 440Hz sine wave"); + + // For the purposes of this demo, leak the audio unit so it keeps running + std::mem::forget(audio_unit); + + Ok(()) +} diff --git a/examples/ios/src/lib.rs b/examples/apple/src/lib.rs similarity index 64% rename from examples/ios/src/lib.rs rename to examples/apple/src/lib.rs index 091f8f7c8..1e578e5de 100644 --- a/examples/ios/src/lib.rs +++ b/examples/apple/src/lib.rs @@ -1,6 +1,6 @@ mod feedback; #[no_mangle] -pub extern "C" fn rust_ios_main() { +pub extern "C" fn rust_apple_main() { feedback::run_example().unwrap(); } diff --git a/examples/ios/build_rust_deps.sh b/examples/ios/build_rust_deps.sh deleted file mode 100755 index 024a42b28..000000000 --- a/examples/ios/build_rust_deps.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -set -e - -PATH=$PATH:$HOME/.cargo/bin - -if [[ -n "${DEVELOPER_SDK_DIR:-}" ]]; then - # Assume we're in Xcode, which means we're probably cross-compiling. - # In this case, we need to add an extra library search path for build scripts and proc-macros, - # which run on the host instead of the target. - # (macOS Big Sur does not have linkable libraries in /usr/lib/.) - export LIBRARY_PATH="${DEVELOPER_SDK_DIR}/MacOSX.sdk/usr/lib:${LIBRARY_PATH:-}" -fi - -# If you want your build to run faster, add a "--targets x86_64-apple-ios" for just using the ios simulator. -if [ -n ${IOS_TARGETS} ]; then - cargo lipo --targets ${IOS_TARGETS} -else - cargo lipo -fi diff --git a/examples/ios/src/feedback.rs b/examples/ios/src/feedback.rs deleted file mode 100644 index f29f3be39..000000000 --- a/examples/ios/src/feedback.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! A basic input + output stream example, copying the mic input stream to the default output stream - -extern crate coreaudio; - -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; - -use coreaudio::audio_unit::audio_format::LinearPcmFlags; -use coreaudio::audio_unit::render_callback::{self, data}; -use coreaudio::audio_unit::{AudioUnit, Element, SampleFormat, Scope, StreamFormat}; -use objc2_audio_toolbox::{kAudioOutputUnitProperty_EnableIO, kAudioUnitProperty_StreamFormat}; -use objc2_core_audio_types::AudioStreamBasicDescription; - -type S = f32; -const SAMPLE_FORMAT: SampleFormat = SampleFormat::F32; -// type S = i32; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I32; -// type S = i16; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I16; -// type S = i8; const SAMPLE_FORMAT: SampleFormat = SampleFormat::I8; - -pub fn run_example() -> Result<(), coreaudio::Error> { - let mut input_audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?; - let mut output_audio_unit = AudioUnit::new(coreaudio::audio_unit::IOType::RemoteIO)?; - - // Read device sample rate off the output stream - let id = kAudioUnitProperty_StreamFormat; - let asbd: AudioStreamBasicDescription = - output_audio_unit.get_property(id, Scope::Output, Element::Output)?; - let sample_rate = asbd.mSampleRate; - - // iOS doesn't let you reconfigure an "initialized" audio unit, so uninitialize them - input_audio_unit.uninitialize()?; - output_audio_unit.uninitialize()?; - - configure_for_recording(&mut input_audio_unit)?; - - let format_flag = match SAMPLE_FORMAT { - SampleFormat::F32 => LinearPcmFlags::IS_FLOAT, - SampleFormat::I32 | SampleFormat::I16 | SampleFormat::I8 => { - LinearPcmFlags::IS_SIGNED_INTEGER - } - SampleFormat::I24 => { - unimplemented!("Not implemented for I24") - } - }; - - // Using IS_NON_INTERLEAVED everywhere because data::Interleaved is commented out / not implemented - let in_stream_format = StreamFormat { - sample_rate: sample_rate, - sample_format: SAMPLE_FORMAT, - flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, - // audio_unit.set_input_callback is hardcoded to 1 buffer, and when using non_interleaved - // we are forced to 1 channel - channels: 1, - }; - - let out_stream_format = StreamFormat { - sample_rate: sample_rate, - sample_format: SAMPLE_FORMAT, - flags: format_flag | LinearPcmFlags::IS_PACKED | LinearPcmFlags::IS_NON_INTERLEAVED, - // you can change this to 1 - channels: 2, - }; - - println!("input={:#?}", &in_stream_format); - println!("output={:#?}", &out_stream_format); - println!("input_asbd={:#?}", &in_stream_format.to_asbd()); - println!("output_asbd={:#?}", &out_stream_format.to_asbd()); - - let id = kAudioUnitProperty_StreamFormat; - input_audio_unit.set_property( - id, - Scope::Output, - Element::Input, - Some(&in_stream_format.to_asbd()), - )?; - output_audio_unit.set_property( - id, - Scope::Input, - Element::Output, - Some(&out_stream_format.to_asbd()), - )?; - - let buffer_left = Arc::new(Mutex::new(VecDeque::::new())); - let producer_left = buffer_left.clone(); - let consumer_left = buffer_left.clone(); - let buffer_right = Arc::new(Mutex::new(VecDeque::::new())); - let producer_right = buffer_right.clone(); - let consumer_right = buffer_right.clone(); - - // seed roughly 1 second of data to create a delay in the feedback loop for easier testing - for buffer in vec![buffer_left, buffer_right] { - let mut buffer = buffer.lock().unwrap(); - for _ in 0..(out_stream_format.sample_rate as i32) { - buffer.push_back(0 as S); - } - } - - type Args = render_callback::Args>; - - println!("set_input_callback"); - input_audio_unit.set_input_callback(move |args| { - let Args { - num_frames, - mut data, - .. - } = args; - let buffer_left = producer_left.lock().unwrap(); - let buffer_right = producer_right.lock().unwrap(); - let mut buffers = vec![buffer_left, buffer_right]; - for i in 0..num_frames { - for (ch, channel) in data.channels_mut().enumerate() { - let value: S = channel[i]; - buffers[ch].push_back(value); - } - } - Ok(()) - })?; - input_audio_unit.initialize()?; - input_audio_unit.start()?; - - println!("set_render_callback"); - output_audio_unit.set_render_callback(move |args: Args| { - let Args { - num_frames, - mut data, - .. - } = args; - - let buffer_left = consumer_left.lock().unwrap(); - let buffer_right = consumer_right.lock().unwrap(); - let mut buffers = vec![buffer_left, buffer_right]; - for i in 0..num_frames { - // Default other channels to copy value from first channel as a fallback - let zero: S = 0 as S; - let f: S = *buffers[0].front().unwrap_or(&zero); - for (ch, channel) in data.channels_mut().enumerate() { - let sample: S = buffers[ch].pop_front().unwrap_or(f); - channel[i] = sample; - } - } - Ok(()) - })?; - output_audio_unit.initialize()?; - output_audio_unit.start()?; - - // for the purposes of this demo, leak these so that after returning the audio units will - // keep running - std::mem::forget(input_audio_unit); - std::mem::forget(output_audio_unit); - - Ok(()) -} - -fn configure_for_recording(audio_unit: &mut AudioUnit) -> Result<(), coreaudio::Error> { - println!("Configure audio unit for recording"); - - // Enable mic recording - let enable_input = 1u32; - audio_unit.set_property( - kAudioOutputUnitProperty_EnableIO, - Scope::Input, - Element::Input, - Some(&enable_input), - )?; - - // Disable output - let disable_output = 0u32; - audio_unit.set_property( - kAudioOutputUnitProperty_EnableIO, - Scope::Output, - Element::Output, - Some(&disable_output), - )?; - - Ok(()) -} diff --git a/src/audio_unit/mod.rs b/src/audio_unit/mod.rs index 0803b7ba8..6809d7c69 100644 --- a/src/audio_unit/mod.rs +++ b/src/audio_unit/mod.rs @@ -443,13 +443,13 @@ pub unsafe fn get_property( /// Gets the value of a specified audio session property. /// -/// **Available** in iOS 2.0 and later. +/// **Available** in iOS 2.0 and later, and tvOS 9.0 and later. /// /// Parameters /// ---------- /// /// - **id**: The identifier of the property. -#[cfg(target_os = "ios")] +#[cfg(any(target_os = "ios", target_os = "tvos"))] pub fn audio_session_get_property(id: u32) -> Result { let mut size = ::std::mem::size_of::() as u32; #[allow(deprecated)] diff --git a/src/audio_unit/render_callback.rs b/src/audio_unit/render_callback.rs index 3d4794944..e6a767b21 100644 --- a/src/audio_unit/render_callback.rs +++ b/src/audio_unit/render_callback.rs @@ -571,7 +571,7 @@ impl AudioUnit { let buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?; buffer_frame_size }; - #[cfg(target_os = "ios")] + #[cfg(any(target_os = "ios", target_os = "tvos"))] let mut buffer_frame_size: u32 = { let id = objc2_audio_toolbox::kAudioSessionProperty_CurrentHardwareIOBufferDuration; let seconds: f32 = super::audio_session_get_property(id)?; diff --git a/src/audio_unit/types.rs b/src/audio_unit/types.rs index 1982fa43d..915f81da6 100644 --- a/src/audio_unit/types.rs +++ b/src/audio_unit/types.rs @@ -467,7 +467,7 @@ pub enum IOType { /// cancellation is not needed. /// /// See - /// **Available** in iOS. - #[cfg(target_os = "ios")] - RemoteIO = 1919512419, //kAudioUnitSubType_RemoteIO, only available in the ios sdk, + /// **Available** in iOS 2.0 and later, and tvOS 9.0 and later. + #[cfg(any(target_os = "ios", target_os = "tvos"))] + RemoteIO = 1919512419, // kAudioUnitSubType_RemoteIO, only available in the iOS/tvOS SDK }