Mac OS X ambilight
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

AppDelegate.m 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. //
  2. // AppDelegate.m
  3. // DisplayBacklight
  4. //
  5. // Created by Thomas Buck on 21.12.15.
  6. // Copyright © 2015 xythobuz. All rights reserved.
  7. //
  8. #import "AppDelegate.h"
  9. #import "Serial.h"
  10. #import "Screenshot.h"
  11. // ----------------------- Config starts here -----------------------
  12. // The idea behind this algorithm is very simple. It assumes that each LED strand
  13. // follows one edge of one of your displays. So one of the two coordinates should
  14. // always be zero or the width / height of your display.
  15. // Define the amount of LEDs in your strip here
  16. #define LED_COUNT 156
  17. // This defines how large the averaging-boxes should be in the dimension perpendicular
  18. // to the strand. So eg. for a bottom strand, how high the box should be in px.
  19. #define COLOR_AVERAGE_OTHER_DIMENSION_SIZE 100
  20. // Identify your displays here. Currently they're only distinguished by their resolution.
  21. // The ID will be the index in the list, so the first entry is display 0 and so on.
  22. // The third parameter is used internally for keeping track of the visualized displays,
  23. // simply set it to 0.
  24. struct DisplayAssignment displays[] = {
  25. { 1920, 1080, 0 },
  26. { 900, 1600, 0 }
  27. };
  28. // This defines the orientation and placement of your strands and is the most important part.
  29. // It begins with the LED IDs this strand includes, starting with ID 0 up to LED_COUNT - 1.
  30. // The second item is the length of this strip, as in the count of LEDs in it.
  31. // The third item is the display ID, defined by the previous struct.
  32. // The fourth and fifth items are the starting X and Y coordinates of the strand.
  33. // As described above, one should always be zero or the display width / height.
  34. // The sixth element is the direction the strand goes (no diagonals supported yet).
  35. // The last element is the size of the averaging-box for each LED, moving with the strand.
  36. // So, if your strand contains 33 LEDs and spans 1920 pixels, this should be (1920 / 33).
  37. // By default you can always use (length in pixel / LED count) for the last item, except
  38. // if your strand does not span the whole length of this screen edge.
  39. //
  40. // For example, this is my personal dual-monitor home setup. The strand starts at the 0
  41. // in the bottom-right of D1, then goes left around D1, and from there around D2 back
  42. // to the start.
  43. //
  44. // 29
  45. // |------------|
  46. // 5 | /|\ --> -->|
  47. // 33 | | |
  48. // |---------------------------| | |
  49. // |--> --> --> --> -->| \|/ |
  50. // | | |
  51. // 19 | /|\ D1 | D2 | 48
  52. // | | 1920x1080 | 900x1600 |
  53. // | | |
  54. // |<-- <-- <-- <-- <--| 0 | |
  55. // |---------------------------| /|\ \|/ |
  56. // 33 | | |
  57. // 4 | <-- <-- |
  58. // |------------|
  59. // 29
  60. struct LEDStrand strands[] = {
  61. { 0, 33, 0, 1920, 1080, DIR_LEFT, 1920 / 33 },
  62. { 33, 19, 0, 0, 1080, DIR_UP, 1080 / 19 },
  63. { 52, 33, 0, 0, 0, DIR_RIGHT, 1920 / 33 },
  64. { 85, 5, 1, 0, 250, DIR_UP, 250 / 5 },
  65. { 90, 17, 1, 0, 0, DIR_RIGHT, 900 / 17 },
  66. { 107, 28, 1, 900, 0, DIR_DOWN, 1600 / 28 },
  67. { 135, 17, 1, 900, 1600, DIR_LEFT, 900 / 17 },
  68. { 152, 4, 1, 0, 1600, DIR_UP, 180 / 4 }
  69. };
  70. // This defines the update-speed of the Ambilight, in seconds.
  71. // With a baudrate of 115200 and 156 LEDs and 14-bytes Magic-Word,
  72. // theoretically you could transmit:
  73. // 115200 / (14 + (156 * 3)) * 8 =~ 30 Frames per Second
  74. // Inserting (1.0 / 30.0) here would try to reach these 30FPS,
  75. // but will probably cause high CPU-Usage.
  76. // (Run-Time of the algorithm is ignored here, so real speed will be
  77. // slightly lower.)
  78. #define DISPLAY_DELAY (1.0 / 10.0)
  79. // How many pixels to skip when calculating the average color.
  80. // Slightly increases performance and doesn't really alter the result.
  81. #define AVERAGE_PIXEL_SKIP 2
  82. // Magic identifying string used to differntiate start of packets.
  83. // Has to be the same here and in the Arduino Sketch.
  84. #define MAGIC_WORD @"xythobuzRGBled"
  85. // These are the values stored persistently in the preferences
  86. #define PREF_SERIAL_PORT @"SerialPort"
  87. #define PREF_BRIGHTNESS @"Brightness"
  88. #define PREF_TURNED_ON @"IsEnabled"
  89. // If this is defined it will print the FPS every DEBUG_PRINT_FPS seconds
  90. #define DEBUG_PRINT_FPS 2.5
  91. // ------------------------ Config ends here ------------------------
  92. @interface AppDelegate ()
  93. @property (weak) IBOutlet NSMenu *statusMenu;
  94. @property (weak) IBOutlet NSMenu *menuPorts;
  95. @property (weak) IBOutlet NSMenuItem *buttonAmbilight;
  96. @property (weak) IBOutlet NSMenuItem *brightnessItem;
  97. @property (weak) IBOutlet NSSlider *brightnessSlider;
  98. @property (weak) IBOutlet NSMenuItem *brightnessLabel;
  99. @property (strong) NSStatusItem *statusItem;
  100. @property (strong) NSImage *statusImage;
  101. @property (strong) NSTimer *timer;
  102. @property (strong) Serial *serial;
  103. @property (strong) NSArray *lastDisplayIDs;
  104. @property (assign) BOOL restartAmbilight;
  105. @property (strong) NSArray *captureSessions;
  106. @property (strong) NSLock *lock;
  107. @end
  108. @implementation AppDelegate
  109. @synthesize statusMenu, application;
  110. @synthesize menuPorts, buttonAmbilight;
  111. @synthesize brightnessItem, brightnessSlider, brightnessLabel;
  112. @synthesize statusItem, statusImage, lastDisplayIDs;
  113. @synthesize timer, serial, restartAmbilight;
  114. @synthesize captureSessions, lock;
  115. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  116. serial = [[Serial alloc] init];
  117. lock = [[NSLock alloc] init];
  118. timer = nil;
  119. restartAmbilight = NO;
  120. captureSessions = nil;
  121. // Set default configuration values
  122. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  123. NSMutableDictionary *appDefaults = [NSMutableDictionary dictionaryWithObject:@"" forKey:PREF_SERIAL_PORT];
  124. [appDefaults setObject:[NSNumber numberWithFloat:50.0] forKey:PREF_BRIGHTNESS];
  125. [appDefaults setObject:[NSNumber numberWithBool:NO] forKey:PREF_TURNED_ON];
  126. [store registerDefaults:appDefaults];
  127. [store synchronize];
  128. // Load existing configuration values
  129. NSString *savedPort = [store stringForKey:PREF_SERIAL_PORT];
  130. float brightness = [store floatForKey:PREF_BRIGHTNESS];
  131. BOOL ambilightIsOn = [store boolForKey:PREF_TURNED_ON];
  132. // Prepare status bar menu
  133. statusImage = [NSImage imageNamed:@"MenuIcon"];
  134. statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
  135. [statusImage setTemplate:YES];
  136. [statusItem setImage:statusImage];
  137. [statusItem setMenu:statusMenu];
  138. // Prepare brightness menu
  139. brightnessItem.view = brightnessSlider;
  140. [brightnessSlider setFloatValue:brightness];
  141. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", brightness]];
  142. // Prepare serial port menu
  143. BOOL foundPort = NO;
  144. NSArray *ports = [Serial listSerialPorts];
  145. if ([ports count] > 0) {
  146. [menuPorts removeAllItems];
  147. for (int i = 0; i < [ports count]; i++) {
  148. // Add Menu Item for this port
  149. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  150. [menuPorts addItem:item];
  151. // Set Enabled if it was used the last time
  152. if ((savedPort != nil) && [[ports objectAtIndex:i] isEqualToString:savedPort]) {
  153. // Try to open serial port
  154. [serial setPortName:savedPort];
  155. if (![serial openPort]) {
  156. foundPort = YES;
  157. [[menuPorts itemAtIndex:i] setState:NSOnState];
  158. }
  159. }
  160. }
  161. if (!foundPort) {
  162. // I'm using a cheap chinese Arduino Nano clone with a CH340 chipset.
  163. // This driver creates device-files in /dev/cu.* that don't correspond
  164. // to the chip-id and change every time the adapter is re-enumerated.
  165. // That means we may have to try and find the device again after the
  166. // stored name does no longer exist. In this case, we simply try the first
  167. // device that starts with /dev/cu.wchusbserial*...
  168. for (int i = 0; i < [ports count]; i++) {
  169. if ([[ports objectAtIndex:i] hasPrefix:@"/dev/cu.wchusbserial"]) {
  170. // Try to open serial port
  171. [serial setPortName:savedPort];
  172. if (![serial openPort]) {
  173. [[menuPorts itemAtIndex:i] setState:NSOnState];
  174. // Reattempt next matching device when opening this one fails.
  175. break;
  176. }
  177. }
  178. }
  179. }
  180. }
  181. // Enumerate displays and start ambilight if required
  182. [Screenshot init:self];
  183. restartAmbilight = ambilightIsOn;
  184. [self newDisplayList:[Screenshot listDisplays]];
  185. }
  186. - (void)applicationWillTerminate:(NSNotification *)aNotification {
  187. [self stopAmbilightTimer];
  188. // Remove display callback
  189. [Screenshot close:self];
  190. // Turn off all lights if possible
  191. if ([serial isOpen]) {
  192. [self sendNullFrame];
  193. [serial closePort];
  194. }
  195. }
  196. BOOL timerRunning = NO;
  197. - (void)startAmbilightTimer {
  198. //timer = [NSTimer scheduledTimerWithTimeInterval:DISPLAY_DELAY target:self selector:@selector(visualizeDisplay:) userInfo:nil repeats:NO];
  199. timerRunning = YES;
  200. if (captureSessions != nil) {
  201. for (int i = 0; i < [captureSessions count]; i++) {
  202. [[captureSessions objectAtIndex:i] startRunning];
  203. }
  204. }
  205. }
  206. - (void)stopAmbilightTimer {
  207. // Stop previous timer setting
  208. //if (timer != nil) {
  209. // [timer invalidate];
  210. // timer = nil;
  211. //}
  212. timerRunning = NO;
  213. if (captureSessions != nil) {
  214. for (int i = 0; i < [captureSessions count]; i++) {
  215. [[captureSessions objectAtIndex:i] stopRunning];
  216. }
  217. }
  218. }
  219. - (BOOL)isAmbilightRunning {
  220. //return (timer != nil) ? YES : NO;
  221. return timerRunning;
  222. }
  223. - (IBAction)relistSerialPorts:(id)sender {
  224. // Refill port list
  225. NSArray *ports = [Serial listSerialPorts];
  226. [menuPorts removeAllItems];
  227. for (int i = 0; i < [ports count]; i++) {
  228. // Add Menu Item for this port
  229. NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[ports objectAtIndex:i] action:@selector(selectedSerialPort:) keyEquivalent:@""];
  230. [menuPorts addItem:item];
  231. // Mark it if it is currently open
  232. if ([serial isOpen]) {
  233. if ([[ports objectAtIndex:i] isEqualToString:[serial portName]]) {
  234. [[menuPorts itemAtIndex:i] setState:NSOnState];
  235. }
  236. }
  237. }
  238. }
  239. - (void)selectedSerialPort:(NSMenuItem *)source {
  240. // Store selection for next start-up
  241. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  242. [store setObject:[source title] forKey:PREF_SERIAL_PORT];
  243. [store synchronize];
  244. // De-select all other ports
  245. for (int i = 0; i < [menuPorts numberOfItems]; i++) {
  246. [[menuPorts itemAtIndex:i] setState:NSOffState];
  247. }
  248. // Select only the current port
  249. [source setState:NSOnState];
  250. // Close previously opened port, if any
  251. if ([serial isOpen]) {
  252. [serial closePort];
  253. }
  254. [self stopAmbilightTimer];
  255. // Turn off ambilight button
  256. [buttonAmbilight setState:NSOffState];
  257. // Try to open selected port
  258. [serial setPortName:[source title]];
  259. if ([serial openPort] != 0) {
  260. [source setState:NSOffState];
  261. }
  262. }
  263. - (IBAction)toggleAmbilight:(NSMenuItem *)sender {
  264. if ([sender state] == NSOnState) {
  265. [sender setState:NSOffState];
  266. [self stopAmbilightTimer];
  267. [self sendNullFrame];
  268. // Store state
  269. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  270. [store setObject:[NSNumber numberWithBool:NO] forKey:PREF_TURNED_ON];
  271. [store synchronize];
  272. } else {
  273. [sender setState:NSOnState];
  274. [self startAmbilightTimer];
  275. // Store state
  276. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  277. [store setObject:[NSNumber numberWithBool:YES] forKey:PREF_TURNED_ON];
  278. [store synchronize];
  279. }
  280. }
  281. - (void)stopAmbilight {
  282. restartAmbilight = [self isAmbilightRunning];
  283. [buttonAmbilight setState:NSOffState];
  284. [self stopAmbilightTimer];
  285. }
  286. - (void)newDisplayList:(NSArray *)displayIDs {
  287. lastDisplayIDs = displayIDs;
  288. // Create capturing sessions for each display
  289. AVCaptureSession *sessions[[displayIDs count]];
  290. for (int i = 0; i < [displayIDs count]; i++) {
  291. sessions[i] = [[AVCaptureSession alloc] init];
  292. [sessions[i] beginConfiguration];
  293. if ([sessions[i] canSetSessionPreset:AVCaptureSessionPresetHigh]) {
  294. // TODO could use other presets?
  295. sessions[i].sessionPreset = AVCaptureSessionPresetHigh;
  296. } else {
  297. NSLog(@"Can't set preset for display %ld!", (long)[[displayIDs objectAtIndex:i] integerValue]);
  298. }
  299. // Add Screen Capture input for this screen
  300. AVCaptureScreenInput *input = [[AVCaptureScreenInput alloc] initWithDisplayID:[[displayIDs objectAtIndex:i] unsignedIntValue]];
  301. [input setCapturesCursor:YES]; // Enable mouse cursor capturing (ToDo disable for performance?)
  302. [input setMinFrameDuration:CMTimeMakeWithSeconds(DISPLAY_DELAY, 1000)]; // Set out target frame rate
  303. if ([sessions[i] canAddInput:input]) {
  304. [sessions[i] addInput:input];
  305. } else {
  306. NSLog(@"Can't add screen grab input for display %ld!", (long)[[displayIDs objectAtIndex:i] integerValue]);
  307. }
  308. // Add Screen Capture output into this object
  309. AVCaptureVideoDataOutput *output = [[AVCaptureVideoDataOutput alloc] init];
  310. [output setSampleBufferDelegate:self queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
  311. [output setVideoSettings:@{ (NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA) }];
  312. NSArray *formats = [output availableVideoCVPixelFormatTypes];
  313. for (int i = 0; i < [formats count]; i++) {
  314. NSLog(@"Supported format: 0x%lX", (long)[[formats objectAtIndex:i] integerValue]);
  315. }
  316. if ([sessions[i] canAddOutput:output]) {
  317. [sessions[i] addOutput:output];
  318. } else {
  319. NSLog(@"Can't add screen grab output for display %ld!", (long)[[displayIDs objectAtIndex:i] integerValue]);
  320. }
  321. [sessions[i] commitConfiguration];
  322. NSLog(@"Added output for display %ld", (long)[[displayIDs objectAtIndex:i] integerValue]);
  323. }
  324. captureSessions = [NSArray arrayWithObjects:sessions count:[displayIDs count]];
  325. if (restartAmbilight) {
  326. restartAmbilight = NO;
  327. [buttonAmbilight setState:NSOnState];
  328. [self startAmbilightTimer];
  329. }
  330. }
  331. - (IBAction)brightnessMoved:(NSSlider *)sender {
  332. [brightnessLabel setTitle:[NSString stringWithFormat:@"Value: %.0f%%", [sender floatValue]]];
  333. // Store changed value in preferences
  334. NSUserDefaults *store = [NSUserDefaults standardUserDefaults];
  335. [store setObject:[NSNumber numberWithFloat:[sender floatValue]] forKey:PREF_BRIGHTNESS];
  336. [store synchronize];
  337. }
  338. - (IBAction)showAbout:(id)sender {
  339. [NSApp activateIgnoringOtherApps:YES];
  340. [application orderFrontStandardAboutPanel:self];
  341. }
  342. - (void)sendLEDFrame {
  343. //NSLog(@"New LED frame");
  344. if ([serial isOpen]) {
  345. [serial sendString:MAGIC_WORD];
  346. [serial sendData:(char *)ledColorData withLength:(sizeof(ledColorData) / sizeof(ledColorData[0]))];
  347. }
  348. }
  349. - (void)sendNullFrame {
  350. for (int i = 0; i < (sizeof(ledColorData) / sizeof(ledColorData[0])); i++) {
  351. ledColorData[i] = 0;
  352. }
  353. [self sendLEDFrame];
  354. }
  355. // ----------------------------------------------------
  356. // ------------ 'Ambilight' Visualizations ------------
  357. // ----------------------------------------------------
  358. UInt8 ledColorData[LED_COUNT * 3];
  359. - (UInt32)calculateAverage:(unsigned char *)data Width:(NSInteger)width Height:(NSInteger)height SPP:(NSInteger)spp Alpha:(BOOL)alpha StartX:(NSInteger)startX StartY:(NSInteger)startY EndX:(NSInteger)endX EndY:(NSInteger)endY {
  360. //int redC = 0, greenC = 1, blueC = 2;
  361. int redC = 2, greenC = 1, blueC = 0;
  362. if (alpha) {
  363. //redC = 3; greenC = 2; blueC = 1;
  364. }
  365. NSInteger xa, xb, ya, yb;
  366. if (startX < endX) {
  367. xa = startX;
  368. xb = endX;
  369. } else {
  370. xa = endX;
  371. xb = startX;
  372. }
  373. if (startY < endY) {
  374. ya = startY;
  375. yb = endY;
  376. } else {
  377. ya = endY;
  378. yb = startY;
  379. }
  380. unsigned long red = 0, green = 0, blue = 0, count = 0;
  381. for (NSInteger i = xa; i < xb; i += AVERAGE_PIXEL_SKIP) {
  382. for (NSInteger j = ya; j < yb; j++) {
  383. count++;
  384. unsigned long index = i + (j * width);
  385. red += data[(index * spp) + redC];
  386. green += data[(index * spp) + greenC];
  387. blue += data[(index * spp) + blueC];
  388. }
  389. }
  390. red /= count;
  391. green /= count;
  392. blue /= count;
  393. red *= [brightnessSlider floatValue] / 100.0f;
  394. green *= [brightnessSlider floatValue] / 100.0f;
  395. blue *= [brightnessSlider floatValue] / 100.0f;
  396. return ((UInt32)red << 16) | ((UInt32)green << 8) | ((UInt32)blue);
  397. }
  398. - (void)visualizeSingleDisplay:(NSInteger)disp Data:(unsigned char *)data Width:(unsigned long)width Height:(unsigned long)height SPP:(NSInteger)spp Alpha:(BOOL)alpha {
  399. displays[disp].shown = 1;
  400. for (int i = 0; i < (sizeof(strands) / sizeof(strands[0])); i++) {
  401. if (strands[i].display == disp) {
  402. // Walk the strand, calculating value for each LED
  403. unsigned long x = strands[i].startX;
  404. unsigned long y = strands[i].startY;
  405. unsigned long blockWidth = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  406. unsigned long blockHeight = COLOR_AVERAGE_OTHER_DIMENSION_SIZE;
  407. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  408. blockWidth = strands[i].size;
  409. } else {
  410. blockHeight = strands[i].size;
  411. }
  412. for (int led = strands[i].idMin; led < (strands[i].idMin + strands[i].count); led++) {
  413. // First move appropriately in the direction of the strand
  414. unsigned long endX = x, endY = y;
  415. if (strands[i].direction == DIR_LEFT) {
  416. endX -= blockWidth;
  417. } else if (strands[i].direction == DIR_RIGHT) {
  418. endX += blockWidth;
  419. } else if (strands[i].direction == DIR_UP) {
  420. endY -= blockHeight;
  421. } else if (strands[i].direction == DIR_DOWN) {
  422. endY += blockHeight;
  423. }
  424. // But also span the averaging-square in the other dimension, depending on which
  425. // side of the monitor we're at.
  426. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  427. if (y == 0) {
  428. endY = blockHeight;
  429. } else if (y == displays[disp].height) {
  430. endY -= blockHeight;
  431. }
  432. } else {
  433. if (x == 0) {
  434. endX = blockWidth;
  435. } else if (x == displays[disp].width) {
  436. endX -= blockWidth;
  437. }
  438. }
  439. // Calculate average color for this led
  440. UInt32 color = [self calculateAverage:data Width:width Height:height SPP:spp Alpha:alpha StartX:x StartY:y EndX:endX EndY:endY];
  441. ledColorData[led * 3] = (color & 0xFF0000) >> 16;
  442. ledColorData[(led * 3) + 1] = (color & 0x00FF00) >> 8;
  443. ledColorData[(led * 3) + 2] = color & 0x0000FF;
  444. // Move to next LED
  445. if ((strands[i].direction == DIR_LEFT) || (strands[i].direction == DIR_RIGHT)) {
  446. x = endX;
  447. } else {
  448. y = endY;
  449. }
  450. }
  451. }
  452. }
  453. int doneCount = 0;;
  454. for (int i = 0; i < (sizeof(displays) / sizeof(displays[0])); i++) {
  455. if (displays[i].shown != 0) {
  456. doneCount++;
  457. }
  458. }
  459. if (doneCount >= (sizeof(displays) / sizeof(displays[0]))) {
  460. [self sendLEDFrame];
  461. for (int i = 0; i < (sizeof(displays) / sizeof(displays[0])); i++) {
  462. displays[i].shown = 0;
  463. }
  464. }
  465. }
  466. - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
  467. [lock lock];
  468. #ifdef DEBUG_PRINT_FPS
  469. static NSInteger frameCount = 0;
  470. static NSDate *lastPrintTime = nil;
  471. if (lastPrintTime == nil) {
  472. lastPrintTime = [NSDate date];
  473. }
  474. #endif
  475. if (![self isAmbilightRunning]) {
  476. [lock unlock];
  477. return;
  478. }
  479. CMFormatDescriptionRef formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer);
  480. CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(formatDescription);
  481. //NSLog(@"W=%d H=%d", dimensions.width, dimensions.height);
  482. // Try to find the matching display id for the strand associations
  483. for (int n = 0; n < (sizeof(displays) / sizeof(displays[0])); n++) {
  484. if ((dimensions.width == displays[n].width) && (dimensions.height == displays[n].height)) {
  485. //NSLog(@"Capture conversion for %d...", n);
  486. // Convert our frame to an NSImage
  487. CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  488. CVPixelBufferLockBaseAddress(imageBuffer, 0);
  489. void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
  490. size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
  491. size_t width = CVPixelBufferGetWidth(imageBuffer);
  492. size_t height = CVPixelBufferGetHeight(imageBuffer);
  493. CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  494. CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow,
  495. colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Little);
  496. if (context == nil) {
  497. NSLog(@"Error creating context!");
  498. break;
  499. }
  500. CGImageRef quartzImage = CGBitmapContextCreateImage(context);
  501. CVPixelBufferUnlockBaseAddress(imageBuffer,0);
  502. CGContextRelease(context);
  503. CGColorSpaceRelease(colorSpace);
  504. NSBitmapImageRep *image = [[NSBitmapImageRep alloc] initWithCGImage:quartzImage];
  505. CGImageRelease(quartzImage);
  506. [self visualizeThisImage:image];
  507. break;
  508. }
  509. }
  510. #ifdef DEBUG_PRINT_FPS
  511. frameCount++;
  512. NSDate *now = [NSDate date];
  513. NSTimeInterval interval = [now timeIntervalSinceDate:lastPrintTime];
  514. if (interval >= DEBUG_PRINT_FPS) {
  515. NSLog(@"FPS: ~%.2f / %lu = %.2f", frameCount / interval,
  516. (sizeof(displays) / sizeof(displays[0])), frameCount / interval / (sizeof(displays) / sizeof(displays[0])));
  517. frameCount = 0;
  518. lastPrintTime = now;
  519. }
  520. #endif
  521. [lock unlock];
  522. }
  523. - (void)visualizeThisImage:(NSBitmapImageRep *)screen {
  524. unsigned long width = [screen pixelsWide];
  525. unsigned long height = [screen pixelsHigh];
  526. // Ensure we can handle the format of this display
  527. NSInteger spp = [screen samplesPerPixel];
  528. if (((spp != 3) && (spp != 4)) || ([screen isPlanar] == YES) || ([screen numberOfPlanes] != 1)) {
  529. NSLog(@"Unknown image format for (%ld, %c, %ld)!\n", (long)spp, ([screen isPlanar] == YES) ? 'p' : 'n', (long)[screen numberOfPlanes]);
  530. return;
  531. }
  532. // Find out how the color components are ordered
  533. BOOL alpha = NO;
  534. if ([screen bitmapFormat] & NSAlphaFirstBitmapFormat) {
  535. alpha = YES;
  536. }
  537. // Try to find the matching display id for the strand associations
  538. for (int n = 0; n < (sizeof(displays) / sizeof(displays[0])); n++) {
  539. if ((width == displays[n].width) && (height == displays[n].height)) {
  540. unsigned char *data = [screen bitmapData];
  541. [self visualizeSingleDisplay:n Data:data Width:width Height:height SPP:spp Alpha:alpha];
  542. return;
  543. }
  544. }
  545. }
  546. /*
  547. - (void)visualizeDisplay:(NSTimer *)time {
  548. #ifdef DEBUG_PRINT_FPS
  549. static NSInteger frameCount = 0;
  550. static NSDate *lastPrintTime = nil;
  551. if (lastPrintTime == nil) {
  552. lastPrintTime = [NSDate date];
  553. }
  554. #endif
  555. //NSLog(@"Running Ambilight-Algorithm (%lu)...", (unsigned long)[lastDisplayIDs count]);
  556. // Create a Screenshot for all connected displays
  557. for (NSInteger i = 0; i < [lastDisplayIDs count]; i++) {
  558. NSBitmapImageRep *screen = [Screenshot screenshot:[lastDisplayIDs objectAtIndex:i]];
  559. [self visualizeThisImage:screen];
  560. }
  561. [self sendLEDFrame];
  562. #ifdef DEBUG_PRINT_FPS
  563. frameCount++;
  564. NSDate *now = [NSDate date];
  565. NSTimeInterval interval = [now timeIntervalSinceDate:lastPrintTime];
  566. if (interval >= DEBUG_PRINT_FPS) {
  567. NSLog(@"FPS: %.2f", frameCount / interval);
  568. frameCount = 0;
  569. lastPrintTime = now;
  570. }
  571. #endif
  572. [self startAmbilightTimer];
  573. }
  574. */
  575. @end