Why AVCaptureSession output a wrong orientation? - iphone

So, I followed Apple's instructions to capture video session using AVCaptureSession: http://developer.apple.com/iphone/library/qa/qa2010/qa1702.html. One problem I'm facing is that even though the orientation of the camera / iPhone device is vertical (and the AVCaptureVideoPreviewLayer shows a vertical camera stream), the output image seems to be in the landscape mode. I checked the width and height of imageBuffer inside imageFromSampleBuffer: of the sample code, and I got 640px and 480px respectively. Does anyone know why this's the case?
Thanks!

Take a look at the header AVCaptureSession.h. There is a definition for an enum called AVCaptureVideoOrientation that defines various video orientations. On the AVCaptureConnection object there is a property called videoOrientation that is a AVCaptureVideoOrientation. You should be able to set this to change the orientation of the video. You probably want AVCaptureVideoOrientationLandscapeRight or AVCaptureVideoOrientationLandscapeLeft.
You can find the AVCaptureConnections for the session by looking at the outputs for the session. The outputs have a connections property that is an array of connections for that output.

Y'all are making this difficult.
In the DidOutputSampleBuffer, simply change the orientation before you grab the image. It's mono, but you have
public class OutputRecorder : AVCaptureVideoDataOutputSampleBufferDelegate {
public override void DidOutputSampleBuffer (AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection)
{
try {
connection.videoOrientation = AVCaptureVideoOrientation.LandscapeLeft;
in objC it's this method
- ( void ) captureOutput: ( AVCaptureOutput * ) captureOutput
didOutputSampleBuffer: ( CMSampleBufferRef ) sampleBuffer
fromConnection: ( AVCaptureConnection * ) connection

I made a simple one-line modification to the imageFromSampleBuffer to correct the orientation problem (see my comment in the code under "I modified ..."). Hope it helps someone because I spent too much time on this.
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer {
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context1 = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context1);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context1);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
//I modified this line: [UIImage imageWithCGImage:quartzImage]; to the following to correct the orientation:
UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0 orientation:UIImageOrientationRight];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}

Here is a right sequence:
AVCaptureVideoDataOutput *videoCaptureOutput = [[AVCaptureVideoDataOutput alloc] init];
if([self.captureSession canAddOutput:self.videoCaptureOutput]){
[self.captureSession addOutput:self.videoCaptureOutput];
}else{
NSLog(#"cantAddOutput");
}
// set portrait orientation
AVCaptureConnection *conn = [self.videoCaptureOutput connectionWithMediaType:AVMediaTypeVideo];
[conn setVideoOrientation:AVCaptureVideoOrientationPortrait];

For instance:
AVCaptureConnection *captureConnection = <a capture connection>;
if ([captureConnection isVideoOrientationSupported]) {
captureConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
}
The default appears to be AVCaptureVideoOrientationLandscapeRight.
See also QA1744: Setting the orientation of video with AV Foundation.

For those people that need to work with CIImage and orientation from buffer is wrong I used this correction.
As easy as that. BTW the numbers 3,1,6,8 are from here https://developer.apple.com/reference/imageio/kcgimagepropertyorientation
And don't ask me why 3,1,6,8 is the right combination. I used brute-force method to find it. If you know why let the explanation in a comment please...
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
// common way to get CIImage
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, sampleBuffer, kCMAttachmentMode_ShouldPropagate);
CIImage *ciImage = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer
options:(__bridge NSDictionary *)attachments];
if (attachments) {
CFRelease(attachments);
}
// fixing the orientation of the CIImage
UIInterfaceOrientation curOrientation = [[UIApplication sharedApplication] statusBarOrientation];
if (curOrientation == UIInterfaceOrientationLandscapeLeft){
ciImage = [ciImage imageByApplyingOrientation:3];
} else if (curOrientation == UIInterfaceOrientationLandscapeRight){
ciImage = [ciImage imageByApplyingOrientation:1];
} else if (curOrientation == UIInterfaceOrientationPortrait){
ciImage = [ciImage imageByApplyingOrientation:6];
} else if (curOrientation == UIInterfaceOrientationPortraitUpsideDown){
ciImage = [ciImage imageByApplyingOrientation:8];
}
// ....
}

