diff --git a/.clang-format b/.clang-format index 2643c1b58a..fef62d1d1c 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,4 @@ --- -Language: Cpp BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 386ed6fefc..f603a50910 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,7 +46,7 @@ jobs: build-host: env: - MACOSX_DEPLOYMENT_TARGET: 10.14 + MACOSX_DEPLOYMENT_TARGET: 10.15 strategy: fail-fast: false matrix: diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 698377a210..ebdf143724 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -9,5 +9,5 @@ repositories { } } dependencies { - implementation "edu.wpi.first:native-utils:2023.10.0" + implementation "edu.wpi.first:native-utils:2023.11.0" } diff --git a/cscore/.styleguide b/cscore/.styleguide index f5e99ed9f1..dadecd21e7 100644 --- a/cscore/.styleguide +++ b/cscore/.styleguide @@ -13,6 +13,10 @@ cppSrcFileInclude { \.cpp$ } +modifiableFileExclude { + objcpp +} + licenseUpdateExclude { src/main/native/cpp/default_init_allocator\.h$ } diff --git a/cscore/CMakeLists.txt b/cscore/CMakeLists.txt index 0b39f27df6..af0d803429 100644 --- a/cscore/CMakeLists.txt +++ b/cscore/CMakeLists.txt @@ -11,6 +11,7 @@ file(GLOB cscore_native_src src/main/native/cpp/*.cpp) file(GLOB cscore_linux_src src/main/native/linux/*.cpp) file(GLOB cscore_osx_src src/main/native/osx/*.cpp) +file(GLOB cscore_osx_objc_src src/main/native/objcpp/*.mm) file(GLOB cscore_windows_src src/main/native/windows/*.cpp) add_library(cscore ${cscore_native_src}) @@ -18,7 +19,9 @@ set_target_properties(cscore PROPERTIES DEBUG_POSTFIX "d") if(NOT MSVC) if (APPLE) - target_sources(cscore PRIVATE ${cscore_osx_src}) + target_sources(cscore PRIVATE ${cscore_osx_src} ${cscore_osx_objc_src}) + target_compile_options(cscore PRIVATE "-fobjc-arc") + set_target_properties(cscore PROPERTIES LINK_FLAGS "-framework CoreFoundation -framework AVFoundation -framework Foundation -framework CoreMedia -framework CoreVideo") else() target_sources(cscore PRIVATE ${cscore_linux_src}) endif() diff --git a/cscore/build.gradle b/cscore/build.gradle index 50c70266c2..a656731519 100644 --- a/cscore/build.gradle +++ b/cscore/build.gradle @@ -2,16 +2,16 @@ import org.gradle.internal.os.OperatingSystem ext { nativeName = 'cscore' - devMain = 'edu.wpi.cscore.DevMain' + devMain = 'edu.wpi.first.cscore.DevMain' } // Removed because having the objective-cpp plugin added breaks // embedded tools and its toolchain check. It causes an obj-cpp // source set to be added to all binaries, even cross binaries // with no support. -// if (OperatingSystem.current().isMacOsX()) { -// apply plugin: 'objective-cpp' -// } +if (OperatingSystem.current().isMacOsX()) { + apply plugin: 'objective-cpp' +} apply from: "${rootDir}/shared/jni/setupBuild.gradle" @@ -87,16 +87,16 @@ ext { splitSetup = { if (it.targetPlatform.operatingSystem.isMacOsX()) { it.sources { - // macObjCpp(ObjectiveCppSourceSet) { - // source { - // srcDirs = ['src/main/native/objcpp'] - // include '**/*.mm' - // } - // exportedHeaders { - // srcDirs 'src/main/native/include' - // include '**/*.h' - // } - // } + macObjCpp(ObjectiveCppSourceSet) { + source { + srcDirs = ['src/main/native/objcpp'] + include '**/*.mm' + } + exportedHeaders { + srcDirs 'src/main/native/include', 'src/main/native/cpp' + include '**/*.h' + } + } cscoreMacCpp(CppSourceSet) { source { srcDirs 'src/main/native/osx' @@ -157,6 +157,12 @@ Action> symbolFilter = { symbols -> symbols.removeIf({ !it.startsWith('CS_') }) } as Action>; +run { + if (OperatingSystem.current().isMacOsX()) { + jvmArgs("-XstartOnFirstThread"); + } +} + nativeUtils.exportsConfigs { cscore { x64ExcludeSymbols = [ diff --git a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java index fe46baf75c..bb2b69aaa3 100644 --- a/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java +++ b/cscore/src/main/java/edu/wpi/first/cscore/CameraServerJNI.java @@ -390,4 +390,10 @@ public class CameraServerJNI { public static native long allocateRawFrame(); public static native void freeRawFrame(long frame); + + public static native void runOsxRunLoop(); + + public static native int runOsxRunLoopTimeout(double timeoutSeconds); + + public static native void stopOsxMainRunLoop(); } diff --git a/cscore/src/main/native/cpp/SourceImpl.h b/cscore/src/main/native/cpp/SourceImpl.h index dd2e574f19..5df9ad0608 100644 --- a/cscore/src/main/native/cpp/SourceImpl.h +++ b/cscore/src/main/native/cpp/SourceImpl.h @@ -50,6 +50,7 @@ class SourceImpl : public PropertyContainer { void SetConnectionStrategy(CS_ConnectionStrategy strategy) { m_strategy = static_cast(strategy); + NumSinksChanged(); } bool IsEnabled() const { return m_strategy == CS_CONNECTION_KEEP_OPEN || diff --git a/cscore/src/main/native/cpp/UnlimitedHandleResource.h b/cscore/src/main/native/cpp/UnlimitedHandleResource.h index 3406dc93fc..28ad30159f 100644 --- a/cscore/src/main/native/cpp/UnlimitedHandleResource.h +++ b/cscore/src/main/native/cpp/UnlimitedHandleResource.h @@ -154,7 +154,7 @@ template inline std::span UnlimitedHandleResource::GetAll( wpi::SmallVectorImpl& vec) { - ForEach([&](THandle handle, const TStruct& data) { vec.push_back(handle); }); + ForEach([&](THandle handle, const TStruct&) { vec.push_back(handle); }); return vec; } diff --git a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp index 4be7e57f47..bd259fa28a 100644 --- a/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp +++ b/cscore/src/main/native/cpp/jni/CameraServerJNI.cpp @@ -13,6 +13,7 @@ #include "cscore_cpp.h" #include "cscore_cv.h" #include "cscore_raw.h" +#include "cscore_runloop.h" #include "edu_wpi_first_cscore_CameraServerJNI.h" namespace cv { @@ -2226,4 +2227,40 @@ Java_edu_wpi_first_cscore_CameraServerJNI_freeRawFrame delete ptr; } +/* + * Class: edu_wpi_first_cscore_CameraServerJNI + * Method: runOsxRunLoop + * Signature: ()V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_cscore_CameraServerJNI_runOsxRunLoop + (JNIEnv*, jclass) +{ + cs::RunOsxRunLoop(); +} + +/* + * Class: edu_wpi_first_cscore_CameraServerJNI + * Method: runOsxRunLoopTimeout + * Signature: (D)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_cscore_CameraServerJNI_runOsxRunLoopTimeout + (JNIEnv*, jclass, jdouble timeoutSeconds) +{ + return cs::RunOsxRunLoopTimeout(timeoutSeconds); +} + +/* + * Class: edu_wpi_first_cscore_CameraServerJNI + * Method: stopOsxMainRunLoop + * Signature: ()V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_cscore_CameraServerJNI_stopOsxMainRunLoop + (JNIEnv*, jclass) +{ + return cs::StopOsxMainRunLoop(); +} + } // extern "C" diff --git a/cscore/src/main/native/include/cscore_cpp.h b/cscore/src/main/native/include/cscore_cpp.h index 47bb4db840..0031e5ed5a 100644 --- a/cscore/src/main/native/include/cscore_cpp.h +++ b/cscore/src/main/native/include/cscore_cpp.h @@ -90,6 +90,11 @@ struct VideoMode : public CS_VideoMode { return pixelFormat == other.pixelFormat && width == other.width && height == other.height && fps == other.fps; } + + bool CompareWithoutFps(const VideoMode& other) const { + return pixelFormat == other.pixelFormat && width == other.width && + height == other.height; + } }; /** diff --git a/cscore/src/main/native/include/cscore_runloop.h b/cscore/src/main/native/include/cscore_runloop.h new file mode 100644 index 0000000000..4e1a7ea887 --- /dev/null +++ b/cscore/src/main/native/include/cscore_runloop.h @@ -0,0 +1,11 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +namespace cs { +void RunOsxRunLoop(); +int RunOsxRunLoopTimeout(double timeoutSeconds); +void StopOsxMainRunLoop(); +} // namespace cs diff --git a/cscore/src/main/native/linux/RunLoopHelpers.cpp b/cscore/src/main/native/linux/RunLoopHelpers.cpp new file mode 100644 index 0000000000..bb38da4ca9 --- /dev/null +++ b/cscore/src/main/native/linux/RunLoopHelpers.cpp @@ -0,0 +1,13 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "cscore_runloop.h" + +namespace cs { +void RunOsxRunLoop() {} +int RunOsxRunLoopTimeout(double timeoutSeconds) { + return 0; +} +void StopOsxMainRunLoop() {} +} // namespace cs diff --git a/cscore/src/main/native/objcpp/RunLoopHelpers.mm b/cscore/src/main/native/objcpp/RunLoopHelpers.mm new file mode 100644 index 0000000000..a696044e5a --- /dev/null +++ b/cscore/src/main/native/objcpp/RunLoopHelpers.mm @@ -0,0 +1,21 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "cscore_runloop.h" + +#include + +namespace cs { +void RunOsxRunLoop() { + CFRunLoopRun(); +} + +int RunOsxRunLoopTimeout(double timeoutSeconds) { + return CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeoutSeconds, false); +} + +void StopOsxMainRunLoop() { + CFRunLoopStop(CFRunLoopGetMain()); +} +} diff --git a/cscore/src/main/native/objcpp/UsbCameraDelegate.h b/cscore/src/main/native/objcpp/UsbCameraDelegate.h new file mode 100644 index 0000000000..61a0473767 --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraDelegate.h @@ -0,0 +1,22 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#import +#include + +namespace cs { +class UsbCameraImpl; +} + +@interface UsbCameraDelegate + : NSObject + +@property(nonatomic) std::weak_ptr cppImpl; + +- (void)captureOutput:(AVCaptureOutput*)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection*)connection; +@end diff --git a/cscore/src/main/native/objcpp/UsbCameraDelegate.mm b/cscore/src/main/native/objcpp/UsbCameraDelegate.mm new file mode 100644 index 0000000000..efa0fbf602 --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraDelegate.mm @@ -0,0 +1,67 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#import "UsbCameraDelegate.h" +#include "UsbCameraImpl.h" + +#include + +#include +#include + +@implementation UsbCameraDelegate + +- (id)init { + self = [super init]; + return self; +} + +- (void)captureOutput:(AVCaptureOutput*)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection*)connection { + (void)captureOutput; + (void)sampleBuffer; + (void)connection; + + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return; + } + + // Buffer always comes in a 32BGRA + auto imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + void* baseaddress = CVPixelBufferGetBaseAddress(imageBuffer); + + size_t width = CVPixelBufferGetWidth(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + size_t rowBytes = CVPixelBufferGetBytesPerRow(imageBuffer); + OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer); + + if (rowBytes == 0) { + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + return; + } + + if (pixelFormat != kCVPixelFormatType_32BGRA) { + NSLog(@"Unknown Pixel Format %u", pixelFormat); + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + return; + } + + size_t currSize = width * 3 * height; + + auto tmpMat = cv::Mat(height, width, CV_8UC4, baseaddress, rowBytes); + auto image = sharedThis->AllocImage(cs::VideoMode::PixelFormat::kBGR, width, + height, currSize); + cv::cvtColor(tmpMat, image->AsMat(), cv::COLOR_BGRA2BGR); + + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + + sharedThis->objcPutFrame(std::move(image), wpi::Now()); +} + +@end diff --git a/cscore/src/main/native/objcpp/UsbCameraImpl.h b/cscore/src/main/native/objcpp/UsbCameraImpl.h new file mode 100644 index 0000000000..0fd09fb7e1 --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraImpl.h @@ -0,0 +1,96 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#import +#import "UsbCameraDelegate.h" +#import "UsbCameraImplObjc.h" + +#include +#include +#include + +#include "SourceImpl.h" + +namespace cs { +struct CameraFPSRange { + int min; + int max; + + bool IsWithinRange(int fps) { return fps >= min && fps <= max; } +}; + +struct CameraModeStore { + VideoMode mode; + AVCaptureDeviceFormat* format; + std::vector fpsRanges; +}; + +class UsbCameraImpl : public SourceImpl { + public: + UsbCameraImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, std::string_view path); + UsbCameraImpl(std::string_view name, wpi::Logger& logger, Notifier& notifier, + Telemetry& telemetry, int deviceId); + ~UsbCameraImpl() override; + + void Start() override; + + // Property functions + void SetProperty(int property, int value, CS_Status* status) override; + void SetStringProperty(int property, std::string_view value, + CS_Status* status) override; + + // Standard common camera properties + void SetBrightness(int brightness, CS_Status* status) override; + int GetBrightness(CS_Status* status) const override; + void SetWhiteBalanceAuto(CS_Status* status) override; + void SetWhiteBalanceHoldCurrent(CS_Status* status) override; + void SetWhiteBalanceManual(int value, CS_Status* status) override; + void SetExposureAuto(CS_Status* status) override; + void SetExposureHoldCurrent(CS_Status* status) override; + void SetExposureManual(int value, CS_Status* status) override; + + bool SetVideoMode(const VideoMode& mode, CS_Status* status) override; + bool SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) override; + bool SetResolution(int width, int height, CS_Status* status) override; + bool SetFPS(int fps, CS_Status* status) override; + + void NumSinksChanged() override; + void NumSinksEnabledChanged() override; + + cs::Notifier& objcGetNotifier() { return m_notifier; } + + void objcSwapVideoModes(std::vector& modes) { + std::scoped_lock lock(m_mutex); + m_videoModes.swap(modes); + } + + void objcSetVideoMode(const VideoMode& mode) { + std::scoped_lock lock(m_mutex); + m_mode = mode; + } + + void objcPutFrame(std::unique_ptr image, Frame::Time time) { + PutFrame(std::move(image), time); + } + + const VideoMode& objcGetVideoMode() const { return m_mode; } + + std::vector& objcGetPlatformVideoModes() { + return m_platformModes; + } + + wpi::Logger& objcGetLogger() { return m_logger; } + + UsbCameraImplObjc* cppGetObjc() { return m_objc; } + + private: + UsbCameraImplObjc* m_objc; + std::vector m_platformModes; + VideoMode m_mode; +}; +} // namespace cs diff --git a/cscore/src/main/native/objcpp/UsbCameraImpl.mm b/cscore/src/main/native/objcpp/UsbCameraImpl.mm new file mode 100644 index 0000000000..5607fbf30f --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraImpl.mm @@ -0,0 +1,203 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#import +#import + +#include + +#include +#include +#include + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "Handle.h" +#include "Log.h" +#include "Notifier.h" +#include "Instance.h" +#include "c_util.h" +#include "cscore_cpp.h" +#include "opencv2/imgproc.hpp" +#include "UsbCameraImpl.h" + +namespace cs { + +UsbCameraImpl::UsbCameraImpl(std::string_view name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + std::string_view path) + : SourceImpl{name, logger, notifier, telemetry} { + UsbCameraImplObjc* objc = [[UsbCameraImplObjc alloc] init]; + objc.path = [[NSString alloc] initWithBytes:path.data() + length:path.size() + encoding:NSUTF8StringEncoding]; + m_objc = objc; +} +UsbCameraImpl::UsbCameraImpl(std::string_view name, wpi::Logger& logger, + Notifier& notifier, Telemetry& telemetry, + int deviceId) + : SourceImpl{name, logger, notifier, telemetry} { + UsbCameraImplObjc* objc = [[UsbCameraImplObjc alloc] init]; + objc.path = nil; + objc.deviceId = deviceId; + m_objc = objc; +} + +UsbCameraImpl::~UsbCameraImpl() { + m_objc = nil; +} + +void UsbCameraImpl::Start() { + [m_objc start]; +} + +// Property functions +void UsbCameraImpl::SetProperty(int property, int value, CS_Status* status) { + [m_objc setProperty:property withValue:value status:status]; +} +void UsbCameraImpl::SetStringProperty(int property, std::string_view value, + CS_Status* status) { + [m_objc setStringProperty:property withValue:&value status:status]; +} + +// Standard common camera properties +void UsbCameraImpl::SetBrightness(int brightness, CS_Status* status) { + [m_objc setBrightness:brightness status:status]; +} +int UsbCameraImpl::GetBrightness(CS_Status* status) const { + return [m_objc getBrightness:status]; +} +void UsbCameraImpl::SetWhiteBalanceAuto(CS_Status* status) { + [m_objc setWhiteBalanceAuto:status]; +} +void UsbCameraImpl::SetWhiteBalanceHoldCurrent(CS_Status* status) { + [m_objc setWhiteBalanceHoldCurrent:status]; +} +void UsbCameraImpl::SetWhiteBalanceManual(int value, CS_Status* status) { + [m_objc setWhiteBalanceManual:value status:status]; +} +void UsbCameraImpl::SetExposureAuto(CS_Status* status) { + [m_objc setExposureAuto:status]; +} +void UsbCameraImpl::SetExposureHoldCurrent(CS_Status* status) { + [m_objc setExposureHoldCurrent:status]; +} +void UsbCameraImpl::SetExposureManual(int value, CS_Status* status) { + [m_objc setExposureManual:value status:status]; +} + +bool UsbCameraImpl::SetVideoMode(const VideoMode& mode, CS_Status* status) { + return [m_objc setVideoMode:mode status:status]; +} +bool UsbCameraImpl::SetPixelFormat(VideoMode::PixelFormat pixelFormat, + CS_Status* status) { + return [m_objc setPixelFormat:pixelFormat status:status]; +} +bool UsbCameraImpl::SetResolution(int width, int height, CS_Status* status) { + return [m_objc setResolutionWidth:width withHeight:height status:status]; +} +bool UsbCameraImpl::SetFPS(int fps, CS_Status* status) { + return [m_objc setFPS:fps status:status]; +} + +void UsbCameraImpl::NumSinksChanged() { + [m_objc numSinksChanged]; +} +void UsbCameraImpl::NumSinksEnabledChanged() { + [m_objc numSinksEnabledChanged]; +} + +CS_Source CreateUsbCameraDev(std::string_view name, int dev, + CS_Status* status) { + std::vector devices = cs::EnumerateUsbCameras(status); + if (static_cast(devices.size()) > dev) { + return CreateUsbCameraPath(name, devices[dev].path, status); + } + auto& inst = Instance::GetInstance(); + return inst.CreateSource(CS_SOURCE_USB, std::make_shared( + name, inst.logger, inst.notifier, + inst.telemetry, dev)); +} + +CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path, + CS_Status* status) { + (void)status; + auto& inst = Instance::GetInstance(); + auto val = std::make_shared(name, inst.logger, inst.notifier, + inst.telemetry, path); + val->cppGetObjc().cppImpl = val; + return inst.CreateSource(CS_SOURCE_USB, val); +} + +std::vector EnumerateUsbCameras(CS_Status* status) { + @autoreleasepool { + (void)status; + std::vector retval; + NSArray* deviceTypes = @[ + AVCaptureDeviceTypeBuiltInWideAngleCamera, + AVCaptureDeviceTypeExternalUnknown + ]; + AVCaptureDeviceDiscoverySession* session = [AVCaptureDeviceDiscoverySession + discoverySessionWithDeviceTypes:deviceTypes + mediaType:AVMediaTypeVideo + position:AVCaptureDevicePositionUnspecified]; + + NSArray* captureDevices = [session devices]; + + int count = 0; + for (id device in captureDevices) { + NSString* name = [device localizedName]; + NSString* uniqueIdentifier = [(AVCaptureDevice*)device uniqueID]; + retval.push_back( + {count, [uniqueIdentifier UTF8String], [name UTF8String], {}}); + + count++; + } + + return retval; + } +} + +void SetUsbCameraPath(CS_Source source, std::string_view path, + CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || data->kind != CS_SOURCE_USB) { + *status = CS_INVALID_HANDLE; + return; + } + [static_cast(*data->source).cppGetObjc() + setNewCameraPath:&path]; +} + +std::string GetUsbCameraPath(CS_Source source, CS_Status* status) { + auto data = Instance::GetInstance().GetSource(source); + if (!data || data->kind != CS_SOURCE_USB) { + *status = CS_INVALID_HANDLE; + return std::string{}; + } + std::string ret; + [static_cast(*data->source).cppGetObjc() + getCurrentCameraPath:&ret]; + return ret; +} + +UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) { + UsbCameraInfo info; + auto data = Instance::GetInstance().GetSource(source); + if (!data || data->kind != CS_SOURCE_USB) { + *status = CS_INVALID_HANDLE; + return info; + } + + [static_cast(*data->source).cppGetObjc() + getCurrentCameraPath:&info.path]; + [static_cast(*data->source).cppGetObjc() + getCameraName:&info.name]; + info.productId = 0; + info.vendorId = 0; + // ParseVidAndPid(info.path, &info.productId, &info.vendorId); + info.dev = -1; // We have lost dev information by this point in time. + return info; +} + +} // namespace cs diff --git a/cscore/src/main/native/objcpp/UsbCameraImplObjc.h b/cscore/src/main/native/objcpp/UsbCameraImplObjc.h new file mode 100644 index 0000000000..c173e80011 --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraImplObjc.h @@ -0,0 +1,71 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#import +#import "UsbCameraDelegate.h" +#include +#include +#include "cscore_cpp.h" + +namespace cs { +class UsbCameraImpl; +} + +@interface UsbCameraImplObjc : NSObject + +@property(nonatomic) AVCaptureDeviceFormat* currentFormat; +@property(nonatomic) int currentFPS; +@property(nonatomic) std::weak_ptr cppImpl; +@property(nonatomic) dispatch_queue_t sessionQueue; +@property(nonatomic) NSString* path; +@property(nonatomic) int deviceId; +@property(nonatomic) bool propertiesCached; +@property(nonatomic) bool streaming; +@property(nonatomic) bool deviceValid; +@property(nonatomic) bool isAuthorized; + +@property(nonatomic) AVCaptureDevice* videoDevice; +@property(nonatomic) AVCaptureDeviceInput* videoInput; +@property(nonatomic) UsbCameraDelegate* callback; +@property(nonatomic) AVCaptureVideoDataOutput* videoOutput; +@property(nonatomic) AVCaptureSession* session; + +- (void)start; + +// Property functions +- (void)setProperty:(int)property + withValue:(int)value + status:(CS_Status*)status; +- (void)setStringProperty:(int)property + withValue:(std::string_view*)value + status:(CS_Status*)status; + +// Standard common camera properties +- (void)setBrightness:(int)brightness status:(CS_Status*)status; +- (int)getBrightness:(CS_Status*)status; +- (void)setWhiteBalanceAuto:(CS_Status*)status; +- (void)setWhiteBalanceHoldCurrent:(CS_Status*)status; +- (void)setWhiteBalanceManual:(int)value status:(CS_Status*)status; +- (void)setExposureAuto:(CS_Status*)status; +- (void)setExposureHoldCurrent:(CS_Status*)status; +- (void)setExposureManual:(int)value status:(CS_Status*)status; + +- (bool)setVideoMode:(const cs::VideoMode&)mode status:(CS_Status*)status; +- (bool)setPixelFormat:(cs::VideoMode::PixelFormat)pixelFormat + status:(CS_Status*)status; +- (bool)setResolutionWidth:(int)width + withHeight:(int)height + status:(CS_Status*)status; +- (bool)setFPS:(int)fps status:(CS_Status*)status; + +- (void)numSinksChanged; +- (void)numSinksEnabledChanged; + +- (void)getCurrentCameraPath:(std::string*)path; +- (void)getCameraName:(std::string*)name; +- (void)setNewCameraPath:(std::string_view*)path; + +@end diff --git a/cscore/src/main/native/objcpp/UsbCameraImplObjc.mm b/cscore/src/main/native/objcpp/UsbCameraImplObjc.mm new file mode 100644 index 0000000000..febe6a55f0 --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraImplObjc.mm @@ -0,0 +1,669 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#import "UsbCameraImplObjc.h" +#include "UsbCameraImpl.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "Notifier.h" +#include "Log.h" + +template +inline void NamedLog(UsbCameraImplObjc* objc, unsigned int level, + const char* file, unsigned int line, const S& format, + Args&&... args) { + auto sharedThis = objc.cppImpl.lock(); + if (!sharedThis) { + return; + } + + wpi::Logger& logger = sharedThis->objcGetLogger(); + std::string_view name = sharedThis->GetName(); + + if (logger.HasLogger() && level >= logger.min_level()) { + cs::NamedLogV(logger, level, file, line, name, format, + fmt::make_format_args(args...)); + } +} + +#define OBJCLOG(level, format, ...) \ + NamedLog(self, level, __FILE__, __LINE__, \ + FMT_STRING(format) __VA_OPT__(, ) __VA_ARGS__) + +#define OBJCERROR(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_ERROR, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCWARNING(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_WARNING, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCINFO(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_INFO, format __VA_OPT__(, ) __VA_ARGS__) + +#ifdef NDEBUG +#define OBJCDEBUG(format, ...) \ + do { \ + } while (0) +#define OBJCDEBUG1(format, ...) \ + do { \ + } while (0) +#define OBJCDEBUG2(format, ...) \ + do { \ + } while (0) +#define OBJCDEBUG3(format, ...) \ + do { \ + } while (0) +#define OBJCDEBUG4(format, ...) \ + do { \ + } while (0) +#else +#define OBJCDEBUG(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_DEBUG, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCDEBUG1(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_DEBUG1, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCDEBUG2(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_DEBUG2, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCDEBUG3(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_DEBUG3, format __VA_OPT__(, ) __VA_ARGS__) +#define OBJCDEBUG4(format, ...) \ + OBJCLOG(::wpi::WPI_LOG_DEBUG4, format __VA_OPT__(, ) __VA_ARGS__) +#endif + +using namespace cs; + +@implementation UsbCameraImplObjc + +- (void)start { + switch ([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) { + case AVAuthorizationStatusAuthorized: + self.isAuthorized = true; + break; + default: + OBJCERROR( + "Camera access explicitly blocked for application. No cameras are " + "accessable"); + self.isAuthorized = false; + // TODO log + break; + case AVAuthorizationStatusNotDetermined: + dispatch_suspend(self.sessionQueue); + [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo + completionHandler:^(BOOL granted) { + self.isAuthorized = granted; + dispatch_resume(self.sessionQueue); + }]; + break; + } + dispatch_async(self.sessionQueue, ^{ + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(cameraConnected:) + name:AVCaptureDeviceWasConnectedNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(cameraDisconnected:) + name:AVCaptureDeviceWasDisconnectedNotification + object:nil]; + [self deviceConnect]; + }); +} + +// Property functions +- (void)setProperty:(int)property + withValue:(int)value + status:(CS_Status*)status { +} +- (void)setStringProperty:(int)property + withValue:(std::string_view*)value + status:(CS_Status*)status { +} + +// Standard common camera properties +- (void)setBrightness:(int)brightness status:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (int)getBrightness:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; + return 0; +} +- (void)setWhiteBalanceAuto:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (void)setWhiteBalanceHoldCurrent:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (void)setWhiteBalanceManual:(int)value status:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (void)setExposureAuto:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (void)setExposureHoldCurrent:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} +- (void)setExposureManual:(int)value status:(CS_Status*)status { + *status = CS_INVALID_PROPERTY; +} + +- (bool)setVideoMode:(const cs::VideoMode&)mode status:(CS_Status*)status { + dispatch_async_and_wait(self.sessionQueue, ^{ + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + *status = CS_READ_FAILED; + return; + } + + [self internalSetMode:mode status:status]; + }); + return true; +} +- (bool)setPixelFormat:(cs::VideoMode::PixelFormat)pixelFormat + status:(CS_Status*)status { + dispatch_async_and_wait(self.sessionQueue, ^{ + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + *status = CS_READ_FAILED; + return; + } + VideoMode newMode; + newMode = sharedThis->objcGetVideoMode(); + newMode.pixelFormat = pixelFormat; + + [self internalSetMode:newMode status:status]; + }); + return true; +} +- (bool)setResolutionWidth:(int)width + withHeight:(int)height + status:(CS_Status*)status { + dispatch_async_and_wait(self.sessionQueue, ^{ + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + *status = CS_READ_FAILED; + return; + } + VideoMode newMode; + newMode = sharedThis->objcGetVideoMode(); + newMode.width = width; + newMode.height = height; + + [self internalSetMode:newMode status:status]; + }); + return true; +} + +- (void)internalSetMode:(const cs::VideoMode&)newMode + status:(CS_Status*)status { + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + *status = CS_READ_FAILED; + return; + } + // If device is not connected, just apply and leave. + if (!self.propertiesCached) { + sharedThis->objcSetVideoMode(newMode); + *status = CS_OK; + return; + } + + if (newMode != sharedThis->objcGetVideoMode()) { + OBJCDEBUG3("Trying Mode {} {} {} {}", newMode.pixelFormat, newMode.width, + newMode.height, newMode.fps); + int localFPS = 0; + AVCaptureDeviceFormat* newModeType = [self deviceCheckModeValid:&newMode + withFps:&localFPS]; + if (newModeType == nil) { + *status = CS_UNSUPPORTED_MODE; + return; + } + + self.currentFormat = newModeType; + self.currentFPS = localFPS; + sharedThis->objcSetVideoMode(newMode); + [self deviceDisconnect]; + [self deviceConnect]; + sharedThis->objcGetNotifier().NotifySourceVideoMode(*sharedThis, newMode); + } + *status = CS_OK; +} + +- (bool)setFPS:(int)fps status:(CS_Status*)status { + dispatch_async_and_wait(self.sessionQueue, ^{ + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + *status = CS_READ_FAILED; + return; + } + VideoMode newMode; + newMode = sharedThis->objcGetVideoMode(); + newMode.fps = fps; + + [self internalSetMode:newMode status:status]; + }); + return true; +} + +- (void)numSinksChanged { + dispatch_async(self.sessionQueue, ^{ + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return; + } + if (!sharedThis->IsEnabled()) { + [self deviceStreamOff]; + } else if (!self.streaming && sharedThis->IsEnabled()) { + [self deviceStreamOn]; + } + }); +} +- (void)numSinksEnabledChanged { + [self numSinksChanged]; +} + +// All above is direct forwarders from C++, must always dispatch to loop + +- (void)getCurrentCameraPath:(std::string*)path { + dispatch_async_and_wait(self.sessionQueue, ^{ + if (self.videoDevice == nil) { + return; + } + *path = [self.videoDevice.uniqueID UTF8String]; + }); +} + +- (void)getCameraName:(std::string*)name { + dispatch_async_and_wait(self.sessionQueue, ^{ + if (self.videoDevice == nil) { + return; + } + *name = [self.videoDevice.localizedName UTF8String]; + }); +} + +- (void)setNewCameraPath:(std::string_view*)path { + dispatch_async_and_wait(self.sessionQueue, ^{ + NSString* nsPath = [[NSString alloc] initWithBytes:path->data() + length:path->size() + encoding:NSUTF8StringEncoding]; + if (self.path != nil && [self.path isEqualToString:nsPath]) { + return; + } + self.path = nsPath; + [self deviceDisconnect]; + [self deviceConnect]; + }); +} + +// All above are called from C++, must always dispatch to loop + +- (void)deviceCacheProperties { + if (self.session == nil) { + return; + } +} + +static cs::VideoMode::PixelFormat FourCCToPixelFormat(FourCharCode fourcc) { + switch (fourcc) { + case kCVPixelFormatType_422YpCbCr8_yuvs: + case kCVPixelFormatType_422YpCbCr8FullRange: + return cs::VideoMode::PixelFormat::kYUYV; + default: + return cs::VideoMode::PixelFormat::kBGR; + } +} + +- (void)deviceCacheVideoModes { + if (self.session == nil) { + return; + } + + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return; + } + std::vector& platformModes = + sharedThis->objcGetPlatformVideoModes(); + platformModes.clear(); + + std::vector modes; + @autoreleasepool { + NSArray* formats = self.videoDevice.formats; + + int count = 0; + + for (AVCaptureDeviceFormat* format in formats) { + CMFormatDescriptionRef cmformat = format.formatDescription; + CMVideoDimensions s1 = CMVideoFormatDescriptionGetDimensions(cmformat); + + FourCharCode fourcc = CMFormatDescriptionGetMediaSubType(cmformat); + auto videoFormat = FourCCToPixelFormat(fourcc); + + NSArray* frameRates = + format.videoSupportedFrameRateRanges; + + CameraModeStore store; + store.mode.pixelFormat = videoFormat; + store.mode.width = static_cast(s1.width); + store.mode.height = static_cast(s1.height); + store.format = format; + int maxFps = 0; + + for (AVFrameRateRange* rate in frameRates) { + CMTime highest = rate.minFrameDuration; + CMTime lowest = rate.maxFrameDuration; + + int highestFps = highest.timescale / static_cast(highest.value); + int lowestFps = lowest.timescale / static_cast(lowest.value); + + store.fpsRanges.emplace_back(CameraFPSRange{lowestFps, highestFps}); + if (highestFps > maxFps) { + maxFps = highestFps; + } + } + store.mode.fps = maxFps; + + modes.emplace_back(store.mode); + platformModes.emplace_back(store); + count++; + } + } + + sharedThis->objcSwapVideoModes(modes); + sharedThis->objcGetNotifier().NotifySource(*sharedThis, + CS_SOURCE_VIDEOMODES_UPDATED); +} + +- (AVCaptureDeviceFormat*)deviceCheckModeValid:(const cs::VideoMode*)toCheck + withFps:(int*)fps { + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return nil; + } + + OBJCDEBUG3("Checking mode {} {} {} {}", toCheck->pixelFormat, toCheck->width, + toCheck->height, toCheck->fps); + std::vector& platformModes = + sharedThis->objcGetPlatformVideoModes(); + // Find the matching mode + auto match = std::find_if(platformModes.begin(), platformModes.end(), + [&](CameraModeStore& input) { + return input.mode.CompareWithoutFps(*toCheck); + }); + + if (match == platformModes.end()) { + return nil; + } + + // Check FPS + for (CameraFPSRange& range : match->fpsRanges) { + OBJCDEBUG3("Checking Range {} {}", range.min, range.max); + if (range.IsWithinRange(toCheck->fps)) { + *fps = toCheck->fps; + return match->format; + } + } + + return nil; +} + +- (void)deviceCacheMode { + if (!self.session) { + return; + } + + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return; + } + + std::vector& platformModes = + sharedThis->objcGetPlatformVideoModes(); + + if (platformModes.size() == 0) { + return; + } + + if (self.currentFormat == nil) { + int localFps = 0; + self.currentFormat = + [self deviceCheckModeValid:&sharedThis->objcGetVideoMode() + withFps:&localFps]; + if (self.currentFormat == nil) { + self.currentFormat = self.videoDevice.activeFormat; + auto result = std::find_if(platformModes.begin(), platformModes.end(), + [f = self.currentFormat](CameraModeStore& i) { + return [f isEqual:i.format]; + }); + if (result == platformModes.end()) { + auto& firstSupported = platformModes[0]; + self.currentFormat = firstSupported.format; + self.currentFPS = firstSupported.mode.fps; + sharedThis->objcSetVideoMode(firstSupported.mode); + } else { + self.currentFPS = result->mode.fps; + sharedThis->objcSetVideoMode(result->mode); + } + } else { + self.currentFPS = localFps; + } + } + + [self deviceSetMode]; + + sharedThis->objcGetNotifier().NotifySourceVideoMode( + *sharedThis, sharedThis->objcGetVideoMode()); +} + +- (void)deviceSetMode { + self.deviceValid = true; +} + +- (bool)deviceStreamOn { + if (self.streaming) { + return false; + } + if (!self.deviceValid) { + return false; + } + self.streaming = true; + + [self.session startRunning]; + + if ([self.videoDevice lockForConfiguration:nil]) { + if (self.currentFormat != nil) { + self.videoDevice.activeFormat = self.currentFormat; + } + if (self.currentFPS != 0) { + self.videoDevice.activeVideoMinFrameDuration = + CMTimeMake(1, self.currentFPS); + self.videoDevice.activeVideoMaxFrameDuration = + CMTimeMake(1, self.currentFPS); + } + [self.videoDevice unlockForConfiguration]; + } else { + OBJCERROR("Failed to lock for configuration"); + } + + return true; +} + +- (bool)deviceStreamOff { + if (self.streaming) { + [self.session stopRunning]; + } + self.streaming = false; + return true; +} + +- (id)init { + self = [super init]; + // TODO pass in name, make this queue specific + self.sessionQueue = + dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL); + return self; +} + +- (void)deviceDisconnect { + std::string pathStr = [self.path UTF8String]; + OBJCINFO("Disconnected from {}", pathStr); + + [self deviceStreamOff]; + self.session = nil; + self.videoOutput = nil; + self.callback = nil; + self.videoInput = nil; + self.videoDevice = nil; + self.streaming = false; + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return; + } + sharedThis->SetConnected(false); +} + +- (bool)deviceConnect { + if (!self.isAuthorized) { + OBJCERROR( + "Camera access not authorized for application. No cameras are " + "accessable"); + return false; + } + + OSType pixelFormat = kCVPixelFormatType_32BGRA; + + NSDictionary* pixelBufferOptions = + @{(id)kCVPixelBufferPixelFormatTypeKey : @(pixelFormat)}; + + if (self.session != nil) { + return true; + } + + auto sharedThis = self.cppImpl.lock(); + if (!sharedThis) { + return false; + } + + if (self.path == nil) { + OBJCINFO("Starting for device id {}", self.deviceId); + // Enumerate Devices + CS_Status status = 0; + auto cameras = cs::EnumerateUsbCameras(&status); + if (static_cast(cameras.size()) <= self.deviceId) { + return false; + } + std::string& path = cameras[self.deviceId].path; + self.path = [[NSString alloc] initWithBytes:path.data() + length:path.size() + encoding:NSUTF8StringEncoding]; + } + + std::string pathStr = [self.path UTF8String]; + OBJCINFO("Connecting to USB camera on {}", pathStr); + + self.videoDevice = [AVCaptureDevice deviceWithUniqueID:self.path]; + if (self.videoDevice == nil) { + OBJCWARNING("Device Not found"); + goto err; + } + + self.videoInput = [AVCaptureDeviceInput deviceInputWithDevice:self.videoDevice + error:nil]; + if (self.videoInput == nil) { + OBJCWARNING("Creating AVCaptureDeviceInput failed"); + goto err; + } + + self.callback = [[UsbCameraDelegate alloc] init]; + if (self.callback == nil) { + OBJCWARNING("Creating Camera Callback failed"); + goto err; + } + self.callback.cppImpl = self.cppImpl; + + self.videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + if (self.videoOutput == nil) { + OBJCWARNING("Creating AVCaptureVideoDataOutput failed"); + goto err; + } + + [self.videoOutput setSampleBufferDelegate:self.callback + queue:self.sessionQueue]; + + self.videoOutput.videoSettings = pixelBufferOptions; + self.videoOutput.alwaysDiscardsLateVideoFrames = YES; + + self.session = [[AVCaptureSession alloc] init]; + if (self.session == nil) { + OBJCWARNING("Creating AVCaptureSession failed"); + goto err; + } + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(sessionRuntimeError:) + name:AVCaptureSessionRuntimeErrorNotification + object:self.session]; + + [self.session addInput:self.videoInput]; + [self.session addOutput:self.videoOutput]; + + sharedThis->SetDescription([self.videoDevice.localizedName UTF8String]); + + if (!self.propertiesCached) { + OBJCDEBUG3("Caching properties"); + [self deviceCacheProperties]; + [self deviceCacheVideoModes]; + [self deviceCacheMode]; + self.propertiesCached = true; + } else { + OBJCDEBUG3("Restoring Video Mode"); + [self deviceSetMode]; + } + + sharedThis->SetConnected(true); + + if (sharedThis->IsEnabled()) { + [self deviceStreamOn]; + } + + return true; + +err: + self.session = nil; + self.videoOutput = nil; + self.callback = nil; + self.videoInput = nil; + self.videoDevice = nil; + return false; +} + +// Helpers +- (void)sessionRuntimeError:(NSNotification*)notification { + @autoreleasepool { + NSError* error = notification.userInfo[AVCaptureSessionErrorKey]; + const char* str = [error.description UTF8String]; + if (str) { + std::string errorStr = str; + OBJCERROR("Capture session runtime error: {}", errorStr); + } + } +} + +- (void)cameraDisconnected:(NSNotification*)notification { + AVCaptureDevice* device = notification.object; + dispatch_async(self.sessionQueue, ^{ + if (self.path != nil && [device.uniqueID isEqualToString:self.path]) { + [self deviceDisconnect]; + } + }); +} + +- (void)cameraConnected:(NSNotification*)notification { + AVCaptureDevice* device = notification.object; + dispatch_async(self.sessionQueue, ^{ + if (self.path == nil || [device.uniqueID isEqualToString:self.path]) { + [self deviceConnect]; + } + }); +} + +@end diff --git a/cscore/src/main/native/objcpp/UsbCameraListener.mm b/cscore/src/main/native/objcpp/UsbCameraListener.mm new file mode 100644 index 0000000000..b6b59d37df --- /dev/null +++ b/cscore/src/main/native/objcpp/UsbCameraListener.mm @@ -0,0 +1,111 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#import +#include "UsbCameraListener.h" + +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "Notifier.h" + +using namespace cs; + +@interface UsbCameraListenerImpl : NSObject +@property(nonatomic) Notifier* notifier; +@property BOOL started; +@property(nonatomic) dispatch_queue_t sessionQueue; + +- (void)start; +- (void)stop; + +@end + +@implementation UsbCameraListenerImpl + +- (id)init { + self = [super init]; + self.sessionQueue = + dispatch_queue_create("Camera Listener", DISPATCH_QUEUE_SERIAL); + return self; +} + +- (void)start { + dispatch_async(self.sessionQueue, ^{ + if (self.started) { + return; + } + self.started = YES; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(camerasChanged:) + name:AVCaptureDeviceWasConnectedNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(camerasChanged:) + name:AVCaptureDeviceWasDisconnectedNotification + object:nil]; + }); +} + +- (void)stop { + dispatch_async_and_wait(self.sessionQueue, ^{ + if (!self.started) { + return; + } + self.started = NO; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:AVCaptureDeviceWasConnectedNotification + object:nil]; + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:AVCaptureDeviceWasDisconnectedNotification + object:nil]; + }); +} + +- (void)camerasChanged:(NSNotification*)notification { + @autoreleasepool { + dispatch_async(self.sessionQueue, ^{ + if (!self.started) { + return; + } + + AVCaptureDevice* device = notification.object; + if ([device.deviceType + isEqualToString:AVCaptureDeviceTypeBuiltInWideAngleCamera] || + [device.deviceType + isEqualToString:AVCaptureDeviceTypeExternalUnknown]) { + self.notifier->NotifyUsbCamerasChanged(); + } + }); + } +} + +@end + +class UsbCameraListener::Impl { + public: + UsbCameraListenerImpl* listener; + + explicit Impl(Notifier& notifier) { + listener = [[UsbCameraListenerImpl alloc] init]; + listener.notifier = ¬ifier; + } +}; + +UsbCameraListener::UsbCameraListener(wpi::Logger&, Notifier& notifier) + : m_impl{std::make_unique(notifier)} {} + +UsbCameraListener::~UsbCameraListener() { + Stop(); +} + +void UsbCameraListener::Start() { + [m_impl->listener start]; +} + +void UsbCameraListener::Stop() { + [m_impl->listener stop]; +} diff --git a/cscore/src/main/native/objcpp/objcpptemp.mm b/cscore/src/main/native/objcpp/objcpptemp.mm deleted file mode 100644 index 27aaaff17f..0000000000 --- a/cscore/src/main/native/objcpp/objcpptemp.mm +++ /dev/null @@ -1,12 +0,0 @@ -#import - -@interface XYZPerson : NSObject -- (void)sayHello; -@end - - -@implementation XYZPerson -- (void)sayHello { - NSLog(@"Hello, World!"); -} -@end diff --git a/cscore/src/main/native/osx/UsbCameraImpl.cpp b/cscore/src/main/native/osx/UsbCameraImpl.cpp deleted file mode 100644 index 48ddbd7456..0000000000 --- a/cscore/src/main/native/osx/UsbCameraImpl.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "Instance.h" -#include "cscore_cpp.h" - -namespace cs { - -CS_Source CreateUsbCameraDev(std::string_view name, int dev, - CS_Status* status) { - *status = CS_INVALID_HANDLE; - WPI_ERROR(Instance::GetInstance().logger, - "USB Camera support not implemented for macOS"); - return 0; -} - -CS_Source CreateUsbCameraPath(std::string_view name, std::string_view path, - CS_Status* status) { - *status = CS_INVALID_HANDLE; - WPI_ERROR(Instance::GetInstance().logger, - "USB Camera support not implemented for macOS"); - return 0; -} - -void SetUsbCameraPath(CS_Source source, std::string_view path, - CS_Status* status) { - *status = CS_INVALID_HANDLE; -} - -std::string GetUsbCameraPath(CS_Source source, CS_Status* status) { - *status = CS_INVALID_HANDLE; - return std::string{}; -} - -UsbCameraInfo GetUsbCameraInfo(CS_Source source, CS_Status* status) { - *status = CS_INVALID_HANDLE; - return UsbCameraInfo{}; -} - -std::vector EnumerateUsbCameras(CS_Status* status) { - *status = CS_INVALID_HANDLE; - WPI_ERROR(Instance::GetInstance().logger, - "USB Camera support not implemented for macOS"); - return std::vector{}; -} - -} // namespace cs diff --git a/cscore/src/main/native/osx/UsbCameraListener.cpp b/cscore/src/main/native/osx/UsbCameraListener.cpp deleted file mode 100644 index b83663d120..0000000000 --- a/cscore/src/main/native/osx/UsbCameraListener.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) FIRST and other WPILib contributors. -// Open Source Software; you can modify and/or share it under the terms of -// the WPILib BSD license file in the root directory of this project. - -#include "UsbCameraListener.h" - -using namespace cs; - -class UsbCameraListener::Impl {}; - -UsbCameraListener::UsbCameraListener(wpi::Logger& logger, Notifier& notifier) {} - -UsbCameraListener::~UsbCameraListener() = default; - -void UsbCameraListener::Start() {} - -void UsbCameraListener::Stop() {} diff --git a/cscore/src/main/native/windows/RunLoopHelpers.cpp b/cscore/src/main/native/windows/RunLoopHelpers.cpp new file mode 100644 index 0000000000..bb38da4ca9 --- /dev/null +++ b/cscore/src/main/native/windows/RunLoopHelpers.cpp @@ -0,0 +1,13 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "cscore_runloop.h" + +namespace cs { +void RunOsxRunLoop() {} +int RunOsxRunLoopTimeout(double timeoutSeconds) { + return 0; +} +void StopOsxMainRunLoop() {} +} // namespace cs diff --git a/shared/config.gradle b/shared/config.gradle index 86f84ec2f9..5237e918ab 100644 --- a/shared/config.gradle +++ b/shared/config.gradle @@ -30,6 +30,8 @@ if (project.name != 'wpilibcExamples') { nativeUtils.setSinglePrintPerPlatform() nativeUtils.enableSourceLink() +nativeUtils.wpi.addMacMinimumVersionArg() + nativeUtils.platformConfigs.each { if (it.name.contains('osx')) { it.linker.args << '-Wl,-rpath,\'@loader_path\''