From e0a9cad867b86b15319ab85ff340cc44042740e2 Mon Sep 17 00:00:00 2001 From: yushulx Date: Tue, 24 Dec 2024 15:58:35 +0800 Subject: [PATCH] Support macOS camera access --- litecam/CMakeLists.txt | 32 +++- litecam/dist/include/Camera.h | 7 +- litecam/include/Camera.h | 7 +- litecam/src/CameraMacOS.mm | 289 ++++++++++++++++++++++++++++++++++ litecam/src/main.cpp | 6 +- 5 files changed, 330 insertions(+), 11 deletions(-) diff --git a/litecam/CMakeLists.txt b/litecam/CMakeLists.txt index a79b4a2..479483a 100644 --- a/litecam/CMakeLists.txt +++ b/litecam/CMakeLists.txt @@ -22,10 +22,15 @@ elseif (UNIX AND NOT APPLE) src/CameraPreviewLinux.cpp ) elseif (APPLE) + # Ensure that Objective-C++ source files are compiled as Objective-C++ set(LIBRARY_SOURCES src/CameraMacOS.mm src/CameraPreviewMacOS.mm ) + set_source_files_properties(src/CameraMacOS.mm src/CameraPreviewMacOS.mm PROPERTIES COMPILE_FLAGS "-x objective-c++") + + # Set main.cpp to be treated as Objective-C++ + set_source_files_properties(src/main.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") endif() # Define source files for the executable @@ -50,9 +55,19 @@ if (UNIX AND NOT APPLE) target_link_libraries(litecam PRIVATE ${X11_LIBRARIES} pthread) endif() elseif (APPLE) - find_library(COCOA_LIBRARY Cocoa) - find_library(AVFOUNDATION_LIBRARY AVFoundation) - target_link_libraries(litecam PRIVATE ${AVFOUNDATION_LIBRARY}) + find_library(COCOA_LIBRARY Cocoa REQUIRED) + find_library(AVFOUNDATION_LIBRARY AVFoundation REQUIRED) + find_library(COREMEDIA_LIBRARY CoreMedia REQUIRED) + find_library(COREVIDEO_LIBRARY CoreVideo REQUIRED) + find_library(OBJC_LIBRARY objc REQUIRED) # Add the Objective-C runtime library + + target_link_libraries(litecam PRIVATE + ${COCOA_LIBRARY} + ${AVFOUNDATION_LIBRARY} + ${COREMEDIA_LIBRARY} + ${COREVIDEO_LIBRARY} + ${OBJC_LIBRARY} # Link the Objective-C runtime + ) elseif (WIN32) target_link_libraries(litecam PRIVATE ole32 uuid mfplat mf mfreadwrite mfuuid) endif() @@ -65,3 +80,14 @@ target_link_libraries(camera_capture PRIVATE litecam) # Include the shared library's headers in the executable target_include_directories(camera_capture PRIVATE ${INCLUDE_DIR}) + +# For macOS, link against the Cocoa framework +if (APPLE) + target_link_libraries(camera_capture PRIVATE + ${COCOA_LIBRARY} + ${AVFOUNDATION_LIBRARY} + ${COREMEDIA_LIBRARY} + ${COREVIDEO_LIBRARY} + ${OBJC_LIBRARY} # Link the Objective-C runtime + ) +endif() diff --git a/litecam/dist/include/Camera.h b/litecam/dist/include/Camera.h index 7d34388..e50e6a7 100644 --- a/litecam/dist/include/Camera.h +++ b/litecam/dist/include/Camera.h @@ -41,9 +41,6 @@ struct Buffer void *start; size_t length; }; - -#elif __APPLE__ -#include #endif /////////////////////////////////////////////////////////////////////////////// @@ -163,6 +160,9 @@ class CAMERA_API Camera #elif __linux__ Camera() : fd(-1), frameWidth(640), frameHeight(480), buffers(nullptr), bufferCount(0) {} ~Camera() { Release(); } +#elif __APPLE__ + Camera() noexcept; // Add noexcept to match the implementation + ~Camera(); #endif bool Open(int cameraIndex); @@ -197,6 +197,7 @@ class CAMERA_API Camera #ifdef __APPLE__ void *captureSession; // AVFoundation session object + void *videoOutput; #endif }; diff --git a/litecam/include/Camera.h b/litecam/include/Camera.h index 7d34388..e50e6a7 100644 --- a/litecam/include/Camera.h +++ b/litecam/include/Camera.h @@ -41,9 +41,6 @@ struct Buffer void *start; size_t length; }; - -#elif __APPLE__ -#include #endif /////////////////////////////////////////////////////////////////////////////// @@ -163,6 +160,9 @@ class CAMERA_API Camera #elif __linux__ Camera() : fd(-1), frameWidth(640), frameHeight(480), buffers(nullptr), bufferCount(0) {} ~Camera() { Release(); } +#elif __APPLE__ + Camera() noexcept; // Add noexcept to match the implementation + ~Camera(); #endif bool Open(int cameraIndex); @@ -197,6 +197,7 @@ class CAMERA_API Camera #ifdef __APPLE__ void *captureSession; // AVFoundation session object + void *videoOutput; #endif }; diff --git a/litecam/src/CameraMacOS.mm b/litecam/src/CameraMacOS.mm index 231d595..24ef263 100644 --- a/litecam/src/CameraMacOS.mm +++ b/litecam/src/CameraMacOS.mm @@ -1 +1,290 @@ #include "Camera.h" +#include +#import + +@interface CaptureDelegate : NSObject +{ + FrameData *frame; + dispatch_semaphore_t semaphore; +} + +- (instancetype)initWithFrame:(FrameData *)frame semaphore:(dispatch_semaphore_t)semaphore; + +@end + +@implementation CaptureDelegate + +- (instancetype)initWithFrame:(FrameData *)frame semaphore:(dispatch_semaphore_t)semaphore { + self = [super init]; + if (self) { + self->frame = frame; + self->semaphore = semaphore; + } + return self; +} + +- (void)captureOutput:(AVCaptureOutput *)output +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + + if (!imageBuffer) { + std::cerr << "Failed to get image buffer." << std::endl; + dispatch_semaphore_signal(semaphore); + return; + } + + CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); + void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); + size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); + size_t height = CVPixelBufferGetHeight(imageBuffer); + size_t width = CVPixelBufferGetWidth(imageBuffer); + + frame->width = width; + frame->height = height; + frame->size = width * height * 3; + frame->rgbData = new unsigned char[frame->size]; + + OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer); + + // CFStringRef pixelFormatString = UTCreateStringForOSType(pixelFormat); + // NSLog(@"Pixel Format: %@", pixelFormatString); + // CFRelease(pixelFormatString); + + if (pixelFormat == kCVPixelFormatType_32BGRA) { + // If already in BGRA format, copy the data + unsigned char *src = (unsigned char *)baseAddress; + unsigned char *dst = frame->rgbData; + + for (size_t y = 0; y < height; ++y) { + for (size_t x = 0; x < width; ++x) { + size_t offset = y * bytesPerRow + x * 4; + dst[0] = src[offset + 2]; // R + dst[1] = src[offset + 1]; // G + dst[2] = src[offset + 0]; // B + dst += 3; + } + } + } else { + std::cerr << "Unsupported pixel format." << std::endl; + } + + CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly); + dispatch_semaphore_signal(semaphore); +} + + +@end + +// Camera class implementation for macOS +Camera::Camera() noexcept: captureSession(nullptr), frameWidth(1920), frameHeight(1080) {} + +Camera::~Camera() +{ + Release(); +} + +bool Camera::Open(int cameraIndex) +{ + @autoreleasepool { + // Get available capture devices + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + if (cameraIndex >= [devices count]) + { + std::cerr << "Camera index out of range." << std::endl; + return false; + } + + AVCaptureDevice *device = devices[cameraIndex]; + + NSError *error = nil; + AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; + if (!input) + { + std::cerr << "Error creating device input: " << [[error localizedDescription] UTF8String] << std::endl; + return false; + } + + AVCaptureSession *cs = [[AVCaptureSession alloc] init]; + captureSession = (void *)cs; + if (![cs canAddInput:input]) + { + std::cerr << "Cannot add device input to session." << std::endl; + return false; + } + [cs addInput:input]; + + // Configure output + AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init]; + output.videoSettings = @{(NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)}; + output.alwaysDiscardsLateVideoFrames = YES; + + videoOutput = (void *)output; + + if (![cs canAddOutput:output]) + { + std::cerr << "Cannot add video output to session." << std::endl; + return false; + } + [cs addOutput:output]; + + [cs startRunning]; + + return true; + } +} + +void Camera::Release() +{ + if (captureSession) + { + AVCaptureSession *session = (__bridge AVCaptureSession *)captureSession; + + if (videoOutput) + { + AVCaptureVideoDataOutput *output = (__bridge AVCaptureVideoDataOutput *)videoOutput; + [session removeOutput:output]; + videoOutput = nil; + } + + [session stopRunning]; + captureSession = nil; + } +} + +FrameData Camera::CaptureFrame() +{ + @autoreleasepool { + FrameData frame = {}; + + if (!captureSession || !videoOutput) { + std::cerr << "Capture session is not initialized." << std::endl; + return frame; + } + + AVCaptureSession *session = (__bridge AVCaptureSession *)captureSession; + AVCaptureVideoDataOutput *vo = (__bridge AVCaptureVideoDataOutput *)videoOutput; + + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + + [vo setSampleBufferDelegate:[[CaptureDelegate alloc] initWithFrame:&frame semaphore:semaphore] + queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)]; + + // Wait for the delegate to process the frame + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + frameWidth = frame.width; + frameHeight = frame.height; + return frame; + } +} + +bool Camera::SetResolution(int width, int height) +{ + frameWidth = width; + frameHeight = height; + + if (captureSession) + { + AVCaptureSession *session = (__bridge AVCaptureSession *)captureSession; + [session beginConfiguration]; + + // Iterate over formats and find the one with the desired resolution + for (AVCaptureDeviceFormat *format in [[session.inputs.firstObject device] formats]) + { + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + if (dimensions.width == width && dimensions.height == height) + { + NSError *error = nil; + AVCaptureDevice *device = [session.inputs.firstObject device]; + + // Lock for configuration + if ([device lockForConfiguration:&error]) + { + // Set the active format + [device setActiveFormat:format]; + + // Verify the resolution was set by checking the active format + CMVideoDimensions currentDimensions = CMVideoFormatDescriptionGetDimensions(device.activeFormat.formatDescription); + if (currentDimensions.width == width && currentDimensions.height == height) + { + std::cout << "Resolution set successfully to " << width << "x" << height << std::endl; + } + else + { + std::cerr << "Failed to set resolution." << std::endl; + } + + // Unlock configuration + [device unlockForConfiguration]; + break; + } + else + { + std::cerr << "Error setting resolution: " << [[error localizedDescription] UTF8String] << std::endl; + } + } + } + + [session commitConfiguration]; + } + + return true; +} + + +std::vector ListCaptureDevices() +{ + @autoreleasepool { + std::vector devicesInfo; + + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + for (AVCaptureDevice *device in devices) + { + CaptureDeviceInfo info = {}; + strncpy(info.friendlyName, [[device localizedName] UTF8String], sizeof(info.friendlyName) - 1); + devicesInfo.push_back(info); + } + + return devicesInfo; + } +} + +std::vector Camera::ListSupportedMediaTypes() +{ + std::vector mediaTypes; + + AVCaptureSession *session = (__bridge AVCaptureSession *)captureSession; + + if (!session || session.inputs.count == 0) { + std::cerr << "No inputs found in capture session." << std::endl; + return mediaTypes; + } + + AVCaptureDeviceInput *input = (__bridge AVCaptureDeviceInput *)session.inputs.firstObject; + AVCaptureDevice *device = input.device; + + if (!device) { + std::cerr << "No device found." << std::endl; + return mediaTypes; + } + + for (AVCaptureDeviceFormat *format in device.formats) { + MediaTypeInfo mediaType = {}; + CMFormatDescriptionRef formatDescription = format.formatDescription; + FourCharCode mediaSubType = CMFormatDescriptionGetMediaSubType(formatDescription); + + // Store width and height + CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription); + mediaType.width = dimensions.width; + mediaType.height = dimensions.height; + + // Convert mediaSubType to a string + snprintf(mediaType.subtypeName, sizeof(mediaType.subtypeName), "Format %.4s", (char *)&mediaSubType); + + mediaTypes.push_back(mediaType); + } + + return mediaTypes; +} \ No newline at end of file diff --git a/litecam/src/main.cpp b/litecam/src/main.cpp index 6f908e1..fde7e66 100644 --- a/litecam/src/main.cpp +++ b/litecam/src/main.cpp @@ -5,6 +5,10 @@ #include // For wcstombs +#if __APPLE__ +#include +#endif + void PrintMediaTypes(const std::vector &mediaTypes) { printf("Supported Media Types:\n"); @@ -63,8 +67,6 @@ int main() std::cout << "Capturing frames...\n"; if (camera.Open(0)) { - - camera.SetResolution(640, 480); auto mediaTypes = camera.ListSupportedMediaTypes(); if (mediaTypes.empty()) {