camera_macos.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /**************************************************************************/
  2. /* camera_macos.mm */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. ///@TODO this is a near duplicate of CameraIOS, we should find a way to combine those to minimize code duplication!!!!
  31. // If you fix something here, make sure you fix it there as well!
  32. #import "camera_macos.h"
  33. #include "servers/camera/camera_feed.h"
  34. #import <AVFoundation/AVFoundation.h>
  35. //////////////////////////////////////////////////////////////////////////
  36. // MyCaptureSession - This is a little helper class so we can capture our frames
  37. @interface MyCaptureSession : AVCaptureSession <AVCaptureVideoDataOutputSampleBufferDelegate> {
  38. Ref<CameraFeed> feed;
  39. size_t width[2];
  40. size_t height[2];
  41. Vector<uint8_t> img_data[2];
  42. AVCaptureDeviceInput *input;
  43. AVCaptureVideoDataOutput *output;
  44. }
  45. @end
  46. @implementation MyCaptureSession
  47. - (id)initForFeed:(Ref<CameraFeed>)p_feed andDevice:(AVCaptureDevice *)p_device {
  48. if (self = [super init]) {
  49. NSError *error;
  50. feed = p_feed;
  51. width[0] = 0;
  52. height[0] = 0;
  53. width[1] = 0;
  54. height[1] = 0;
  55. [self beginConfiguration];
  56. input = [AVCaptureDeviceInput deviceInputWithDevice:p_device error:&error];
  57. if (!input) {
  58. print_line("Couldn't get input device for camera");
  59. [self commitConfiguration];
  60. return nil;
  61. }
  62. [self addInput:input];
  63. output = [AVCaptureVideoDataOutput new];
  64. if (!output) {
  65. print_line("Couldn't get output device for camera");
  66. [self commitConfiguration];
  67. return nil;
  68. }
  69. NSDictionary *settings = @{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) };
  70. output.videoSettings = settings;
  71. // discard if the data output queue is blocked (as we process the still image)
  72. [output setAlwaysDiscardsLateVideoFrames:YES];
  73. // now set ourselves as the delegate to receive new frames.
  74. [output setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
  75. // this takes ownership
  76. [self addOutput:output];
  77. [self commitConfiguration];
  78. // kick off our session..
  79. [self startRunning];
  80. };
  81. return self;
  82. }
  83. - (void)cleanup {
  84. // stop running
  85. [self stopRunning];
  86. // cleanup
  87. [self beginConfiguration];
  88. // remove input
  89. if (input) {
  90. [self removeInput:input];
  91. // don't release this
  92. input = nullptr;
  93. }
  94. // free up our output
  95. if (output) {
  96. [self removeOutput:output];
  97. [output setSampleBufferDelegate:nil queue:nullptr];
  98. output = nullptr;
  99. }
  100. [self commitConfiguration];
  101. }
  102. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  103. // This gets called every time our camera has a new image for us to process.
  104. // May need to investigate in a way to throttle this if we get more images then we're rendering frames..
  105. // For now, version 1, we're just doing the bare minimum to make this work...
  106. CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  107. if (pixelBuffer == nullptr) {
  108. return;
  109. }
  110. // It says that we need to lock this on the documentation pages but it's not in the samples
  111. // need to lock our base address so we can access our pixel buffers, better safe then sorry?
  112. CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
  113. // Check if we have the expected number of planes (Y and CbCr).
  114. size_t planeCount = CVPixelBufferGetPlaneCount(pixelBuffer);
  115. if (planeCount < 2) {
  116. static bool plane_count_error_logged = false;
  117. if (!plane_count_error_logged) {
  118. ERR_PRINT("Unexpected plane count in pixel buffer (expected 2, got " + itos(planeCount) + ")");
  119. plane_count_error_logged = true;
  120. }
  121. CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
  122. return;
  123. }
  124. // get our buffers
  125. unsigned char *dataY = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
  126. unsigned char *dataCbCr = (unsigned char *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
  127. if (dataY == nullptr || dataCbCr == nullptr) {
  128. static bool buffer_access_error_logged = false;
  129. if (!buffer_access_error_logged) {
  130. ERR_PRINT("Couldn't access pixel buffer plane data");
  131. buffer_access_error_logged = true;
  132. }
  133. CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
  134. return;
  135. }
  136. Ref<Image> img[2];
  137. {
  138. // do Y
  139. size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
  140. size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
  141. if ((width[0] != new_width) || (height[0] != new_height)) {
  142. width[0] = new_width;
  143. height[0] = new_height;
  144. img_data[0].resize(new_width * new_height);
  145. }
  146. uint8_t *w = img_data[0].ptrw();
  147. memcpy(w, dataY, new_width * new_height);
  148. img[0].instantiate();
  149. img[0]->set_data(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
  150. }
  151. {
  152. // do CbCr
  153. size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
  154. size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
  155. if ((width[1] != new_width) || (height[1] != new_height)) {
  156. width[1] = new_width;
  157. height[1] = new_height;
  158. img_data[1].resize(2 * new_width * new_height);
  159. }
  160. uint8_t *w = img_data[1].ptrw();
  161. memcpy(w, dataCbCr, 2 * new_width * new_height);
  162. ///TODO OpenGL doesn't support FORMAT_RG8, need to do some form of conversion
  163. img[1].instantiate();
  164. img[1]->set_data(new_width, new_height, 0, Image::FORMAT_RG8, img_data[1]);
  165. }
  166. // set our texture...
  167. feed->set_ycbcr_images(img[0], img[1]);
  168. // and unlock
  169. CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
  170. }
  171. @end
  172. //////////////////////////////////////////////////////////////////////////
  173. // CameraFeedMacOS - Subclass for camera feeds in macOS
  174. class CameraFeedMacOS : public CameraFeed {
  175. GDSOFTCLASS(CameraFeedMacOS, CameraFeed);
  176. private:
  177. AVCaptureDevice *device;
  178. MyCaptureSession *capture_session;
  179. public:
  180. AVCaptureDevice *get_device() const;
  181. CameraFeedMacOS();
  182. ~CameraFeedMacOS();
  183. void set_device(AVCaptureDevice *p_device);
  184. bool activate_feed() override;
  185. void deactivate_feed() override;
  186. };
  187. AVCaptureDevice *CameraFeedMacOS::get_device() const {
  188. return device;
  189. }
  190. CameraFeedMacOS::CameraFeedMacOS() {
  191. device = nullptr;
  192. capture_session = nullptr;
  193. }
  194. CameraFeedMacOS::~CameraFeedMacOS() {
  195. if (is_active()) {
  196. deactivate_feed();
  197. }
  198. }
  199. void CameraFeedMacOS::set_device(AVCaptureDevice *p_device) {
  200. device = p_device;
  201. // get some info
  202. NSString *device_name = p_device.localizedName;
  203. name = String::utf8(device_name.UTF8String);
  204. position = CameraFeed::FEED_UNSPECIFIED;
  205. if ([p_device position] == AVCaptureDevicePositionBack) {
  206. position = CameraFeed::FEED_BACK;
  207. } else if ([p_device position] == AVCaptureDevicePositionFront) {
  208. position = CameraFeed::FEED_FRONT;
  209. };
  210. }
  211. bool CameraFeedMacOS::activate_feed() {
  212. if (capture_session) {
  213. // Already recording.
  214. return true;
  215. }
  216. // Start camera capture, check permission.
  217. if (@available(macOS 10.14, *)) {
  218. AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
  219. if (status == AVAuthorizationStatusAuthorized) {
  220. capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
  221. return capture_session != nullptr;
  222. } else if (status == AVAuthorizationStatusNotDetermined) {
  223. // Request permission asynchronously.
  224. [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
  225. completionHandler:^(BOOL granted) {
  226. if (granted) {
  227. capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
  228. }
  229. }];
  230. return false;
  231. } else if (status == AVAuthorizationStatusDenied) {
  232. print_line("Camera permission denied by user.");
  233. return false;
  234. } else if (status == AVAuthorizationStatusRestricted) {
  235. print_line("Camera access restricted.");
  236. return false;
  237. }
  238. return false;
  239. } else {
  240. capture_session = [[MyCaptureSession alloc] initForFeed:this andDevice:device];
  241. return capture_session != nullptr;
  242. }
  243. }
  244. void CameraFeedMacOS::deactivate_feed() {
  245. // end camera capture if we have one
  246. if (capture_session) {
  247. [capture_session cleanup];
  248. capture_session = nullptr;
  249. };
  250. }
  251. //////////////////////////////////////////////////////////////////////////
  252. // MyDeviceNotifications - This is a little helper class gets notifications
  253. // when devices are connected/disconnected
  254. @interface MyDeviceNotifications : NSObject {
  255. CameraMacOS *camera_server;
  256. }
  257. @end
  258. @implementation MyDeviceNotifications
  259. - (void)devices_changed:(NSNotification *)notification {
  260. camera_server->update_feeds();
  261. }
  262. - (id)initForServer:(CameraMacOS *)p_server {
  263. if (self = [super init]) {
  264. camera_server = p_server;
  265. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasConnectedNotification object:nil];
  266. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(devices_changed:) name:AVCaptureDeviceWasDisconnectedNotification object:nil];
  267. };
  268. return self;
  269. }
  270. - (void)dealloc {
  271. // remove notifications
  272. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasConnectedNotification object:nil];
  273. [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceWasDisconnectedNotification object:nil];
  274. }
  275. @end
  276. MyDeviceNotifications *device_notifications = nil;
  277. //////////////////////////////////////////////////////////////////////////
  278. // CameraMacOS - Subclass for our camera server on macOS
  279. void CameraMacOS::update_feeds() {
  280. NSArray<AVCaptureDevice *> *devices = nullptr;
  281. #if defined(__x86_64__)
  282. if (@available(macOS 10.15, *)) {
  283. #endif
  284. AVCaptureDeviceDiscoverySession *session;
  285. if (@available(macOS 14.0, *)) {
  286. session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternal, AVCaptureDeviceTypeBuiltInWideAngleCamera, AVCaptureDeviceTypeContinuityCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
  287. } else {
  288. session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeExternalUnknown, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
  289. }
  290. devices = session.devices;
  291. #if defined(__x86_64__)
  292. } else {
  293. devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
  294. }
  295. #endif
  296. // Deactivate feeds that are gone before removing them.
  297. for (int i = feeds.size() - 1; i >= 0; i--) {
  298. Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
  299. if (feed.is_null()) {
  300. continue;
  301. }
  302. if (![devices containsObject:feed->get_device()]) {
  303. if (feed->is_active()) {
  304. feed->deactivate_feed();
  305. }
  306. remove_feed(feed);
  307. };
  308. };
  309. for (AVCaptureDevice *device in devices) {
  310. bool found = false;
  311. for (int i = 0; i < feeds.size() && !found; i++) {
  312. Ref<CameraFeedMacOS> feed = (Ref<CameraFeedMacOS>)feeds[i];
  313. if (feed.is_null()) {
  314. continue;
  315. }
  316. if (feed->get_device() == device) {
  317. found = true;
  318. };
  319. };
  320. if (!found) {
  321. Ref<CameraFeedMacOS> newfeed;
  322. newfeed.instantiate();
  323. newfeed->set_device(device);
  324. add_feed(newfeed);
  325. };
  326. };
  327. emit_signal(SNAME(CameraServer::feeds_updated_signal_name));
  328. }
  329. void CameraMacOS::set_monitoring_feeds(bool p_monitoring_feeds) {
  330. if (p_monitoring_feeds == monitoring_feeds) {
  331. return;
  332. }
  333. CameraServer::set_monitoring_feeds(p_monitoring_feeds);
  334. if (p_monitoring_feeds) {
  335. // Find available cameras we have at this time.
  336. update_feeds();
  337. // Get notified on feed changes.
  338. device_notifications = [[MyDeviceNotifications alloc] initForServer:this];
  339. } else {
  340. // Stop monitoring feed changes.
  341. device_notifications = nil;
  342. }
  343. }