If the AVCaptureVideoPreviewLayer orientation is correct, you can simply set the orientation before you capture the image.
AVCaptureStillImageOutput *stillImageOutput;
AVCaptureVideoPreviewLayer *previewLayer;
NSData *capturedImageData;
AVCaptureConnection *videoConnection = [stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
if ([videoConnection isVideoOrientationSupported]) {
[videoConnection setVideoOrientation:previewLayer.connection.videoOrientation];
}
[stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
CFDictionaryRef exifAttachments =
CMGetAttachment(imageSampleBuffer, kCGImagePropertyExifDictionary, NULL);
if (exifAttachments) {
// Do something with the attachments.
}
// TODO need to manually add GPS data to the image captured
capturedImageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
UIImage *image = [UIImage imageWithData:capturedImageData];
}];
Also, it's important to note that UIImageOrientation and AVCaptureVideoOrientation are different. UIImageOrientationUp refers to landscape mode with the volume controls down toward the ground (not up if you think about using the volume controls as a shutter button).
Thus, portrait orientation with the power button pointing to the sky (AVCaptureVideoOrientationPortrait) is actually UIImageOrientationLeft.

First of all, in the configuration of your video output, put these lines:
guard let connection = videoOutput.connection(withMediaType:
AVFoundation.AVMediaTypeVideo) else { return }
guard connection.isVideoOrientationSupported else { return }
guard connection.isVideoMirroringSupported else { return }
connection.videoOrientation = .portrait
connection.isVideoMirrored = position == .front
Then, configure your Target to support just Portait, by unchecking Landscape modes in General configuration.
(Source)

Orientation issue is with the front camera, so check device type and generate new image, it will definitely solve the orientation issue:
-(void)capture:(void(^)(UIImage *))handler{
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in self.stillImageOutput.connections)
{
for (AVCaptureInputPort *port in [connection inputPorts])
{
if ([[port mediaType] isEqual:AVMediaTypeVideo] )
{
videoConnection = connection;
break;
}
}
if (videoConnection) { break; }
}
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
if (imageSampleBuffer != NULL) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];
**UIImage *capturedImage = [UIImage imageWithData:imageData];
if (self.captureDevice == [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo][1]) {
capturedImage = [[UIImage alloc] initWithCGImage:capturedImage.CGImage scale:1.0f orientation:UIImageOrientationLeftMirrored];
}**
handler(capturedImage);
}
}];
}

// #1
AVCaptureVideoOrientation newOrientation = AVCaptureVideoOrientationLandscapeRight;
if (#available(iOS 13.0, *)) {
// #2
for (AVCaptureConnection *connection in [captureSession connections]) {
if ([connection isVideoOrientationSupported]) {
connection.videoOrientation = newOrientation;
break;
}
} // #3
} else if ([previewLayer.connection isVideoOrientationSupported]) {
previewLayer.connection.videoOrientation = newOrientation;
}
Once that you can correctly use your AVCaptureSession, you can set a video orientation.
Here a detailed description of the code above. Remember, this code has to be executed after the [captureSession startRunning] execution:
Choose the orientation that you prefer
For ios version >= 13.0 you have to retrieve the active connection from the captureSession. Remember: only video connection supports videoOrientation
For ios version < 13.0 you can use the connection from the previewLayer
If your viewController doesn't have a fixed orientation you can set a new videoOrientation to your connection once the device orientation changes.

