Forked from domadev812/pubnubheartrateios-captureframe
Created
November 15, 2017 21:06
-
-
Save stephenlb/3e26bbf0751dcd4071afe62c979263ab to your computer and use it in GitHub Desktop.
PubNub Heart Rate App for iOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection { | |
// if we're paused don't do anything | |
if(self.currentState==STATE_PAUSED) { | |
// reset our frame counter | |
self.validFrameCounter=0; | |
return; | |
} | |
// this is the image buffer | |
CVImageBufferRef cvimgRef = CMSampleBufferGetImageBuffer(sampleBuffer); | |
// Lock the image buffer | |
CVPixelBufferLockBaseAddress(cvimgRef,0); | |
// access the data | |
size_t width=CVPixelBufferGetWidth(cvimgRef); | |
size_t height=CVPixelBufferGetHeight(cvimgRef); | |
// get the raw image bytes | |
uint8_t *buf=(uint8_t *) CVPixelBufferGetBaseAddress(cvimgRef); | |
size_t bprow=CVPixelBufferGetBytesPerRow(cvimgRef); | |
// and pull out the average rgb value of the frame | |
float r=0,g=0,b=0; | |
for(int y=0; y<height; y++) { | |
for(int x=0; x<width*4; x+=4) { | |
b+=buf[x]; | |
g+=buf[x+1]; | |
r+=buf[x+2]; | |
} | |
buf+=bprow; | |
} | |
r/=255*(float) (width*height); | |
g/=255*(float) (width*height); | |
b/=255*(float) (width*height); | |
// convert from rgb to hsv colourspace | |
float h,s,v; | |
RGBtoHSV(r, g, b, &h, &s, &v); | |
// do a sanity check to see if a finger is placed over the camera | |
if(s>0.5 && v>0.5) { | |
NSLog(@"RatePulse: %@",self.pulseRate.text); | |
// increment the valid frame count | |
self.validFrameCounter++; | |
// filter the hue value - the filter is a simple band pass filter that removes any DC component and any high frequency noise | |
float filtered=[self.filter processValue:h]; | |
// have we collected enough frames for the filter to settle? | |
if(self.validFrameCounter > MIN_FRAMES_FOR_FILTER_TO_SETTLE) { | |
// add the new value to the pulse detector | |
[self.pulseDetector addNewValue:filtered atTime:CACurrentMediaTime()]; | |
} | |
TimerBool=YES; | |
} else { | |
TimerBool=NO; | |
self.validFrameCounter = 0; | |
// clear the pulse detector - we only really need to do this once, just before we start adding valid samples | |
[self.pulseDetector reset]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- (IBAction)SaveDoctId_btn:(id)sender { | |
if ([_doctorId_txtfld.text length] == 0 ) { | |
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"PubNub" message:@"Please enter DoctorId" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; | |
[alert show]; | |
} | |
else{ | |
[self.view endEditing:YES]; | |
_backButton.hidden=NO; | |
self.pulseRate.text=@"PLACE FINGER ON CAMERA LENS"; | |
[_heartImage setImage:[UIImage imageNamed:@"Black1_heart.png"]]; | |
[UIView animateWithDuration:1.0 | |
animations:^{ | |
_doctorId_view.alpha = 0; | |
} | |
completion:^(BOOL finished){ | |
[UIView animateWithDuration:1.0 | |
animations:^{ | |
_doctorId_view.alpha = 0; | |
} | |
completion:^(BOOL finished){ | |
[_doctorId_view setHidden:YES]; | |
}]; | |
}]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { | |
// Override point for customization after application launch. | |
PNConfiguration *configuration = [PNConfiguration configurationWithPublishKey:@"demo" subscribeKey:@"demo"]; | |
self.client = [PubNub clientWithConfiguration:configuration]; | |
[self.client addListener:self]; | |
[self.client subscribeToChannels: @[@"doctor_id"] withPresence:YES]; | |
return YES; | |
} | |
-(void)PublishOnPubNub :(NSString*)PulseRate docId:(NSString*)Docid{ | |
NSString *DocId=[NSString stringWithFormat:@"%@heartbeat_alert",Docid]; | |
[self.client publish: @"Hello from PubNub iOS!" toChannel: DocId storeInHistory:YES | |
withCompletion:^(PNPublishStatus *status) { | |
[self stopLoader]; | |
// Check whether request successfully completed or not. | |
if (!status.isError) { | |
// Message successfully published to specified channel. | |
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"PubNub" message:@"Your Message Successfuly sent to doctor" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; | |
[alert show]; | |
} | |
// Request processing failed. | |
else { | |
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"PubNub" message:@"Error Occurred !!" delegate:self cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; | |
[alert show]; | |
// Handle message publish error. Check 'category' property to find out possible issue | |
// because of which request did fail. | |
// | |
// Request can be resent using: [status retry]; | |
} | |
}]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-(float) addNewValue:(float) newVal atTime:(double) time { | |
// we keep track of the number of values above and below zero | |
if(newVal>0) { | |
upVals[upValIndex]=newVal; | |
upValIndex++; | |
if(upValIndex>=AVERAGE_SIZE) { | |
upValIndex=0; | |
} | |
} | |
if(newVal<0) { | |
downVals[downValIndex]=-newVal; | |
downValIndex++; | |
if(downValIndex>=AVERAGE_SIZE) { | |
downValIndex=0; | |
} | |
} | |
// work out the average value above zero | |
float count=0; | |
float total=0; | |
for(int i=0; i<AVERAGE_SIZE; i++) { | |
if(upVals[i]!=INVALID_ENTRY) { | |
count++; | |
total+=upVals[i]; | |
} | |
} | |
float averageUp=total/count; | |
// and the average value below zero | |
count=0; | |
total=0; | |
for(int i=0; i<AVERAGE_SIZE; i++) { | |
if(downVals[i]!=INVALID_ENTRY) { | |
count++; | |
total+=downVals[i]; | |
} | |
} | |
float averageDown=total/count; | |
// is the new value a down value? | |
if(newVal<-0.5*averageDown) { | |
wasDown=true; | |
} | |
// is the new value an up value and were we previously in the down state? | |
if(newVal>=0.5*averageUp && wasDown) { | |
wasDown=false; | |
// work out the difference between now and the last time this happenned | |
if(time-periodStart<MAX_PERIOD && time-periodStart>MIN_PERIOD) { | |
periods[periodIndex]=time-periodStart; | |
periodTimes[periodIndex]=time; | |
periodIndex++; | |
if(periodIndex>=MAX_PERIODS_TO_STORE) { | |
periodIndex=0; | |
} | |
} | |
// track when the transition happened | |
periodStart=time; | |
} | |
// return up or down | |
if(newVal<-0.5*averageDown) { | |
return -1; | |
} else if(newVal>0.5*averageUp) { | |
return 1; | |
} | |
return 0; | |
} | |
-(float) getAverage { | |
double time=CACurrentMediaTime(); | |
double total=0; | |
double count=0; | |
for(int i=0; i<MAX_PERIODS_TO_STORE; i++) { | |
// only use upto 10 seconds worth of data | |
if(periods[i]!=INVALID_ENTRY && time-periodTimes[i]<10) { | |
count++; | |
total+=periods[i]; | |
} | |
} | |
// do we have enough values? | |
if(count>2) { | |
return total/count; | |
} | |
return INVALID_PULSE_PERIOD; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-(void) startCameraCapture { | |
// [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(BlinkingMethod) userInfo:nil repeats:YES]; | |
timer = [NSTimer scheduledTimerWithTimeInterval: 0.5 | |
target: self | |
selector:@selector(BlinkingMethod) | |
userInfo: nil repeats:YES]; | |
// Create the AVCapture Session | |
self.session = [[AVCaptureSession alloc] init]; | |
// Get the default camera device | |
self.camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; | |
// switch on torch mode - can't detect the pulse without it | |
if([self.camera isTorchModeSupported:AVCaptureTorchModeOn]) { | |
[self.camera lockForConfiguration:nil]; | |
self.camera.torchMode=AVCaptureTorchModeOn; | |
[self.camera unlockForConfiguration]; | |
} | |
// Create a AVCaptureInput with the camera device | |
NSError *error=nil; | |
AVCaptureInput* cameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.camera error:&error]; | |
if (cameraInput == nil) { | |
NSLog(@"Error to create camera capture:%@",error); | |
} | |
// Set the output | |
AVCaptureVideoDataOutput* videoOutput = [ | |
[AVCaptureVideoDataOutput alloc] init]; | |
// create a queue to run the capture on | |
dispatch_queue_t captureQueue= dispatch_queue_create("captureQueue", NULL); | |
// setup ourself up as the capture delegate | |
[videoOutput setSampleBufferDelegate:self queue:captureQueue]; | |
// configure the pixel format | |
videoOutput.videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA], (id)kCVPixelBufferPixelFormatTypeKey, nil]; | |
// set the minimum acceptable frame rate to 10 fps | |
videoOutput.minFrameDuration=CMTimeMake(1, 10); | |
// and the size of the frames we want - we'll use the smallest frame size available | |
[self.session setSessionPreset:AVCaptureSessionPresetLow]; | |
// Add the input and output | |
[self.session addInput:cameraInput]; | |
[self.session addOutput:videoOutput]; | |
// Start the session | |
[self.session startRunning]; | |
// we're now sampling from the camera | |
self.currentState=STATE_SAMPLING; | |
// stop the app from sleeping | |
[UIApplication sharedApplication].idleTimerDisabled = YES; | |
// update our UI on a timer every 0.1 seconds | |
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(update) userInfo:nil repeats:YES]; | |
} | |
-(void) stopCameraCapture { | |
[self.session stopRunning]; | |
self.session=nil; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment