diff options
Diffstat (limited to 'iphone/ZBarCaptureReader.m')
-rw-r--r-- | iphone/ZBarCaptureReader.m | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/iphone/ZBarCaptureReader.m b/iphone/ZBarCaptureReader.m new file mode 100644 index 0000000..b3dd65c --- /dev/null +++ b/iphone/ZBarCaptureReader.m @@ -0,0 +1,370 @@ +//------------------------------------------------------------------------ +// Copyright 2010 (c) Jeff Brown <spadix@users.sourceforge.net> +// +// This file is part of the ZBar Bar Code Reader. +// +// The ZBar Bar Code Reader is free software; you can redistribute it +// and/or modify it under the terms of the GNU Lesser Public License as +// published by the Free Software Foundation; either version 2.1 of +// the License, or (at your option) any later version. +// +// The ZBar Bar Code Reader is distributed in the hope that it will be +// useful, but WITHOUT ANY WARRANTY; without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser Public License for more details. +// +// You should have received a copy of the GNU Lesser Public License +// along with the ZBar Bar Code Reader; if not, write to the Free +// Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301 USA +// +// http://sourceforge.net/projects/zbar +//------------------------------------------------------------------------ + +#import <libkern/OSAtomic.h> +#import <AVFoundation/AVFoundation.h> +#import <CoreMedia/CoreMedia.h> +#import <CoreVideo/CoreVideo.h> +#import <ZBarSDK/ZBarCaptureReader.h> +#import <ZBarSDK/ZBarImageScanner.h> +#import "ZBarCVImage.h" + +#define MODULE ZBarCaptureReader +#import "debug.h" + +enum { + STOPPED = 0, + RUNNING = 1, + PAUSED = 2, + CAPTURE = 4, +}; + +@implementation ZBarCaptureReader + +@synthesize captureOutput, captureDelegate, scanner, scanCrop, size, + framesPerSecond, enableCache; +@dynamic enableReader; + +- (void) initResult +{ + [result release]; + result = [ZBarCVImage new]; + result.format = [ZBarImage fourcc: @"CV2P"]; +} + +- (id) initWithImageScanner: (ZBarImageScanner*) _scanner +{ + self = [super init]; + if(!self) + return(nil); + + t_fps = t_frame = timer_now(); + enableCache = YES; + + scanner = [_scanner retain]; + scanCrop = CGRectMake(0, 0, 1, 1); + image = [ZBarImage new]; + image.format = [ZBarImage fourcc: @"Y800"]; + [self initResult]; + + captureOutput = [AVCaptureVideoDataOutput new]; + captureOutput.alwaysDiscardsLateVideoFrames = YES; + +#ifdef FIXED_8697526 + /* iOS 4.2 introduced a bug that causes [session startRunning] to + * hang if the session has a preview layer and this property is + * specified at the output. As this happens to be the default + * setting for the currently supported devices, it can be omitted + * without causing a functional problem (for now...). Of course, + * we still have no idea what the real problem is, or how robust + * this is as a workaround... + */ + captureOutput.videoSettings = + [NSDictionary + dictionaryWithObject: + [NSNumber numberWithInt: + kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] + forKey: (NSString*)kCVPixelBufferPixelFormatTypeKey]; +#endif + + queue = dispatch_queue_create("ZBarCaptureReader", NULL); + [captureOutput setSampleBufferDelegate: + (id<AVCaptureVideoDataOutputSampleBufferDelegate>)self + queue: queue]; + + return(self); +} + +- (id) init +{ + self = [self initWithImageScanner: + [[ZBarImageScanner new] + autorelease]]; + if(!self) + return(nil); + + [scanner setSymbology: 0 + config: ZBAR_CFG_X_DENSITY + to: 3]; + [scanner setSymbology: 0 + config: ZBAR_CFG_Y_DENSITY + to: 3]; + return(self); +} + +- (void) dealloc +{ + captureDelegate = nil; + + // queue continues to run after stopping (NB even after DidStopRunning!); + // ensure released delegate is not called. (also NB that the queue + // may not be null, even in this case...) + [captureOutput setSampleBufferDelegate: nil + queue: queue]; + [captureOutput release]; + captureOutput = nil; + dispatch_release(queue); + + [image release]; + image = nil; + [result release]; + result = nil; + [scanner release]; + scanner = nil; + [super dealloc]; +} + +- (BOOL) enableReader +{ + return(OSAtomicOr32Barrier(0, &state) & RUNNING); +} + +- (void) setEnableReader: (BOOL) enable +{ + if(!enable) + OSAtomicAnd32Barrier(STOPPED, &state); + else if(!(OSAtomicOr32OrigBarrier(RUNNING, &state) & RUNNING)) { + OSAtomicAnd32Barrier(~PAUSED, &state); + @synchronized(scanner) { + scanner.enableCache = enableCache; + } + } +} + +- (void) willStartRunning +{ + self.enableReader = YES; +} + +- (void) willStopRunning +{ + self.enableReader = NO; +} + +- (void) flushCache +{ + @synchronized(scanner) { + scanner.enableCache = enableCache; + } +} + +- (void) captureFrame +{ + OSAtomicOr32(CAPTURE, &state); +} + +- (void) setCaptureDelegate: (id<ZBarCaptureDelegate>) delegate +{ + @synchronized(scanner) { + captureDelegate = delegate; + } +} + +- (void) cropUpdate +{ + @synchronized(scanner) { + image.crop = CGRectMake(scanCrop.origin.x * width, + scanCrop.origin.y * height, + scanCrop.size.width * width, + scanCrop.size.height * height); + } +} + +- (void) setScanCrop: (CGRect) crop +{ + if(CGRectEqualToRect(scanCrop, crop)) + return; + scanCrop = crop; + [self cropUpdate]; +} + +- (void) didTrackSymbols: (ZBarSymbolSet*) syms +{ + [captureDelegate + captureReader: self + didTrackSymbols: syms]; +} + +- (void) didReadNewSymbolsFromImage: (ZBarImage*) img +{ + timer_start; + [captureDelegate + captureReader: self + didReadNewSymbolsFromImage: img]; + OSAtomicAnd32Barrier(~PAUSED, &state); + zlog(@"latency: delegate=%gs total=%gs", + timer_elapsed(t_start, timer_now()), + timer_elapsed(t_scan, timer_now())); +} + +- (void) setFramesPerSecond: (CGFloat) fps +{ + framesPerSecond = fps; +} + +- (void) updateFPS: (NSNumber*) val +{ + [self setFramesPerSecond: val.doubleValue]; +} + +- (void) setSize: (CGSize) _size +{ + size = _size; +} + +- (void) updateSize: (CFDictionaryRef) val +{ + CGSize _size; + if(CGSizeMakeWithDictionaryRepresentation(val, &_size)) + [self setSize: _size]; +} + +- (void) captureOutput: (AVCaptureOutput*) output + didOutputSampleBuffer: (CMSampleBufferRef) samp + fromConnection: (AVCaptureConnection*) conn +{ + // queue is apparently not flushed when stopping; + // only process when running + uint32_t _state = OSAtomicOr32Barrier(0, &state); + if((_state & (PAUSED | RUNNING)) != RUNNING) + return; + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + image.sequence = framecnt++; + + uint64_t now = timer_now(); + double dt = timer_elapsed(t_frame, now); + t_frame = now; + if(dt > 2) { + t_fps = now; + dt_frame = 0; + } + else if(!dt_frame) + dt_frame = dt; + dt_frame = (dt_frame + dt) / 2; + if(timer_elapsed(t_fps, now) >= 1) { + [self performSelectorOnMainThread: @selector(updateFPS:) + withObject: [NSNumber numberWithDouble: 1 / dt_frame] + waitUntilDone: NO]; + t_fps = now; + } + + CVImageBufferRef buf = CMSampleBufferGetImageBuffer(samp); + if(CMSampleBufferGetNumSamples(samp) != 1 || + !CMSampleBufferIsValid(samp) || + !CMSampleBufferDataIsReady(samp) || + !buf) { + zlog(@"ERROR: invalid sample"); + goto error; + } + + OSType format = CVPixelBufferGetPixelFormatType(buf); + int planes = CVPixelBufferGetPlaneCount(buf); + + if(format != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange || + !planes) { + zlog(@"ERROR: invalid buffer format"); + goto error; + } + + int w = CVPixelBufferGetBytesPerRowOfPlane(buf, 0); + int h = CVPixelBufferGetHeightOfPlane(buf, 0); + CVReturn rc = + CVPixelBufferLockBaseAddress(buf, kCVPixelBufferLock_ReadOnly); + if(!w || !h || rc) { + zlog(@"ERROR: invalid buffer data"); + goto error; + } + + void *data = CVPixelBufferGetBaseAddressOfPlane(buf, 0); + if(data) { + [image setData: data + withLength: w * h]; + + BOOL doTrack = NO; + int ngood = 0; + ZBarSymbolSet *syms = nil; + @synchronized(scanner) { + if(width != w || height != h) { + width = w; + height = h; + CGSize _size = CGSizeMake(w, h); + CFDictionaryRef sized = + CGSizeCreateDictionaryRepresentation(_size); + if(sized) { + [self performSelectorOnMainThread: @selector(updateSize:) + withObject: (id)sized + waitUntilDone: NO]; + CFRelease(sized); + } + image.size = _size; + [self cropUpdate]; + } + + ngood = [scanner scanImage: image]; + syms = scanner.results; + doTrack = [captureDelegate respondsToSelector: + @selector(captureReader:didTrackSymbols:)]; + } + now = timer_now(); + + if(ngood >= 0) { + // return unfiltered results for tracking feedback + syms.filterSymbols = NO; + int nraw = syms.count; + if(nraw > 0 || (_state & CAPTURE)) + zlog(@"scan image: %dx%d crop=%@ ngood=%d nraw=%d st=%d", + w, h, NSStringFromCGRect(image.crop), ngood, nraw, _state); + + if(ngood || (_state & CAPTURE)) { + // copy image data so we can release the buffer + result.size = CGSizeMake(w, h); + result.pixelBuffer = buf; + result.symbols = syms; + t_scan = now; + OSAtomicXor32Barrier((_state & CAPTURE) | PAUSED, &state); + [self performSelectorOnMainThread: + @selector(didReadNewSymbolsFromImage:) + withObject: result + waitUntilDone: NO]; + [self initResult]; + } + + if(nraw && doTrack) + [self performSelectorOnMainThread: + @selector(didTrackSymbols:) + withObject: syms + waitUntilDone: NO]; + } + [image setData: NULL + withLength: 0]; + } + else + zlog(@"ERROR: invalid data"); + CVPixelBufferUnlockBaseAddress(buf, kCVPixelBufferLock_ReadOnly); + + error: + [pool release]; +} + +@end |