You can try this:
private func startLiveVideo() {
let captureSession = AVCaptureSession()
captureSession.sessionPreset = .photo
let captureDevice = AVCaptureDevice.default(for: .video)
let input = try! AVCaptureDeviceInput(device: captureDevice!)
let output = AVCaptureVideoDataOutput()
captureSession.addInput(input)
captureSession.addOutput(output)
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
output.connection(with: .video)?.videoOrientation = .portrait
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
}

Related

Photo Size With UIPrintInteractionController

I have one problem to print photo using AirPrint. I printed 4 * 6 inch image but printed image size is too large! How can I resolve this problem.
Can I specify paper size and photo programmatically?
Here is screen shot url.
https://www.dropbox.com/s/1f6wa0waao56zqk/IMG_0532.jpg
` here is my code
-(void)printPhotoWithImage:(UIImage *)image
{
NSData *myData = UIImageJPEGRepresentation(image, 1.f);
UIPrintInteractionController *pic = [UIPrintInteractionController sharedPrintController];
if (pic && [UIPrintInteractionController canPrintData:myData]) {
pic.delegate = self;
UIPrintInfo *pinfo = [UIPrintInfo printInfo];
pinfo.outputType = UIPrintInfoOutputPhoto;
pinfo.jobName = #"My Photo";
pinfo.duplex = UIPrintInfoDuplexLongEdge;
pic.printInfo = pinfo;
pic.showsPageRange = YES;
pic.printingItem = myData;
pic.printFormatter = format;
[format release];
void(^completionHandler)(UIPrintInteractionController *, BOOL, NSError *) = ^(UIPrintInteractionController *print, BOOL completed, NSError *error) {
[self resignFirstResponder];
if (!completed && error) {
NSLog(#"--- print error! ---");
}
};
[pic presentFromRect:CGRectMake((self.view.bounds.size.width - 64) + 27, (self.view.bounds.size.height - 16) + 55, 0, 0) inView:self.view animated:YES completionHandler:completionHandler];
}
}
- (UIPrintPaper *)printInteractionController:(UIPrintInteractionController *)printInteractionController choosePaper:(NSArray *)paperList
{
CGSize pageSize = CGSizeMake(6 * 72, 4 * 72);
return [UIPrintPaper bestPaperForPageSize:pageSize withPapersFromArray:paperList];
}
Just this is my code. should I use UIPrintPageRenderer property to give draw area?
`
first you should set
/*
PrintPhotoPageRenderer *pageRenderer = [[PrintPhotoPageRenderer alloc]init];
pageRenderer.imageToPrint =image;
pic.printPageRenderer = pageRenderer;
*/
- (void)printImage {
// Obtain the shared UIPrintInteractionController
UIPrintInteractionController *controller = [UIPrintInteractionController sharedPrintController];
controller.delegate = self;
if(!controller){
NSLog(#"Couldn't get shared UIPrintInteractionController!");
return;
}
// We need a completion handler block for printing.
UIPrintInteractionCompletionHandler completionHandler = ^(UIPrintInteractionController *printController, BOOL completed, NSError *error) {
if(completed && error)
NSLog(#"FAILED! due to error in domain %# with error code %u", error.domain, error.code);
};
// Obtain a printInfo so that we can set our printing defaults.
UIPrintInfo *printInfo = [UIPrintInfo printInfo];
UIImage *image = ((UIImageView *)self.view).image;
[controller setDelegate:self];
printInfo.outputType = UIPrintInfoOutputPhoto;
if(!controller.printingItem && image.size.width > image.size.height)
printInfo.orientation = UIPrintInfoOrientationLandscape;
// Use this printInfo for this print job.
controller.printInfo = printInfo;
// Since the code below relies on printingItem being zero if it hasn't
// already been set, this code sets it to nil.
controller.printingItem = nil;
#if DIRECT_SUBMISSION
// Use the URL of the image asset.
if(self.imageURL && [UIPrintInteractionController canPrintURL:self.imageURL])
controller.printingItem = self.imageURL;
#endif
// If we aren't doing direct submission of the image or for some reason we don't
// have an ALAsset or URL for our image, we'll draw it instead.
if(!controller.printingItem){
// Create an instance of our PrintPhotoPageRenderer class for use as the
// printPageRenderer for the print job.
PrintPhotoPageRenderer *pageRenderer = [[PrintPhotoPageRenderer alloc]init];
// The PrintPhotoPageRenderer subclass needs the image to draw. If we were taking
// this path we use the original image and not the fullScreenImage we obtained from
// the ALAssetRepresentation.
//pageRenderer.imageToPrint = ((UIImageView *)self.view).image;
pageRenderer.imageToPrint =image;
controller.printPageRenderer = pageRenderer;
}
// The method we use presenting the printing UI depends on the type of
// UI idiom that is currently executing. Once we invoke one of these methods
// to present the printing UI, our application's direct involvement in printing
// is complete. Our delegate methods (if any) and page renderer methods (if any)
// are invoked by UIKit.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
//[controller presentFromBarButtonItem:self.printButton animated:YES completionHandler:completionHandler]; // iPad
[controller presentFromRect:CGRectMake(0, 0, 50, 50) inView:_btnPrint animated:YES completionHandler:completionHandler];
}else
[controller presentAnimated:YES completionHandler:completionHandler]; // iPhone
}
and then you should set PrintPhotoPageRenderer
UIPrintPageRenderer.h
#import <UIKit/UIKit.h>
#interface PrintPhotoPageRenderer : UIPrintPageRenderer { UIImage
*imageToPrint; }
#property (readwrite, retain) UIImage *imageToPrint;
#end
//
PrintPhotoPageRenderer.m
#import "PrintPhotoPageRenderer.h"
#implementation PrintPhotoPageRenderer
#synthesize imageToPrint;
// This code always draws one image at print time.
-(NSInteger)numberOfPages { return 1; }
/* When using this UIPrintPageRenderer subclass to draw a photo at
print
time, the app explicitly draws all the content and need only override
the drawPageAtIndex:inRect: to accomplish that.
The following scaling algorithm is implemented here:
1) On borderless paper, users expect to see their content scaled so that there is no whitespace at the edge of the paper. So this
code scales the content to fill the paper at the expense of
clipping any content that lies off the paper.
2) On paper which is not borderless, this code scales the content so that it fills the paper. This reduces the size of the
photo but does not clip any content.
*/
- (void)drawPageAtIndex:(NSInteger)pageIndex inRect:(CGRect)printableRect {
if(self.imageToPrint){
CGSize finialSize = CGSizeMake(560, 431);//you should set width and height for you self
int x = 20;
int y = (printableRect.size.height - finialSize.height);
CGRect finalRect = CGRectMake(x, y, finialSize.width, finialSize.height);
[self.imageToPrint drawInRect:finalRect];
}else {
NSLog(#"%s No image to draw!", __func__); } }
#end

Using UIImageOrientation to Save UIImage Correctly in Landscape Mode

I am receiving my UIImage's from AVCaptureSession and setting them into UIImageView's. The portrait mode sets images with correct orientation but once I use landscape mode the images are set rotated 90 degrees clockwise.
I did a little research and discovered that I can use UIImageOrientation with this method to resolve my issue:
[UIImage imageWithCGImage:cgimageref scale:1 orientation:orientation]
But I do not understand how. If I look at UIImageOrientation on all images, the value is always 3 (NSLog(#"%d",UIImageOrientation)) in both portrait and landscape modes. I am puzzled about how this can help me. Am I missing something?
Here is orientation code:
-(void) didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
self.prevLayer.frame = self.view.bounds;
self.prevLayer.orientation = [[UIDevice currentDevice] orientation];
}
shouldAutorotateToInterfaceOrientation returns YES.
The solution took some time to find. I had to add orientation to AVCaptureConnection
AVCaptureConnection *videoConnection = nil;
for (AVCaptureConnection *connection in stillImageOutput.connections){
for (AVCaptureInputPort *port in [connection inputPorts]){
if ([[port mediaType] isEqual:AVMediaTypeVideo] ){
videoConnection = connection;
break;
}
}
if (videoConnection) { break; }
}
This line below is the fix:
if([videoConnection isVideoOrientationSupported]) {
[videoConnection setVideoOrientation:[[UIDevice currentDevice] orientation]];
}
And all images work right with any orientation.

Always seeing Mirror image while capturing from Front Camera iOS 5.0

While capturing data from Front camera I am always getting Mirror image, how can I get what I am seeing in my preview window. I have set videoMirrored to be TRUE. Following is the code snippet:
AVCaptureConnection *lConnection = nil;
for ( AVCaptureConnection *connection in [lHandle->m_output connections]) {
for ( AVCaptureInputPort *port in [connection inputPorts] ) {
if ( [[port mediaType] isEqual:AVMediaTypeVideo] ) {
lConnection = connection;
break;
}
}
}
if ([lConnection isVideoOrientationSupported])
[lConnection setVideoOrientation:AVCaptureVideoOrientationPortrait];
if ([lConnection isVideoMirroringSupported])
[lConnection setVideoMirrored:TRUE];
Changing setVideoMirrored to True/False also doesn't change anything
(isVideoMirroringSupported returns Success)
Flip the mirrored image with the following code.
UIImage * flippedImage = [UIImage imageWithCGImage:picture.CGImage scale:picture.scale orientation:UIImageOrientationLeftMirrored];
Swift version:
let flippedImage = UIImage(CGImage: image.CGImage, scale: image.scale, orientation: .LeftMirrored)
You can use the withHorizontallyFlippedOrientation() to flip an UIImage.
// first get an UIImage from the front camera
let sourceImg = UIImage(cgImage: image, scale: 1.0, orientation: .leftMirrored)
return sourceImg.withHorizontallyFlippedOrientation()

AVCapture appendSampleBuffer

I am going insane with this one - have looked everywhere and tried anything and everything I can thinks of.
Am making an iPhone app that uses AVFoundation - specifically AVCapture to capture video using the iPhone camera.
I need to have a custom image that is overlayed on the video feed included in the recording.
So far I have the AVCapture session set up, can display the feed, access the frame, save it as a UIImage and marge the overlay Image onto it. Then convert this new UIImage into a CVPixelBufferRef. annnd to double check that the bufferRef is working I converted it back to a UIImage and it displays the image fine still.
The trouble starts when I try to convert the CVPixelBufferRef into a CMSampleBufferRef to append to the AVCaptureSessions assetWriterInput. The CMSampleBufferRef always returning NULL when I attempt to create it.
Here is the -(void)captureOutput function
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
UIImage *botImage = [self imageFromSampleBuffer:sampleBuffer];
UIImage *wheel = [self imageFromView:wheelView];
UIImage *finalImage = [self overlaidImage:botImage :wheel];
//[previewImage setImage:finalImage]; <- works -- the image is being merged into one UIImage
CVPixelBufferRef pixelBuffer = NULL;
CGImageRef cgImage = CGImageCreateCopy(finalImage.CGImage);
CFDataRef image = CGDataProviderCopyData(CGImageGetDataProvider(cgImage));
int status = CVPixelBufferCreateWithBytes(NULL,
self.view.bounds.size.width,
self.view.bounds.size.height,
kCVPixelFormatType_32BGRA,
(void*)CFDataGetBytePtr(image),
CGImageGetBytesPerRow(cgImage),
NULL,
0,
NULL,
&pixelBuffer);
if(status == 0){
OSStatus result = 0;
CMVideoFormatDescriptionRef videoInfo = NULL;
result = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixelBuffer, &videoInfo);
NSParameterAssert(result == 0 && videoInfo != NULL);
CMSampleBufferRef myBuffer = NULL;
result = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,
pixelBuffer, true, NULL, NULL, videoInfo, NULL, &myBuffer);
NSParameterAssert(result == 0 && myBuffer != NULL);//always null :S
NSLog(#"Trying to append");
if (!CMSampleBufferDataIsReady(myBuffer)){
NSLog(#"sampleBuffer data is not ready");
return;
}
if (![assetWriterInput isReadyForMoreMediaData]){
NSLog(#"Not ready for data :(");
return;
}
if (![assetWriterInput appendSampleBuffer:myBuffer]){
NSLog(#"Failed to append pixel buffer");
}
}
}
Another solution I keep hearing about is using a AVAssetWriterInputPixelBufferAdaptor which eliminates the need to do the messy CMSampleBufferRef wrapping. However I have scoured stacked and apple developer forums and docs and can't find a clear description or example on how to set this up or how to use it. If anyone has a working example of it could you please show me or help me nut out the above issue - have been working on this non-stop for a week and am at wits end.
Let me know if you need any other info
Thanks in advance,
Michael
You need AVAssetWriterInputPixelBufferAdaptor, here is the code to create it :
// Create dictionary for pixel buffer adaptor
NSDictionary *bufferAttributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey, nil];
// Create pixel buffer adaptor
m_pixelsBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] initWithAssetWriterInput:assetWriterInput sourcePixelBufferAttributes:bufferAttributes];
And the code to use it :
// If ready to have more media data
if (m_pixelsBufferAdaptor.assetWriterInput.readyForMoreMediaData) {
// Create a pixel buffer
CVPixelBufferRef pixelsBuffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(NULL, m_pixelsBufferAdaptor.pixelBufferPool, &pixelsBuffer);
// Lock pixel buffer address
CVPixelBufferLockBaseAddress(pixelsBuffer, 0);
// Create your function to set your pixels data in the buffer (in your case, fill with your finalImage data)
[self yourFunctionToPutDataInPixelBuffer:CVPixelBufferGetBaseAddress(pixelsBuffer)];
// Unlock pixel buffer address
CVPixelBufferUnlockBaseAddress(pixelsBuffer, 0);
// Append pixel buffer (calculate currentFrameTime with your needing, the most simplest way is to have a frame time starting at 0 and increment each time you write a frame with the time of a frame (inverse of your framerate))
[m_pixelsBufferAdaptor appendPixelBuffer:pixelsBuffer withPresentationTime:currentFrameTime];
// Release pixel buffer
CVPixelBufferRelease(pixelsBuffer);
}
And don't forget to release your pixelsBufferAdaptor.
I do it by using CMSampleBufferCreateForImageBuffer() .
OSStatus ret = 0;
CMSampleBufferRef sample = NULL;
CMVideoFormatDescriptionRef videoInfo = NULL;
CMSampleTimingInfo timingInfo = kCMTimingInfoInvalid;
timingInfo.presentationTimeStamp = pts;
timingInfo.duration = duration;
ret = CMVideoFormatDescriptionCreateForImageBuffer(NULL, pixel, &videoInfo);
if (ret != 0) {
NSLog(#"CMVideoFormatDescriptionCreateForImageBuffer failed! %d", (int)ret);
goto done;
}
ret = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, pixel, true, NULL, NULL,
videoInfo, &timingInfo, &sample);
if (ret != 0) {
NSLog(#"CMSampleBufferCreateForImageBuffer failed! %d", (int)ret);
goto done;
}

AVCaptureSession only getting one frame for iPhone 3gs

I have a piece of code that sets up a capture session from the camera to process the frames using OpenCV and then set the image property of a UIImageView with a generated UIImage from the frame. When the app starts, the image view's image is nil and no frames show up until I push another view controller on the stack and then pop it off. Then the image stays the same until I do it again. NSLog statements show that the callback is called at approximately the correct frame rate. Any ideas why it doesn't show up? I reduced the framerate all the way to 2 frames a second. Is it not processing fast enough?
Here's the code:
- (void)setupCaptureSession {
NSError *error = nil;
// Create the session
AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Configure the session to produce lower resolution video frames, if your
// processing algorithm can cope. We'll specify medium quality for the
// chosen device.
session.sessionPreset = AVCaptureSessionPresetLow;
// Find a suitable AVCaptureDevice
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// Create a device input with the device and add it to the session.
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device
error:&error];
if (!input) {
// Handling the error appropriately.
}
[session addInput:input];
// Create a VideoDataOutput and add it to the session
AVCaptureVideoDataOutput *output = [[[AVCaptureVideoDataOutput alloc] init] autorelease];
output.alwaysDiscardsLateVideoFrames = YES;
[session addOutput:output];
// Configure your output.
dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
[output setSampleBufferDelegate:self queue:queue];
dispatch_release(queue);
// Specify the pixel format
output.videoSettings =
[NSDictionary dictionaryWithObject:
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
forKey:(id)kCVPixelBufferPixelFormatTypeKey];
// If you wish to cap the frame rate to a known value, such as 15 fps, set
// minFrameDuration.
output.minFrameDuration = CMTimeMake(1, 1);
// Start the session running to start the flow of data
[session startRunning];
// Assign session to an ivar.
[self setSession:session];
}
// Create a UIImage from sample buffer data
- (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer {
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer,0);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (!colorSpace)
{
NSLog(#"CGColorSpaceCreateDeviceRGB failure");
return nil;
}
// Get the base address of the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the data size for contiguous planes of the pixel buffer.
size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
// Create a Quartz direct-access data provider that uses data we supply
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, baseAddress, bufferSize,
NULL);
// Create a bitmap image from data supplied by our data provider
CGImageRef cgImage =
CGImageCreate(width,
height,
8,
32,
bytesPerRow,
colorSpace,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little,
provider,
NULL,
true,
kCGRenderingIntentDefault);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
// Create and return an image object representing the specified Quartz image
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return image;
}
// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection {
// Create a UIImage from the sample buffer data
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
[self.delegate cameraCaptureGotFrame:image];
}
This could be related to threading—Try:
[self.delegate performSelectorOnMainThread:#selector(cameraCaptureGotFrame:) withObject:image waitUntilDone:NO];
This looks like a threading issue. You cannot update your views in any other thread than in the main thread. In your setup, which is good, the delegate function captureOutput:didOutputSampleBuffer: is called in a secondary thread. So you cannot set the image view from there. Art Gillespie's answer is one way of solving it if you can get rid of the bad access error.
Another way is to modify the sample buffer in captureOutput:didOutputSampleBuffer: and have is shown by adding a AVCaptureVideoPreviewLayer instance to your capture session. That's certainly the preferred way if you only modify a small part of the image such as highlighting something.
BTW: Your bad access error could arise because you don't retain the created image in the secondary thread and so it will be freed before cameraCaptureGotFrame is called on the main thread.
Update:
To properly retain the image, increase the reference count in captureOutput:didOutputSampleBuffer: (in the secondary thread) and decrement it in cameraCaptureGotFrame: (in the main thread).
// Delegate routine that is called when a sample buffer was written
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
// Create a UIImage from the sample buffer data
UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
// increment ref count
[image retain];
[self.delegate performSelectorOnMainThread:#selector(cameraCaptureGotFrame:)
withObject:image waitUntilDone:NO];
}
- (void) cameraCaptureGotFrame:(UIImage*)image
{
// whatever this function does, e.g.:
imageView.image = image;
// decrement ref count
[image release];
}
If you don't increment the reference count, the image is freed by the auto release pool of the second thread before the cameraCaptureGotFrame: is called in the main thread. If you don't decrement it in the main thread, the images are never freed and you run out of memory within a few seconds.
Are you doing a setNeedsDisplay on the UIImageView after each new image property update?
Edit:
Where and when are you updating the background image property in your image view?