From 2bfbda2b938fa52444e9c02d81fc8143ea1631ec Mon Sep 17 00:00:00 2001 From: l1npengtul Date: Thu, 23 Sep 2021 23:23:40 +0900 Subject: [PATCH] Working AVFoundationn --- examples/.DS_Store | Bin 6148 -> 6148 bytes examples/capture/.DS_Store | Bin 6148 -> 6148 bytes examples/capture/example-capture/.DS_Store | Bin 6148 -> 6148 bytes .../example-capture.xcodeproj/project.pbxproj | 168 ++++--- .../UserInterfaceState.xcuserstate | Bin 16054 -> 30762 bytes .../xcschemes/example-capture.xcscheme | 22 +- .../xcschemes/xcschememanagement.plist | 2 +- .../example-capture/example-capture/.DS_Store | Bin 6148 -> 6148 bytes .../example-capture/Info.plist | 12 +- .../example_capture.entitlements | 16 + .../example-capture/example-capture/main.c | 15 - .../example-capture/main.swift | 50 ++ .../project.pbxproj | 218 +++++++++ .../xcschemes/xcschememanagement.plist | 14 + examples/capture/src/main.rs | 7 +- nokhwa-bindings-macos/Cargo.toml | 2 + nokhwa-bindings-macos/src/lib.rs | 462 ++++++++++-------- src/backends/capture/avfoundation.rs | 44 +- src/utils.rs | 2 +- 19 files changed, 723 insertions(+), 311 deletions(-) create mode 100644 examples/capture/example-capture/example-capture/example_capture.entitlements delete mode 100644 examples/capture/example-capture/example-capture/main.c create mode 100644 examples/capture/example-capture/example-capture/main.swift create mode 100644 examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/project.pbxproj create mode 100644 examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/examples/.DS_Store b/examples/.DS_Store index 1b6343facc24b36a03fc7ff581a0c1918fca9c06..d785470a0a7b3045be1fba7e90c4bb3ad9a491be 100644 GIT binary patch delta 585 zcmZoMXfc=|#>B!ku~2NHo+2ab#(>?7iyN4k7+ELtFnKeco*co{P%l+oZD?pr00Txw2+hC`rD4<@AcLJDnIVy( zfT4t;l%a?rH8Z)aASow5iGhK!HK`ycv$({-;2t9rGYcylI|nBhHxF+;AHRSgzYxE$ zu!yLbfP`ecfN)Y`a&~%AeraBcbAC>KkyB1-YPpvNoqw& zaAp-yN(fCuP-;pXLMWuN0H{U;Apkbiy(l#`FFL&_vp&T?Ev-1UBm%^NvLj=_mX)OD zdgi6&11$hq=wDEhnV(l2mReK{q$3lHiW2kEQ=KYfa}twMbNut1@=Hqcb0ac=P6Rm% zY+P_bVsd6)y0ZNTNEmS{J7_>y0u0I!Mm+-qhX7+pUVdIGP&NP}sO;bkVK8tos4`eH z_%ehsWHVGTOk|kLu##aL!)b=A438P!F#KR-WMpFGX5?iQV-#nUWRzo+XM|e71dAmp ws7X+Y(GbW$ilxm4%*KqH**W+*fC*)DA@g_U$^0UY96;SnARU_{MAk3^0FhpgO8@`> delta 149 zcmZoMXfc=|#>B)qu~2NHo+2a5#(>?7j4YFRSiBieO^#q`@RF>qwlFc%Q7|?&snt=a zHZ(Ld&`~fmwy3S;O2eqxn~$+MGHzDjSi-!S for9kPXz6A_j_=Hq`9&N#K#G7GSvE(AtYHQK00thG diff --git a/examples/capture/.DS_Store b/examples/capture/.DS_Store index 7f5e1d7e48d1fb3563d26bd76f6ded543a57f5e8..e64d5dfdbc401dc960be3135940d34fc45c40bd4 100644 GIT binary patch delta 79 zcmZoMXffE3&ceEbfq}tt@+uas$R@?PtInQ iW1Ky?i&dU+&g50BEo{tzKz;d}P1r6mZf58B%MSphh8GV2 delta 94 zcmZoMXffE3&ceHsfr0VKe<+x|npJ7?G!|h-kI6YKT0BYR#RW+@`AG~645ubPU}@l% ttgf~&G1E~nHZ_@?!z#x(YjQWMJmYMjwwB3DSOhmaupMXI%*OGL9{?rKBTE1P diff --git a/examples/capture/example-capture/.DS_Store b/examples/capture/example-capture/.DS_Store index c2956be7726230b4ad95e7abf41a0894cf9ebe59..c97c0ac11a8c140e86f70a9d462708dd4f1b9f84 100644 GIT binary patch delta 153 zcmZoMXfc@J&&awlU^gQp>tr6LY{t`*E0`L*rK+n94Gk=H6pW3{YjqTW95VwQ1v6ud z+FDKyaaBWG&xG8{s_L5By4eh1z{m)p8Tg?zjG6;9RcUexbE2RCLlHwMLoq`MLjgk~ fLo!45WH}}|9!7DXN{#xXUDn delta 120 zcmZoMXfc@J&&aYdU^gQp%VZv=Y{pZQE0`L*B&(|}Ow4o?j7?2ybrh-%4b2R66wHh* zYHK+;#8nM#Jri;(tEy{i>t->40V5-XX5fd?Flsi?P^HPK%!!-DI0RWHHW+SZ=lIJH E0Oz_IS^xk5 diff --git a/examples/capture/example-capture/example-capture.xcodeproj/project.pbxproj b/examples/capture/example-capture/example-capture.xcodeproj/project.pbxproj index 164c1aa..22002ea 100644 --- a/examples/capture/example-capture/example-capture.xcodeproj/project.pbxproj +++ b/examples/capture/example-capture/example-capture.xcodeproj/project.pbxproj @@ -6,30 +6,22 @@ objectVersion = 50; objects = { -/* Begin PBXCopyFilesBuildPhase section */ - D8F9800B26FA41F6001C02B4 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; -/* End PBXCopyFilesBuildPhase section */ +/* Begin PBXBuildFile section */ + D8C007EC26FAE33C00FFB741 /* capture in Resources */ = {isa = PBXBuildFile; fileRef = D8C007EB26FAE33C00FFB741 /* capture */; }; + D8C007EE26FAFFDC00FFB741 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8C007ED26FAFFDC00FFB741 /* main.swift */; }; +/* End PBXBuildFile section */ /* Begin PBXFileReference section */ - D8F9800D26FA41F6001C02B4 /* example-capture */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "example-capture"; sourceTree = BUILT_PRODUCTS_DIR; }; - D8F9801826FA4230001C02B4 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - D8F9801A26FA4238001C02B4 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - D8F9801C26FA4244001C02B4 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D8F9801E26FA42E2001C02B4 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../src; sourceTree = ""; }; - D8F9802326FA4607001C02B4 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../../src; sourceTree = ""; }; - D8F9802526FA462E001C02B4 /* Cargo.toml */ = {isa = PBXFileReference; lastKnownFileType = text; name = Cargo.toml; path = ../../Cargo.toml; sourceTree = ""; }; + D8C007C026FAE25E00FFB741 /* example-capture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example-capture.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + D8C007CA26FAE25F00FFB741 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D8C007CB26FAE25F00FFB741 /* example_capture.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = example_capture.entitlements; sourceTree = ""; }; + D8C007E726FAE29D00FFB741 /* rustpack-capture.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "rustpack-capture.xcodeproj"; path = "rustpack-capture/rustpack-capture.xcodeproj"; sourceTree = ""; }; + D8C007EB26FAE33C00FFB741 /* capture */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = capture; path = ../../target/debug/capture; sourceTree = ""; }; + D8C007ED26FAFFDC00FFB741 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - D8F9800A26FA41F6001C02B4 /* Frameworks */ = { + D8C007BD26FAE25E00FFB741 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -39,54 +31,51 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - D8F9800426FA41F6001C02B4 = { + D8C007B726FAE25E00FFB741 = { isa = PBXGroup; children = ( - D8F9801E26FA42E2001C02B4 /* src */, - D8F9800F26FA41F6001C02B4 /* example-capture */, - D8F9800E26FA41F6001C02B4 /* Products */, - D8F9801726FA422F001C02B4 /* Frameworks */, + D8C007E726FAE29D00FFB741 /* rustpack-capture.xcodeproj */, + D8C007C226FAE25E00FFB741 /* example-capture */, + D8C007C126FAE25E00FFB741 /* Products */, ); sourceTree = ""; }; - D8F9800E26FA41F6001C02B4 /* Products */ = { + D8C007C126FAE25E00FFB741 /* Products */ = { isa = PBXGroup; children = ( - D8F9800D26FA41F6001C02B4 /* example-capture */, + D8C007C026FAE25E00FFB741 /* example-capture.app */, ); name = Products; sourceTree = ""; }; - D8F9800F26FA41F6001C02B4 /* example-capture */ = { + D8C007C226FAE25E00FFB741 /* example-capture */ = { isa = PBXGroup; children = ( - D8F9801C26FA4244001C02B4 /* Info.plist */, - D8F9802526FA462E001C02B4 /* Cargo.toml */, - D8F9802326FA4607001C02B4 /* src */, + D8C007EB26FAE33C00FFB741 /* capture */, + D8C007CA26FAE25F00FFB741 /* Info.plist */, + D8C007CB26FAE25F00FFB741 /* example_capture.entitlements */, + D8C007ED26FAFFDC00FFB741 /* main.swift */, ); path = "example-capture"; sourceTree = ""; }; - D8F9801726FA422F001C02B4 /* Frameworks */ = { + D8C007E826FAE29D00FFB741 /* Products */ = { isa = PBXGroup; children = ( - D8F9801A26FA4238001C02B4 /* CoreMedia.framework */, - D8F9801826FA4230001C02B4 /* AVFoundation.framework */, ); - name = Frameworks; + name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - D8F9800C26FA41F6001C02B4 /* example-capture */ = { + D8C007BF26FAE25E00FFB741 /* example-capture */ = { isa = PBXNativeTarget; - buildConfigurationList = D8F9801426FA41F6001C02B4 /* Build configuration list for PBXNativeTarget "example-capture" */; + buildConfigurationList = D8C007CE26FAE25F00FFB741 /* Build configuration list for PBXNativeTarget "example-capture" */; buildPhases = ( - D8F9802126FA438F001C02B4 /* Sources */, - D8F9802726FA465F001C02B4 /* Run Script */, - D8F9800A26FA41F6001C02B4 /* Frameworks */, - D8F9800B26FA41F6001C02B4 /* CopyFiles */, + D8C007BC26FAE25E00FFB741 /* Sources */, + D8C007BD26FAE25E00FFB741 /* Frameworks */, + D8C007BE26FAE25E00FFB741 /* Resources */, ); buildRules = ( ); @@ -94,24 +83,26 @@ ); name = "example-capture"; productName = "example-capture"; - productReference = D8F9800D26FA41F6001C02B4 /* example-capture */; - productType = "com.apple.product-type.tool"; + productReference = D8C007C026FAE25E00FFB741 /* example-capture.app */; + productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - D8F9800526FA41F6001C02B4 /* Project object */ = { + D8C007B826FAE25E00FFB741 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 1170; LastUpgradeCheck = 1170; ORGANIZATIONNAME = "l1npengtul-nokhwa"; TargetAttributes = { - D8F9800C26FA41F6001C02B4 = { + D8C007BF26FAE25E00FFB741 = { CreatedOnToolsVersion = 11.7; + LastSwiftMigration = 1170; }; }; }; - buildConfigurationList = D8F9800826FA41F6001C02B4 /* Build configuration list for PBXProject "example-capture" */; + buildConfigurationList = D8C007BB26FAE25E00FFB741 /* Build configuration list for PBXProject "example-capture" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; @@ -119,50 +110,46 @@ en, Base, ); - mainGroup = D8F9800426FA41F6001C02B4; - productRefGroup = D8F9800E26FA41F6001C02B4 /* Products */; + mainGroup = D8C007B726FAE25E00FFB741; + productRefGroup = D8C007C126FAE25E00FFB741 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D8C007E826FAE29D00FFB741 /* Products */; + ProjectRef = D8C007E726FAE29D00FFB741 /* rustpack-capture.xcodeproj */; + }, + ); projectRoot = ""; targets = ( - D8F9800C26FA41F6001C02B4 /* example-capture */, + D8C007BF26FAE25E00FFB741 /* example-capture */, ); }; /* End PBXProject section */ -/* Begin PBXShellScriptBuildPhase section */ - D8F9802726FA465F001C02B4 /* Run Script */ = { - isa = PBXShellScriptBuildPhase; +/* Begin PBXResourcesBuildPhase section */ + D8C007BE26FAE25E00FFB741 /* Resources */ = { + isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/capture", + D8C007EC26FAE33C00FFB741 /* capture in Resources */, ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\ncargo +nightly build --features \"input-avfoundation\" --out-dir . -Z unstable-options\ncargo clean\n"; }; -/* End PBXShellScriptBuildPhase section */ +/* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - D8F9802126FA438F001C02B4 /* Sources */ = { + D8C007BC26FAE25E00FFB741 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D8C007EE26FAFFDC00FFB741 /* main.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - D8F9801226FA41F6001C02B4 /* Debug */ = { + D8C007CC26FAE25F00FFB741 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -217,10 +204,12 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - D8F9801326FA41F6001C02B4 /* Release */ = { + D8C007CD26FAE25F00FFB741 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -268,49 +257,72 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; - D8F9801526FA41F6001C02B4 /* Debug */ = { + D8C007CF26FAE25F00FFB741 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "example-capture/example_capture.entitlements"; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = "$(SRCROOT)/example-capture/Info.plist"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "example-capture/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "l1npengtul-nokhwa.example-capture"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; - D8F9801626FA41F6001C02B4 /* Release */ = { + D8C007D026FAE25F00FFB741 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = "example-capture/example_capture.entitlements"; CODE_SIGN_STYLE = Automatic; - INFOPLIST_FILE = "$(SRCROOT)/example-capture/Info.plist"; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = "example-capture/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "l1npengtul-nokhwa.example-capture"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D8F9800826FA41F6001C02B4 /* Build configuration list for PBXProject "example-capture" */ = { + D8C007BB26FAE25E00FFB741 /* Build configuration list for PBXProject "example-capture" */ = { isa = XCConfigurationList; buildConfigurations = ( - D8F9801226FA41F6001C02B4 /* Debug */, - D8F9801326FA41F6001C02B4 /* Release */, + D8C007CC26FAE25F00FFB741 /* Debug */, + D8C007CD26FAE25F00FFB741 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - D8F9801426FA41F6001C02B4 /* Build configuration list for PBXNativeTarget "example-capture" */ = { + D8C007CE26FAE25F00FFB741 /* Build configuration list for PBXNativeTarget "example-capture" */ = { isa = XCConfigurationList; buildConfigurations = ( - D8F9801526FA41F6001C02B4 /* Debug */, - D8F9801626FA41F6001C02B4 /* Release */, + D8C007CF26FAE25F00FFB741 /* Debug */, + D8C007D026FAE25F00FFB741 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = D8F9800526FA41F6001C02B4 /* Project object */; + rootObject = D8C007B826FAE25E00FFB741 /* Project object */; } diff --git a/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate b/examples/capture/example-capture/example-capture.xcodeproj/project.xcworkspace/xcuserdata/pengg.xcuserdatad/UserInterfaceState.xcuserstate index 469319b9331ea940bd64b619cbc9be5511d15ff1..69396aa1310a13d99ddeac887aed9bcc08a7de5b 100644 GIT binary patch literal 30762 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 J@vnZ@{{avDi*^72 literal 16054 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 @@ -44,12 +44,22 @@ runnableDebuggingMode = "0"> + + + + + + diff --git a/examples/capture/example-capture/example-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist b/examples/capture/example-capture/example-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist index a3fce54..95a5c0c 100644 --- a/examples/capture/example-capture/example-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/examples/capture/example-capture/example-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ SuppressBuildableAutocreation - D8F9800C26FA41F6001C02B4 + D8C007BF26FAE25E00FFB741 primary diff --git a/examples/capture/example-capture/example-capture/.DS_Store b/examples/capture/example-capture/example-capture/.DS_Store index d8f87671f1f101ff3dc017b4c109a5ad82350c97..49a553e8a90714dacd22f3387406513b2a607e09 100644 GIT binary patch literal 6148 zcmeHK!EVz)5S?uUb&ODRKq^8Ud_m$6MTsI6Dum>ka>xzI2o8W!yK$+JV@I)rTSAa8 zdE@=vnTFM?v0d%^St_nrjBNvPwe zS(co~LHI0=%35^)@8RHZeD-ese(~Xxd^8f+)}(D~`~{y8Tq4_Bf0`y) z`Wl&B#U>A%B8%LigeEkljNYtB`vrLyrM->pQ?SO!PY@5~#yY{*Gy|V2MUmNV?<*X1jjgW&`p0cuTi+#` zl^F$$0{=(>-X9z|qpPt}D7OwY@(KWKU|1Sr{*%BQN29B;QivItP*k9z3Vp>8ijHwd z=eZgyg^Er>Up|EXWT9^;LVh~xJ2IVwtI)JY0i(dO0-LJa;`9IUpYQ+6B-1kr7zO?- z1w`Y(Kk)EK`fRN|9G|rg{1DE@d6hy*L8Fgj8Sqhj6D|#N4hKM2W2F!yF!M)1%3vC! Iz+YA1Cw&5@@c;k- delta 111 zcmZoMXfc=|#>CJ*u~2NHo}wrd0|Nsi1A_nqgC0WxLk>eGLoq|i#6tDS1|lqz*pw&R zun6+9G2}8N0%h`mYLX{+vdVAHV4KglS%HI{Wn)7T<7Rdaeh#3Cn*}+(Gf(ChF=S+z NY{MhHIYwj!GXP4?8C?JX diff --git a/examples/capture/example-capture/example-capture/Info.plist b/examples/capture/example-capture/example-capture/Info.plist index 21eef70..132275d 100644 --- a/examples/capture/example-capture/example-capture/Info.plist +++ b/examples/capture/example-capture/example-capture/Info.plist @@ -6,8 +6,6 @@ $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) - CFBundleIconFile - CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion @@ -20,17 +18,23 @@ 1.0 CFBundleVersion 1 + LSApplicationCategoryType + public.app-category.developer-tools LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) + NSCameraUsageDescription + we *notices buldge* wiww s-s-steaw aww youw webcam and pewsonyaw infowmation undew the x3 guise of b-being a &quot;nyokhwa exampwe c-captuwe app&quot; and a &quot;devewopew toow&quot; &lt;3 :3 *nyuzzwes yuwu* + NSMicrophoneUsageDescription + steawing yuwu dawta uwu owo NSHumanReadableCopyright Copywight © :3 2021 w1npengtuw, the x3 Nyokhwa Contwibutews. Wicensed undew the x3 MPL-2.0. *looks at you* You shouwd have weceived a copy with this softwawe, but if nyot i-it c-can be found at You c-can obtain onye at https://mozilla.org/MPL/2.0/. *huggles tightly* - NSCameraUsageDescription - we *notices buldge* wiww s-s-steaw aww youw webcam and pewsonyaw infowmation undew the x3 guise of b-being a "nyokhwa exampwe c-captuwe app" and a "devewopew toow" <3 :3 *nyuzzwes yuwu* NSPrincipalClass NSApplication NSSupportsAutomaticTermination NSSupportsSuddenTermination + RustBinName + capture diff --git a/examples/capture/example-capture/example-capture/example_capture.entitlements b/examples/capture/example-capture/example-capture/example_capture.entitlements new file mode 100644 index 0000000..1c2384a --- /dev/null +++ b/examples/capture/example-capture/example-capture/example_capture.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.device.usb + + com.apple.security.files.user-selected.read-only + + + diff --git a/examples/capture/example-capture/example-capture/main.c b/examples/capture/example-capture/example-capture/main.c deleted file mode 100644 index ed7b5f1..0000000 --- a/examples/capture/example-capture/example-capture/main.c +++ /dev/null @@ -1,15 +0,0 @@ -// -// main.c -// example-capture -// -// Created by pengg on 2021/09/22. -// Copyright © 2021 l1npengtul-nokhwa. All rights reserved. -// - -#include - -int main(int argc, const char * argv[]) { - // insert code here... - printf("Hello, World!\n"); - return 0; -} diff --git a/examples/capture/example-capture/example-capture/main.swift b/examples/capture/example-capture/example-capture/main.swift new file mode 100644 index 0000000..21ce866 --- /dev/null +++ b/examples/capture/example-capture/example-capture/main.swift @@ -0,0 +1,50 @@ +// +// main.swift +// example-capture +// +// Created by pengg on 2021/09/22. +// Copyright © 2021 l1npengtul-nokhwa. All rights reserved. +// + +import Cocoa + +func log(_ data: Data) { + if let message = NSString(data: data, encoding: String.Encoding.utf8.rawValue) { + print(message) + } +} + + +let task = Process() +let bundle = Bundle.main +let rustBinName = bundle.infoDictionary?["RustBinName"] as! String +task.launchPath = bundle.path(forResource: rustBinName, ofType: nil) +task.environment = ["RUST_BACKTRACE": "1"] + +let arguments = CommandLine.arguments +var passed_arguments = "" +for argument in arguments { + if !(argument.starts(with: "/") || argument.starts(with: "-NS")) && argument != "YES" && argument != "NO" { + passed_arguments += " " + passed_arguments += argument + } +} + +print(passed_arguments) + +let stdOut = Pipe() +let stdErr = Pipe() + +stdOut.fileHandleForReading.readabilityHandler = { log($0.availableData) } +stdErr.fileHandleForReading.readabilityHandler = { log($0.availableData) } + +task.standardOutput = stdOut +task.standardError = stdErr + +task.terminationHandler = { task in + (task.standardOutput as AnyObject?)?.fileHandleForReading.readabilityHandler = nil + (task.standardError as AnyObject?)?.fileHandleForReading.readabilityHandler = nil +} + +task.launch() +task.waitUntilExit() diff --git a/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/project.pbxproj b/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/project.pbxproj new file mode 100644 index 0000000..58890fc --- /dev/null +++ b/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/project.pbxproj @@ -0,0 +1,218 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXGroup section */ + D8C007DC26FAE29D00FFB741 = { + isa = PBXGroup; + children = ( + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXLegacyTarget section */ + D8C007E126FAE29D00FFB741 /* rustpack-capture */ = { + isa = PBXLegacyTarget; + buildArgumentsString = "$(ACTION)"; + buildConfigurationList = D8C007E426FAE29D00FFB741 /* Build configuration list for PBXLegacyTarget "rustpack-capture" */; + buildPhases = ( + ); + buildToolPath = /usr/bin/make; + dependencies = ( + ); + name = "rustpack-capture"; + passBuildSettingsInEnvironment = 1; + productName = "rustpack-capture"; + }; +/* End PBXLegacyTarget section */ + +/* Begin PBXProject section */ + D8C007DD26FAE29D00FFB741 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1170; + ORGANIZATIONNAME = "l1npengtul-nokhwa"; + TargetAttributes = { + D8C007E126FAE29D00FFB741 = { + CreatedOnToolsVersion = 11.7; + }; + }; + }; + buildConfigurationList = D8C007E026FAE29D00FFB741 /* Build configuration list for PBXProject "rustpack-capture" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D8C007DC26FAE29D00FFB741; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D8C007E126FAE29D00FFB741 /* rustpack-capture */, + ); + }; +/* End PBXProject section */ + +/* Begin XCBuildConfiguration section */ + D8C007E226FAE29D00FFB741 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + }; + name = Debug; + }; + D8C007E326FAE29D00FFB741 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + }; + name = Release; + }; + D8C007E526FAE29D00FFB741 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEBUGGING_SYMBOLS = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D8C007E626FAE29D00FFB741 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D8C007E026FAE29D00FFB741 /* Build configuration list for PBXProject "rustpack-capture" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D8C007E226FAE29D00FFB741 /* Debug */, + D8C007E326FAE29D00FFB741 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D8C007E426FAE29D00FFB741 /* Build configuration list for PBXLegacyTarget "rustpack-capture" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D8C007E526FAE29D00FFB741 /* Debug */, + D8C007E626FAE29D00FFB741 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D8C007DD26FAE29D00FFB741 /* Project object */; +} diff --git a/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist b/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000..cc4c734 --- /dev/null +++ b/examples/capture/example-capture/rustpack-capture/rustpack-capture.xcodeproj/xcuserdata/pengg.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + rustpack-capture.xcscheme_^#shared#^_ + + orderHint + 1 + + + + diff --git a/examples/capture/src/main.rs b/examples/capture/src/main.rs index 82bf053..56c3cb8 100644 --- a/examples/capture/src/main.rs +++ b/examples/capture/src/main.rs @@ -107,6 +107,8 @@ fn main() { use_backend = CaptureAPIBackend::AVFoundation; } + println!("www"); + match query_devices(use_backend) { Ok(devs) => { for (idx, camera) in devs.iter().enumerate() { @@ -119,8 +121,6 @@ fn main() { } } - println!("a"); - if matches.is_present("capture") { let backend_value = { match matches.value_of("capture-backend").unwrap() { @@ -196,8 +196,11 @@ fn main() { } } + println!("www"); + // open stream camera.open_stream().unwrap(); + println!("wwww"); loop { if let Ok(frame) = camera.frame() { println!( diff --git a/nokhwa-bindings-macos/Cargo.toml b/nokhwa-bindings-macos/Cargo.toml index cdaaef0..9554193 100644 --- a/nokhwa-bindings-macos/Cargo.toml +++ b/nokhwa-bindings-macos/Cargo.toml @@ -14,3 +14,5 @@ core-media-sys = "0.1.2" cocoa-foundation = "0.1.0" objc = { version = "0.2.7", features = ["exception"] } block = "0.1.6" +flume = "0.10.9" +dashmap = "4.0.2" \ No newline at end of file diff --git a/nokhwa-bindings-macos/src/lib.rs b/nokhwa-bindings-macos/src/lib.rs index b41b761..cc19802 100644 --- a/nokhwa-bindings-macos/src/lib.rs +++ b/nokhwa-bindings-macos/src/lib.rs @@ -54,24 +54,29 @@ pub enum AVFError { any(target_os = "macos", target_os = "ios"), link(name = "AVFoundation", kind = "framework") )] +#[allow(non_snake_case)] pub mod core_media { + // all of this is stolen from bindgen + // steal it idc use core_media_sys::{ CMBlockBufferRef, CMFormatDescriptionRef, CMSampleBufferRef, CMTime, CMVideoDimensions, + FourCharCode, }; - use objc::runtime::Object; + use objc::{runtime::Object, Message}; + use std::ops::Deref; - pub type id = *mut Object; + pub type Id = *mut Object; #[repr(transparent)] #[derive(Clone)] - pub struct NSObject(pub id); - impl std::ops::Deref for NSObject { + pub struct NSObject(pub Id); + impl Deref for NSObject { type Target = objc::runtime::Object; fn deref(&self) -> &Self::Target { unsafe { &*self.0 } } } - unsafe impl objc::Message for NSObject {} + unsafe impl Message for NSObject {} impl NSObject { pub fn alloc() -> Self { Self(unsafe { msg_send!(objc::class!(NSObject), alloc) }) @@ -80,14 +85,14 @@ pub mod core_media { #[repr(transparent)] #[derive(Clone)] - pub struct NSString(pub id); - impl std::ops::Deref for NSString { + pub struct NSString(pub Id); + impl Deref for NSString { type Target = objc::runtime::Object; fn deref(&self) -> &Self::Target { unsafe { &*self.0 } } } - unsafe impl objc::Message for NSString {} + unsafe impl Message for NSString {} impl NSString { pub fn alloc() -> Self { Self(unsafe { msg_send!(objc::class!(NSString), alloc) }) @@ -95,34 +100,17 @@ pub mod core_media { } pub type AVMediaType = NSString; + extern "C" { pub static AVMediaTypeVideo: AVMediaType; - } - extern "C" { pub static AVMediaTypeAudio: AVMediaType; - } - extern "C" { pub static AVMediaTypeText: AVMediaType; - } - extern "C" { pub static AVMediaTypeClosedCaption: AVMediaType; - } - extern "C" { pub static AVMediaTypeSubtitle: AVMediaType; - } - extern "C" { pub static AVMediaTypeTimecode: AVMediaType; - } - extern "C" { pub static AVMediaTypeMetadata: AVMediaType; - } - extern "C" { pub static AVMediaTypeMuxed: AVMediaType; - } - extern "C" { pub static AVMediaTypeMetadataObject: AVMediaType; - } - extern "C" { pub static AVMediaTypeDepthData: AVMediaType; } @@ -146,49 +134,116 @@ pub mod core_media { pub fn CMSampleBufferGetDataBuffer(sbuf: CMSampleBufferRef) -> CMBlockBufferRef; } - pub type dispatch_queue_t = NSObject; - extern "C" { pub fn dispatch_queue_create( label: *const ::std::os::raw::c_char, attr: NSObject, - ) -> dispatch_queue_t; + ) -> NSObject; } extern "C" { pub fn dispatch_release(object: NSObject); } + + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct __CVBuffer { + _unused: [u8; 0], + } + pub type CVBufferRef = *mut __CVBuffer; + + #[allow(non_snake_case)] + extern "C" { + pub fn CMSampleBufferGetImageBuffer(sbuf: CMSampleBufferRef) -> CVImageBufferRef; + } + + pub type CVImageBufferRef = CVBufferRef; + pub type CVPixelBufferRef = CVImageBufferRef; + pub type CVPixelBufferLockFlags = u64; + pub type CVReturn = i32; + + #[allow(non_snake_case)] + extern "C" { + pub fn CVPixelBufferLockBaseAddress( + pixelBuffer: CVPixelBufferRef, + lockFlags: CVPixelBufferLockFlags, + ) -> CVReturn; + + pub fn CVPixelBufferUnlockBaseAddress( + pixelBuffer: CVPixelBufferRef, + unlockFlags: CVPixelBufferLockFlags, + ) -> CVReturn; + + pub fn CVPixelBufferGetDataSize(pixelBuffer: CVPixelBufferRef) -> std::os::raw::c_ulong; + + pub fn CVPixelBufferGetBaseAddress( + pixelBuffer: CVPixelBufferRef, + ) -> *mut ::std::os::raw::c_void; + } + + extern "C" { + pub static AVVideoCodecKey: NSString; + } + pub type OSType = FourCharCode; + pub type AVVideoCodecType = NSString; + extern "C" { + pub static AVVideoCodecTypeHEVC: AVVideoCodecType; + pub static AVVideoCodecTypeH264: AVVideoCodecType; + pub static AVVideoCodecTypeJPEG: AVVideoCodecType; + pub static AVVideoCodecTypeAppleProRes4444: AVVideoCodecType; + pub static AVVideoCodecTypeAppleProRes422: AVVideoCodecType; + pub static AVVideoCodecTypeAppleProRes422HQ: AVVideoCodecType; + pub static AVVideoCodecTypeAppleProRes422LT: AVVideoCodecType; + pub static AVVideoCodecTypeAppleProRes422Proxy: AVVideoCodecType; + pub static AVVideoCodecTypeHEVCWithAlpha: AVVideoCodecType; + pub static AVVideoCodecHEVC: NSString; + pub static AVVideoCodecH264: NSString; + pub static AVVideoCodecJPEG: NSString; + pub static AVVideoCodecAppleProRes4444: NSString; + pub static AVVideoCodecAppleProRes422: NSString; + pub static AVVideoWidthKey: NSString; + pub static AVVideoHeightKey: NSString; + pub static AVVideoExpectedSourceFrameRateKey: NSString; + pub fn CVPixelBufferGetPixelFormatType(pixelBuffer: CVPixelBufferRef) -> OSType; + + } } #[cfg(any(target_os = "macos", target_os = "ios"))] pub mod avfoundation { - use crate::core_media::{dispatch_queue_create, dispatch_release, AVMediaTypeVideo, NSObject}; + use crate::core_media::{ + AVMediaTypeAudio, AVMediaTypeClosedCaption, AVMediaTypeDepthData, AVMediaTypeMetadata, + AVMediaTypeMetadataObject, AVMediaTypeMuxed, AVMediaTypeSubtitle, AVMediaTypeText, + AVMediaTypeTimecode, CVPixelBufferGetPixelFormatType, + }; use crate::{ core_media::{ - CMBlockBufferCopyDataBytes, CMBlockBufferGetDataLength, CMSampleBufferGetDataBuffer, - CMTimeMake, CMVideoFormatDescriptionGetDimensions, + dispatch_queue_create, AVMediaTypeVideo, CMSampleBufferGetImageBuffer, + CMVideoFormatDescriptionGetDimensions, CVImageBufferRef, CVPixelBufferGetBaseAddress, + CVPixelBufferGetDataSize, CVPixelBufferLockBaseAddress, CVPixelBufferUnlockBaseAddress, + NSObject, }, AVFError, }; use block::ConcreteBlock; use cocoa_foundation::foundation::{NSArray, NSInteger, NSString, NSUInteger}; use core_media_sys::{ - kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, CMBlockBufferRef, - CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, CMTime, CMVideoDimensions, + kCMPixelFormat_422YpCbCr8_yuvs, kCMVideoCodecType_422YpCbCr8, kCMVideoCodecType_JPEG, + kCMVideoCodecType_JPEG_OpenDML, CMFormatDescriptionGetMediaSubType, CMSampleBufferRef, + CMVideoDimensions, }; + use dashmap::DashMap; + use flume::{Receiver, Sender}; use objc::{ declare::ClassDecl, runtime::{Class, Object, Protocol, Sel, BOOL, YES}, }; - use std::ffi::CString; - use std::os::raw::c_char; use std::{ - borrow::{Borrow, Cow}, + borrow::Cow, cmp::Ordering, convert::TryFrom, error::Error, - ffi::{c_void, CStr}, - os::raw::c_int, + ffi::{c_void, CStr, CString}, sync::{ atomic::{AtomicBool, Ordering as MemOrdering}, Arc, Mutex, TryLockError, @@ -306,82 +361,91 @@ pub mod avfoundation { Ok(out_vec) } + fn compare_ns_string(this: *mut Object, other: crate::core_media::NSString) -> bool { + unsafe { + let equal: BOOL = msg_send![this, isEqualToString: other]; + equal == YES + } + } + fn default_callback(_: bool) {} + pub type CompressionData<'a> = (Cow<'a, [u8]>, AVFourCC); + pub type DataPipe<'a> = (Sender>, Receiver>); + lazy_static! { static ref CAMERA_AUTHORIZED: Arc = Arc::new(AtomicBool::new(false)); static ref USER_CALLBACK_FN: Arc> = Arc::new(Mutex::new(default_callback)); + static ref PIPE_MAP: Arc>> = Arc::new(DashMap::new()); static ref CALLBACK_CLASS: &'static Class = { let mut decl = ClassDecl::new("MyCaptureCallback", class!(NSObject)).unwrap(); // frame stack - decl.add_ivar::<*mut c_void>("_frame_data"); - decl.add_ivar::("_frame_length"); + decl.add_ivar::("_index"); - extern "C" fn my_callback_get_data_ptr(this: &mut Object, _: Sel) -> *mut c_void { + extern "C" fn my_callback_get_index(this: &Object, _: Sel) -> usize { unsafe { - *this.get_ivar("_frame_data") + *this.get_ivar("_index") } } - extern "C" fn my_callback_set_data_ptr(this: &mut Object, _: Sel, ptr: *mut c_void) { - unsafe { - this.set_ivar("_frame_data", ptr); - } - } - - extern "C" fn my_callback_get_data_length(this: &Object, _: Sel) -> usize { + extern "C" fn my_callback_set_index(this: &mut Object, _: Sel, new_index: usize) { unsafe { - *this.get_ivar("_frame_length") - } - } - - extern "C" fn my_callback_set_data_length(this: &mut Object, _: Sel, new_length: usize) { - unsafe { - this.set_ivar("_frame_length", new_length); + this.set_ivar("_index", new_index); } } // Delegate compliance method + // SAFETY: This assumes that the buffer byte size is a u8. Any other size will cause unsafety. #[allow(non_snake_case)] + #[allow(non_upper_case_globals)] extern fn capture_out_callback(this: &mut Object, _: Sel, _: *mut Object, didOutputSampleBuffer: CMSampleBufferRef, _: *mut Object) { - println!("aaa"); - let data_buffer: CMBlockBufferRef = unsafe { CMSampleBufferGetDataBuffer(didOutputSampleBuffer) }; - let length = unsafe { CMBlockBufferGetDataLength(data_buffer) } as usize; - let ptr: *mut c_void = unsafe { msg_send![this, dataPointer] }; - let _: c_int = unsafe { CMBlockBufferCopyDataBytes(data_buffer, 0, length, ptr) }; - let _: () = unsafe { msg_send![this, setDataLength:length] }; + let image_buffer: CVImageBufferRef = unsafe { CMSampleBufferGetImageBuffer(didOutputSampleBuffer) }; + unsafe { CVPixelBufferLockBaseAddress(image_buffer, 0); }; + + let buffer_codec = unsafe { CVPixelBufferGetPixelFormatType(image_buffer) }; + + let fourcc = match buffer_codec { + kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, + kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, + _ => { + return; + } + }; + + let buffer_length = unsafe { CVPixelBufferGetDataSize(image_buffer) }; + let buffer_ptr = unsafe { CVPixelBufferGetBaseAddress(image_buffer) }; + let buffer_as_vec = unsafe { std::slice::from_raw_parts_mut(buffer_ptr as *mut u8, buffer_length as usize).to_vec() }; + + unsafe { CVPixelBufferUnlockBaseAddress(image_buffer, 0) }; + let index: usize = unsafe { msg_send![this, index] }; + let pipes = &PIPE_MAP.get(&index); + if let Some(pipe) = pipes { + let _ = pipe.value().0.send((Cow::from(buffer_as_vec), fourcc)); + } + } + + #[allow(non_snake_case)] + extern fn capture_drop_callback(_: &mut Object, _: Sel, _: *mut Object, _: *mut Object, _: *mut Object) { } unsafe { - decl.add_protocol(Protocol::get("AVCaptureVideoDataOutputSampleBufferDelegate").unwrap()); - decl.add_method( - sel!(dataPointer), my_callback_get_data_ptr as extern "C" fn(&mut Object, Sel) -> *mut c_void + sel!(index), my_callback_get_index as extern "C" fn(&Object, Sel) -> usize ); decl.add_method( - sel!(setDataPointer:), my_callback_set_data_ptr as extern "C" fn(&mut Object, Sel, *mut c_void) - ); - decl.add_method( - sel!(dataLength), my_callback_get_data_length as extern "C" fn(&Object, Sel) -> usize - ); - decl.add_method( - sel!(setDataLength:), my_callback_set_data_length as extern "C" fn(&mut Object, Sel, usize) + sel!(setIndex:), my_callback_set_index as extern "C" fn(&mut Object, Sel, usize) ); decl.add_method( sel!(captureOutput:didOutputSampleBuffer:fromConnection:), capture_out_callback as extern "C" fn(&mut Object, Sel, *mut Object, CMSampleBufferRef, *mut Object) ); + decl.add_method( + sel!(captureOutput:didDropSampleBuffer:fromConnection:), capture_drop_callback as extern "C" fn(&mut Object, Sel, *mut Object, *mut Object, *mut Object) + ); + + decl.add_protocol(Protocol::get("AVCaptureVideoDataOutputSampleBufferDelegate").unwrap()); } - decl.register() - }; - static ref OS_DISPATCH_CLASS: &'static Class = { - let mut decl = ClassDecl::new("MyDispatchQueueSerial", class!(NSObject)).unwrap(); - - // decl.add_protocol(Protocol::get("OS_dispatch_object").unwrap()); - // decl.add_protocol(Protocol::get("OS_dispatch_queue").unwrap()); - decl.add_protocol(Protocol::get("OS_dispatch_queue_serial").unwrap()); - decl.register() }; } @@ -526,16 +590,16 @@ pub mod avfoundation { impl From for *mut Object { fn from(media_type: AVMediaType) -> Self { match media_type { - AVMediaType::Audio => str_to_nsstr("AVMediaTypeAudio"), - AVMediaType::ClosedCaption => str_to_nsstr("AVMediaTypeClosedCaption"), - AVMediaType::DepthData => str_to_nsstr("AVMediaTypeDepthData"), - AVMediaType::Metadata => str_to_nsstr("AVMediaTypeMetadata"), - AVMediaType::MetadataObject => str_to_nsstr("AVMediaTypeMetadataObject"), - AVMediaType::Muxed => str_to_nsstr("AVMediaTypeMuxed"), - AVMediaType::Subtitle => str_to_nsstr("AVMediaTypeSubtitle"), - AVMediaType::Text => str_to_nsstr("AVMediaTypeText"), - AVMediaType::Timecode => str_to_nsstr("AVMediaTypeTimecode"), - AVMediaType::Video => str_to_nsstr("AVMediaTypeVideo"), + AVMediaType::Audio => unsafe { AVMediaTypeAudio.0 }, + AVMediaType::ClosedCaption => unsafe { AVMediaTypeClosedCaption.0 }, + AVMediaType::DepthData => unsafe { AVMediaTypeDepthData.0 }, + AVMediaType::Metadata => unsafe { AVMediaTypeMetadata.0 }, + AVMediaType::MetadataObject => unsafe { AVMediaTypeMetadataObject.0 }, + AVMediaType::Muxed => unsafe { AVMediaTypeMuxed.0 }, + AVMediaType::Subtitle => unsafe { AVMediaTypeSubtitle.0 }, + AVMediaType::Text => unsafe { AVMediaTypeText.0 }, + AVMediaType::Timecode => unsafe { AVMediaTypeTimecode.0 }, + AVMediaType::Video => unsafe { AVMediaTypeVideo.0 }, } } } @@ -544,28 +608,34 @@ pub mod avfoundation { type Error = AVFError; fn try_from(value: *mut Object) -> Result { - let borrow_str = nsstr_to_str(value); - let value_str: &str = borrow_str.borrow(); - let v = match value_str { - "AVMediaTypeAudio" => Ok(AVMediaType::Audio), - "AVMediaTypeClosedCaption" => Ok(AVMediaType::ClosedCaption), - "AVMediaTypeDepthData" => Ok(AVMediaType::DepthData), - "AVMediaTypeMetadata" => Ok(AVMediaType::Metadata), - "AVMediaTypeMetadataObject" => Ok(AVMediaType::MetadataObject), - "AVMediaTypeMuxed" => Ok(AVMediaType::Muxed), - "AVMediaTypeSubtitle" => Ok(AVMediaType::Subtitle), - "AVMediaTypeText" => Ok(AVMediaType::Text), - "AVMediaTypeTimecode" => Ok(AVMediaType::Timecode), - "AVMediaTypeVideo" => Ok(AVMediaType::Video), - _ => { - return Err(AVFError::InvalidValue { - found: value_str.to_string(), + unsafe { + if compare_ns_string(value, (AVMediaTypeAudio).clone()) { + Ok(AVMediaType::Audio) + } else if compare_ns_string(value, (AVMediaTypeClosedCaption).clone()) { + Ok(AVMediaType::ClosedCaption) + } else if compare_ns_string(value, (AVMediaTypeDepthData).clone()) { + Ok(AVMediaType::DepthData) + } else if compare_ns_string(value, (AVMediaTypeMetadata).clone()) { + Ok(AVMediaType::Metadata) + } else if compare_ns_string(value, (AVMediaTypeMetadataObject).clone()) { + Ok(AVMediaType::MetadataObject) + } else if compare_ns_string(value, (AVMediaTypeMuxed).clone()) { + Ok(AVMediaType::Muxed) + } else if compare_ns_string(value, (AVMediaTypeSubtitle).clone()) { + Ok(AVMediaType::Subtitle) + } else if compare_ns_string(value, (AVMediaTypeText).clone()) { + Ok(AVMediaType::Text) + } else if compare_ns_string(value, (AVMediaTypeTimecode).clone()) { + Ok(AVMediaType::Timecode) + } else if compare_ns_string(value, (AVMediaTypeVideo).clone()) { + Ok(AVMediaType::Video) + } else { + let name = nsstr_to_str(value); + Err(AVFError::InvalidValue { + found: name.to_string(), }) } - }; - - let _: *mut std::ffi::c_void = unsafe { msg_send![value, autorelease] }; - v + } } } @@ -595,8 +665,8 @@ pub mod avfoundation { #[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] #[repr(u32)] pub enum AVFourCC { - YUV2 = kCMVideoCodecType_JPEG, - MJPEG = kCMVideoCodecType_422YpCbCr8, + YUV2, + MJPEG, } // Localized Name @@ -636,36 +706,66 @@ pub mod avfoundation { } pub struct AVCaptureVideoCallback { + index: usize, delegate: *mut Object, - queue: *mut Object, } impl AVCaptureVideoCallback { - pub fn new() -> Self { - AVCaptureVideoCallback::default() + pub fn new(index: usize) -> Self { + let cls = &CALLBACK_CLASS as &Class; + let delegate: *mut Object = unsafe { msg_send![cls, alloc] }; + let delegate: *mut Object = unsafe { msg_send![delegate, init] }; + + let data_pipe: DataPipe = flume::unbounded(); + let _ = &PIPE_MAP.insert(index, data_pipe); + + AVCaptureVideoCallback { index, delegate } + } + + pub fn index(&self) -> usize { + self.index } pub fn data_len(&self) -> usize { unsafe { msg_send![self.delegate, dataLength] } } - pub fn frame_to_slice<'a>(&self) -> Result, AVFError> { - let data_ptr: *mut c_void = unsafe { msg_send![self.delegate, dataPointer] }; - println!("tp1"); - let data_ptr = data_ptr as *mut u8 as *const u8; - let data_length = self.data_len(); - println!("tp2"); - if data_ptr.is_null() { - return Err(AVFError::ReadFrame("Nullptr".to_string())); - } - println!("tp3"); - if data_length == 0 { - return Err(AVFError::ReadFrame("Zero Size Len".to_string())); - } - println!("tp4"); - let cow_slice = Cow::from(unsafe { std::slice::from_raw_parts(data_ptr, data_length) }); - println!("tp5"); - Ok(cow_slice) + pub fn frame_to_slice<'a>(&self) -> Result, AVFError> { + let pipe_map = &PIPE_MAP.get(&self.index); // why rust + let pipe_recv = match pipe_map { + Some(pipe) => &pipe.value().1, + None => return Err(AVFError::ReadFrame("Data Pipe None".to_string())), + }; + let data = match pipe_recv.drain().last() { + Some(frame) => frame, + None => match pipe_recv.recv() { + Ok(f) => f, + Err(why) => { + return Err(AVFError::ReadFrame(format!( + "Failed to read frame from pipe: {}", + why.to_string() + ))) + } + }, + }; + Ok(data) + } + + pub fn frame_to_slice_no_block<'a>(&self) -> Result, AVFError> { + let pipe_map = &PIPE_MAP.get(&self.index); // why rust + let pipe_recv = match pipe_map { + Some(pipe) => &pipe.value().1, + None => return Err(AVFError::ReadFrame("Data Pipe None".to_string())), + }; + let data = match pipe_recv.drain().last() { + Some(frame) => frame, + None => { + return Err(AVFError::ReadFrame( + "Failed to read frame from pipe: None".to_string(), + )) + } + }; + Ok(data) } pub fn inner(&self) -> *mut Object { @@ -673,29 +773,6 @@ pub mod avfoundation { } } - impl Default for AVCaptureVideoCallback { - fn default() -> Self { - println!("b"); - let cls = &CALLBACK_CLASS as &Class; - let delegate: *mut Object = unsafe { msg_send![cls, alloc] }; - let delegate: *mut Object = unsafe { msg_send![delegate, init] }; - println!("c"); - let ptr: *mut c_void = std::ptr::null_mut(); - let len = 0_usize; - - let queue_cls = &OS_DISPATCH_CLASS as &Class; - let queue: *mut Object = unsafe { msg_send![queue_cls, new] }; - - unsafe { - let _: () = msg_send![delegate, setDataPointer: ptr]; - let _: () = msg_send![delegate, setDataLength: len]; - } - println!("c"); - - AVCaptureVideoCallback { delegate, queue } - } - } - impl Drop for AVCaptureVideoCallback { fn drop(&mut self) { unsafe { @@ -727,29 +804,20 @@ pub mod avfoundation { #[derive(Copy, Clone, Debug)] pub struct CaptureDeviceFormatDescriptor { pub resolution: AVVideoResolution, - pub fps: f64, + pub fps: u32, pub fourcc: AVFourCC, } impl CaptureDeviceFormatDescriptor { pub fn compatible_with_capture_format(&self, other: &AVCaptureDeviceFormat) -> bool { - let lower_fps = match other.fps_list.get(0) { - Some(fps) => fps, - None => return false, - }; - - let higher_fps = match other.fps_list.get(1) { - Some(fps) => fps, - None => return false, - }; - - if self.resolution.height == other.resolution.height - && self.resolution.width == other.resolution.width - && self.fourcc == other.fourcc - && ((self.fps - *lower_fps).abs() < f64::EPSILON - || (self.fps - *higher_fps).abs() < f64::EPSILON) - { - return true; + for fps in &other.fps_list { + if self.resolution.height == other.resolution.height + && self.resolution.width == other.resolution.width + && self.fourcc == other.fourcc + && (*fps as u32) == self.fps + { + return true; + } } false } @@ -791,8 +859,8 @@ pub mod avfoundation { unsafe { CMFormatDescriptionGetMediaSubType(description_obj as *mut c_void) }; #[allow(non_upper_case_globals)] let fourcc = match fcc_raw { - kCMVideoCodecType_422YpCbCr8 => AVFourCC::YUV2, - kCMVideoCodecType_JPEG => AVFourCC::MJPEG, + kCMVideoCodecType_422YpCbCr8 | kCMPixelFormat_422YpCbCr8_yuvs => AVFourCC::YUV2, + kCMVideoCodecType_JPEG | kCMVideoCodecType_JPEG_OpenDML => AVFourCC::MJPEG, _ => { return Err(AVFError::InvalidValue { found: fcc_raw.to_string(), @@ -851,7 +919,7 @@ pub mod avfoundation { pub fn devices(&self) -> Vec { let device_ns_array: *mut Object = unsafe { msg_send![self.inner, devices] }; - let objects_len: NSUInteger = unsafe { device_ns_array.count() }; + let objects_len: NSUInteger = unsafe { NSArray::count(device_ns_array) }; let mut devices = vec![AVCaptureDeviceDescriptor::default(); objects_len as usize]; for index in 0..objects_len { let device = unsafe { device_ns_array.objectAtIndex(index) }; @@ -958,14 +1026,7 @@ pub mod avfoundation { unsafe { msg_send![self.inner, unlockForConfiguration] } } - pub fn set_frame_rate(&mut self, fps: u32) { - let mut time = unsafe { CMTimeMake(1, fps as i32) }; - let time_ptr: *mut CMTime = &mut time; - unsafe { - let _: () = msg_send![self.inner, activeVideoMinFrameDuration: time_ptr]; - let _: () = msg_send![self.inner, activeVideoMaxFrameDuration: time_ptr]; - } - } + pub fn set_frame_rate(&mut self, _: u32) {} pub fn set_all( &mut self, @@ -1031,24 +1092,30 @@ pub mod avfoundation { pub fn add_delegate(&self, delegate: &AVCaptureVideoCallback) -> Result<(), AVFError> { unsafe { + let avf_queue_str = match CString::new("avf_queue") { + Ok(avf) => avf.into_raw(), + Err(_) => { + // should not happen + return Err(AVFError::StreamOpen("String contains null? This is a bug, please report it https://github.com/l1npengtul/nokhwa".to_string())); + } + }; + let queue = dispatch_queue_create(avf_queue_str, NSObject(std::ptr::null_mut())); + let _: () = msg_send![ self.inner, setSampleBufferDelegate: delegate.delegate - queue: delegate.queue + queue: queue ]; - } - println!("e"); + }; Ok(()) } } impl Default for AVCaptureVideoDataOutput { fn default() -> Self { - println!("d"); let cls = class!(AVCaptureVideoDataOutput); - let inner: *mut Object = unsafe { msg_send![cls, alloc] }; - let inner: *mut Object = unsafe { msg_send![inner, init] }; - println!("d"); + let inner: *mut Object = unsafe { msg_send![cls, new] }; + AVCaptureVideoDataOutput { inner } } } @@ -1064,8 +1131,15 @@ pub mod avfoundation { AVCaptureSession::default() } + pub fn begin_configuration(&self) { + unsafe { msg_send![self.inner, beginConfiguration] } + } + + pub fn commit_configuration(&self) { + unsafe { msg_send![self.inner, commitConfiguration] } + } + pub fn can_add_input(&self, input: &AVCaptureDeviceInput) -> bool { - println!("{:?}", unsafe { self.inner.as_ref() }.unwrap().class()); let result: BOOL = unsafe { msg_send![self.inner, canAddInput:input.inner] }; result == YES } diff --git a/src/backends/capture/avfoundation.rs b/src/backends/capture/avfoundation.rs index ca35cf7..e221d93 100644 --- a/src/backends/capture/avfoundation.rs +++ b/src/backends/capture/avfoundation.rs @@ -11,7 +11,7 @@ use crate::{ use image::{ImageBuffer, Rgb}; use nokhwa_bindings_macos::avfoundation::{ query_avfoundation, AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, - AVCaptureVideoCallback, AVCaptureVideoDataOutput, + AVCaptureVideoCallback, AVCaptureVideoDataOutput, AVFourCC, }; use std::{any::Any, borrow::Cow, collections::HashMap}; @@ -20,6 +20,8 @@ use std::{any::Any, borrow::Cow, collections::HashMap}; /// # Quirks /// - While working with `iOS` is allowed, it is not officially supported and may not work. /// - You **must** call [`nokhwa_initialize`](crate::nokhwa_initialize) **before** doing anything with `AVFoundation`. +/// - This only works on 64 bit platforms. +/// - FPS adjustment does not work. pub struct AVFoundationCaptureDevice { device: AVCaptureDevice, dev_input: Option, @@ -52,7 +54,9 @@ impl AVFoundationCaptureDevice { } }; - let device = AVCaptureDevice::from_id(&device_descriptor.misc())?; + let mut device = AVCaptureDevice::from_id(&device_descriptor.misc())?; + device.lock()?; + device.set_all(camera_format.into())?; Ok(AVFoundationCaptureDevice { device, @@ -95,10 +99,8 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { } fn set_camera_format(&mut self, new_fmt: CameraFormat) -> Result<(), NokhwaError> { - self.device.lock()?; self.device.set_all(new_fmt.into())?; self.format = new_fmt; - self.device.unlock(); Ok(()) } @@ -208,11 +210,13 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { fn open_stream(&mut self) -> Result<(), NokhwaError> { let input = AVCaptureDeviceInput::new(&self.device)?; let session = AVCaptureSession::new(); + session.begin_configuration(); session.add_input(&input)?; - let callback = AVCaptureVideoCallback::new(); + let callback = AVCaptureVideoCallback::new(self.info.index()); let output = AVCaptureVideoDataOutput::new(); output.add_delegate(&callback)?; session.add_output(&output)?; + session.commit_configuration(); session.start()?; self.dev_input = Some(input); @@ -238,11 +242,7 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { fn frame(&mut self) -> Result, Vec>, NokhwaError> { let cam_fmt = self.camera_format(); - let raw_frame = self.frame_raw()?; - let conv = match cam_fmt.format() { - FrameFormat::MJPEG => mjpeg_to_rgb888(&raw_frame)?, - FrameFormat::YUYV => yuyv422_to_rgb888(&raw_frame)?, - }; + let conv = self.frame_raw()?.to_vec(); let image_buf = match ImageBuffer::from_vec(cam_fmt.width(), cam_fmt.height(), conv) { Some(buf) => { @@ -258,9 +258,33 @@ impl CaptureBackendTrait for AVFoundationCaptureDevice { } fn frame_raw(&mut self) -> Result, NokhwaError> { + match &self.session { + Some(session) => { + if !session.is_running() { + return Err(NokhwaError::ReadFrameError( + "Stream Not Started".to_string(), + )); + } + if session.is_interrupted() { + return Err(NokhwaError::ReadFrameError( + "Stream Interrupted".to_string(), + )); + } + } + None => { + return Err(NokhwaError::ReadFrameError( + "Stream Not Started".to_string(), + )) + } + } + match &self.data_collect { Some(collector) => { let data = collector.frame_to_slice()?; + let data = match data.1 { + AVFourCC::YUV2 => Cow::from(yuyv422_to_rgb888(&data.0)?), + AVFourCC::MJPEG => Cow::from(mjpeg_to_rgb888(&data.0)?), + }; Ok(data) } None => Err(NokhwaError::ReadFrameError( diff --git a/src/utils.rs b/src/utils.rs index a7ef2a6..49409ec 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -372,7 +372,7 @@ impl From for CaptureDeviceFormatDescriptor { width: cf.width() as i32, height: cf.height() as i32, }, - fps: cf.frame_rate() as f64, + fps: cf.frame_rate(), fourcc: cf.format().into(), } }