CVPixelBufferLockBaseAddress why? Capture still image using AVFoundation - iphone

I'm writing an iPhone app that creates still images from the camera using AVFoundation.
Reading the programming guide I've found a code that does almost I need to do, so I'm trying to "reverse engineering" and understand it.
I'm founding some difficulties to understand the part that converts a CMSampleBuffer into an image.
So here is what I understood and later the code.
The CMSampleBuffer represent a buffer in the memory where the image with additional data is stored. Later I call the function CMSampleBufferGetImageBuffer() to receive a CVImageBuffer back with just the image data.
Now there is a function that I didn't understand and I can only imagine its function: CVPixelBufferLockBaseAddress(imageBuffer, 0); I can't understand if it is a "thread lock" to avoid multiple operation on it or a lock to the address of the buffer to avoid changes during operation(and why should it change?..another frame, aren't data copied in another location?). The rest of the code it's clear to me.
Tried to search on google but still didn't find nothing helpful.
Can someone bring some light?
-(UIImage*) getUIImageFromBuffer:(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);
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 context = 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(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}
Thanks,
Andrea

The header file says that CVPixelBufferLockBaseAddress makes the memory "accessible". I'm not sure what that means exactly, but if you don't do it, CVPixelBufferGetBaseAddress fails so you'd better do it.
EDIT
Just do it is the short answer. For why consider that image may not live in main memory, it may live in a texture on some GPU somewhere (CoreVideo works on the mac too) or even be in a different format to what you expect, so the pixels you get are actually a copy. Without Lock/Unlock or some kind of Begin/End pair the implementation has no way to know when you've finished with the duplicate pixels so they would effectively be leaked. CVPixelBufferLockBaseAddress simply gives CoreVideo scope information, I wouldn't get too hung up on it.
Yes, they could have simply returned the pixels from CVPixelBufferGetBaseAddress and eliminate CVPixelBufferLockBaseAddress altogether. I don't know why they didn't do that.

I'd like to give more hints about this function, I made some tests so far and I can tell you that. When you get the base address you are probably getting the address of some shared memory resource. This becomes clear if you print the address of the base address, doing that you can see that base addresses are repeated while getting video frames. In my app I take frames at specific intervals and pass the CVImageBufferRef to an NSOperation subclass that converts the buffer in an image and saves it on the phone. I do not lock the pixel buffer until the operation starts to convert the CVImageBufferRef, even if pushing at higher framerates the base address of the pixel and the CVImageBufferRef buffer address are equal before the creation of the NSOperation and inside it. I just retain the CVImageBufferRef. I was expecting to se unmatching references and even if I didn't see it I guess that the best description is that CVPixelBufferLockBaseAddress locks the memory portion where the buffer is located, making it inaccessible from other resources so it will keep the same data, until you unlock it.

Related

Memory consumption spikes when resizing, rotating and cropping images on iPhone

I have an "ImageManipulator" class that performs some cropping, resizing and rotating of camera images on the iPhone.
At the moment, everything works as expected but I keep getting a few huge spikes in memory consumption which occasionally cause the app to crash.
I have managed to isolate the problem to a part of the code where I check for the current image orientation property and rotate it accordingly to UIImageOrientationUp. I then get the image from the bitmap context and save it to disk.
This is currently what I am doing:
CGAffineTransform transform = CGAffineTransformIdentity;
// Check for orientation and set transform accordingly...
transform = CGAffineTransformTranslate(transform, self.size.width, 0);
transform = CGAffineTransformScale(transform, -1, 1);
// Create a bitmap context with the image that was passed so we can perform the rotation
CGContextRef ctx = CGBitmapContextCreate(NULL, self.size.width, self.size.height,
CGImageGetBitsPerComponent(self.CGImage), 0,
CGImageGetColorSpace(self.CGImage),
CGImageGetBitmapInfo(self.CGImage));
// Rotate the context
CGContextConcatCTM(ctx, transform);
// Draw the image into the context
CGContextDrawImage(ctx, CGRectMake(0,0,self.size.height,self.size.width), self.CGImage);
// Grab the bitmap context and save it to the disk...
Even after trying to scale the image down to half or even 1/4 of the size, I am still seeing the spikes to I am wondering if there is a different / more efficient way to get the rotation done as above?
Thanks in advance for the replies.
Rog
If you are saving to JPEG, I guess an alternative approach is to save the image as-is and then set the rotation to whatever you'd like by manipulating the EXIF metadata? See for example this post. Simple but probably effective, even if you have to hold the image payload bytes in memory ;)
Things you can do:
Scale down the image even more (which you probably don't want)
Remember to release everything as soon as you finish with it
Live with it
I would choose option 2 and 3.
Image editing is very resource intensive, as it loads the entire raw uncompressed image data into the memory for processing. This is inevitable as there is absolutely no other way to modify an image other than to load the complete raw data into the memory. Having memory consumption spikes doesn't really matter unless the app receives a memory warning, in that case quickly get rid of everything before it crashes. It is very rare that you would get a memory warning, though, because my app regularly loads a single > 10 mb file into the memory and I don't get a warning, even on older devices. So you'll be fine with the spikes.
Have you tried checking for memory leaks and analyzing allocations?
If the image is still too big, try rotating the image in pieces instead of as a whole.
As Anomie mentioned, CGBitmapContextCreate creates a context. We should release that by using
CGContextRelease(ctx);
If you have any other objects created using create or copy, that should also be released. If it is CFData, then
CFRelease(cfdata);

Low FPS when accesing iPhone video output image buffer

I'm trying to do some image processing on iPhone. I'm using http://developer.apple.com/library/ios/#qa/qa2010/qa1702.html to capture the camera frames.
My problem is that when I'm trying to access the captured buffer, the camera FPS drops from 30 to about 20. Does anybody knows how I can fix it?
I use the lowest capture quality I could find (AVCaptureSessionPresetLow = 192x144) in kCVPixelFormatType_32BGRA format. If anybody knows a lower quality I could use, I'm willing to try it.
When I do the same image access on other platforms, like Symbian, it works OK.
Here is my code:
#pragma mark -
#pragma mark AVCaptureSession delegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
{
/*We create an autorelease pool because as we are not in the main_queue our code is
not executed in the main thread. So we have to create an autorelease pool for the thread we are in*/
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
//Lock the image buffer
if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess)
{
// calculate FPS and display it using main thread
[self performSelectorOnMainThread:#selector(updateFps:) withObject: (id) nil waitUntilDone:NO];
UInt8 *base = (UInt8 *)CVPixelBufferGetBaseAddress(imageBuffer); //image buffer start address
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
int size = (height*width);
UInt8* pRGBtmp = m_pRGBimage;
/*
Here is the problem; m_pRGBimage is RGB image I want to process.
In the 'for' loop I convert the image from BGRA to RGB. As a resault, the FPS drops to 20.
*/
for (int i=0;i<size;i++)
{
pRGBtmp[0] = base[2];
pRGBtmp[1] = base[1];
pRGBtmp[2] = base[0];
base = base+4;
pRGBtmp = pRGBtmp+3;
}
// Display received action
[self performSelectorOnMainThread:#selector(displayAction:) withObject: (id) nil waitUntilDone:NO];
//[self displayAction:&eyePlayOutput];
//saveFrame( imageBuffer );
//unlock the image buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
}
[pool drain];
}
As a follow-on to the answers, I need to process the image in realtime, it is being displayed.
I noticed that when I use AVCaptureSessionPresetHigh, the most simple thing I do, like:
for (int i=0;i<size;i++)
x = base[0];
causes the framerate to drop to 4-5 FPS. I guess its because an image in that size is not cached.
Basically I need 96x48 image. is there a simple way to downscale the camera output image, a way that uses hardware acceleration, so I could work with the small one?
Anything that iterates over every pixel in an image will be fairly slow on all but the fastest iOS devices. For example, I benchmarked iterating over every pixel in a 640 x 480 video frame (307,200 pixels) with a simple per-pixel color test and found that this only runs at around 4 FPS on an iPhone 4.
You're looking at processing 27,648 pixels in your case, which should run fast enough to hit 30 FPS on an iPhone 4, but that's a much faster processor than what was in the original iPhone and iPhone 3G. The iPhone 3G will probably still struggle with this processing load. You also don't say how fast the processor was in your Symbian devices.
I'd suggest reworking your processing algorithm to avoid the colorspace conversion. There should be no need to reorder the color components in order to process them.
Additionally, you could selectively process only a few pixels by sampling at certain intervals within the rows and columns of the image.
Finally, if you are targeting the newer iOS devices that have support for OpenGL ES 2.0 (iPhone 3G S and newer), you might want to look at using a GLSL fragment shader to process the video frame entirely on the GPU. I describe the process here, along with sample code for realtime color-based object tracking. The GPU can handle this kind of processing 14 - 28 times faster than the CPU, in my benchmarks.
disclaimer: THIS ANSWER IS A GUESS :)
You're doing quite a lot of work while the buffer is locked; is this holding up the thread that is capturing the image from the camera?
You could you copy the data out of the buffer while you work on it so you can unlock it asap i.e. something like
if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess) {
// Get the base address and size of the buffer
UInt8 *buffer_base = (UInt8 *)CVPixelBufferGetBaseAddress(imageBuffer); //image buffer start address
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Copy it's contents out
Uint8 *base = malloc(width * height * 4);
memcpy(base, buffer_base, size);
// Unlock the buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// base now points to a copy of the buffers' data - do what you want to it . . .
...
// remember to free base once you're done ;)
free(base);
If it's the lock that's holding up the capture then this should help.
NB You could speed this up if you know all the buffers will be the same size you can just call malloc once to get the memory and then just reuse it each time and only free it when you have finished processing all the buffers.
Or if that's not the problem you could try lowering the priority of this thread
[NSThread setThreadPriority:0.25];
Copy the contents of the camera frame into a dedicated buffer and operate on it from there. This results in a massive speed improvement in my experience. My best guess is that the region of memory where the camera frame is located has special protections that make reading/writing accesses slow.
Check out the memory address of the camera frame data. On my device the camera buffer is at 0x63ac000. That doesn't mean anything to me, except that the other heap objects are in addresses closer to 0x1300000. The lock suggestion did not solve my slowdown, but the memcpy did.

Drawing imagedata from data set of integers on an iPad using OpenGL

I'm trying to draw an image using OpenGL in a project for iPad.
The image data:
A data blob of UInt8 that represents the grayscale value for each pixel in three dimensions (I'm going to draw slices from the 3D-body). I also have information on height and width for the image.
My current (unsuccessful) approach is to use it as a texture on a square and I am looking at some example code I found on the net. That code, however, loads an image file from the disc.
While setting up the view there is a call to CGContextDrawImage and the last parameter is suppose to be an CGImageRef. Do you know how I can create one from my data or is this a dead end?
Thankful for all input. I really haven't gotten the grip of OpenGL yet so please be gentle :-)
It's not a dead end.
You can create an CGImageRef from a blob of pixel memory using CGBitmapContextCreate() to create a bitmap context and CGBitmapContextCreateImage() to create the image ref from the bitmap context.

How to convert .jpg image to .bmp format using Objective C?

Anyone knows how to convert .jpg image to .bmp format in iphone using objective-C?
And how i process(or RGB color) the each pixel of IPhone Device caputured image?
Is there need to conversion of image type?
You won't be able to easily get to a bmp representation on the iPhone. In Cocoa on the Mac, it is managed by the NSBitmapImageRep class and is pretty straight forward as outlined below.
At a high level, you need to get the .jpg into an NSBitmapImageRep object and then let the frameworks handle the conversion for you:
a. Convert the JPG image to an NSBitmapImageRep
b. Use built in NSBitmapImageRep methods to save in desired formats.
NSBitmapImageRep *origImage = [self documentAsBitmapImageRep:[NSURL fileURLWithPath:pathToJpgImage]];
NSBitmapImageRep *bmpImage = [origImage representationUsingType:NSBMPFileType properties:nil];
- (NSBitmapImageRep*)documentAsBitmapImageRep:(NSURL*)urlOfJpg;
{
CIImage *anImage = [CIImage imageWithContentsOfURL:urlOfJpg];
CGRect outputExtent = [anImage extent];
// Create a new NSBitmapImageRep.
NSBitmapImageRep *theBitMapToBeSaved = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL pixelsWide:outputExtent.size.width
pixelsHigh:outputExtent.size.height bitsPerSample:8 samplesPerPixel:4
hasAlpha:YES isPlanar:NO colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:0 bitsPerPixel:0];
// Create an NSGraphicsContext that draws into the NSBitmapImageRep.
NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:theBitMapToBeSaved];
// Save the previous graphics context and state, and make our bitmap context current.
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext: nsContext];
CGPoint p = CGPointMake(0.0, 0.0);
// Get a CIContext from the NSGraphicsContext, and use it to draw the CIImage into the NSBitmapImageRep.
[[nsContext CIContext] drawImage:anImage atPoint:p fromRect:outputExtent];
// Restore the previous graphics context and state.
[NSGraphicsContext restoreGraphicsState];
return [[theBitMapToBeSaved retain] autorelease];
}
On the iPhone, BMP is not directly supported by UIKit, so you would have to drop down into Quartz/Core Graphics and manage the transformation yourself.
Pixel by pixel processing is much more involved. Again, you should get intimately familiar with the core graphics capabilities on the device if this is a hard requirement for you.
Load the JPG image into a UIImage, which it can handle natively.
Then you can grab the CGImageRef from the UIImage object.
Create a new bitmap CG image context with the same properties of the image you already have, and provide your own data buffer to hold the bytes of the bitmap context.
Draw the original image into the new bitmap context: the bytes in your provided buffer are now the image's pixels.
Now you need to encode the actual BMP file, which isn't a functionality that exists in the UIKit or CoreGraphics (as far as I know) frameworks. Fortunately, it's an intentionally trivial format-- I've written quick and dirty encoders for BMP in an hour or less. Here's the spec: http://www.fileformat.info/format/bmp/egff.htm (Version 3 should be fine, unless you need to support alpha, but coming from JPEG you probably don't.)
Good luck.

How to push pixels faster on the iPhone?

I asked before about pixel-pushing, and have now managed to get far enough to get noise to show up on the screen. Here's how I init:
CGDataProviderRef provider;
bitmap = malloc(320*480*4);
provider = CGDataProviderCreateWithData(NULL, bitmap, 320*480*4, NULL);
CGColorSpaceRef colorSpaceRef;
colorSpaceRef = CGColorSpaceCreateDeviceRGB();
ir = CGImageCreate(
320,
480,
8,
32,
4 * 320,
colorSpaceRef,
kCGImageAlphaNoneSkipLast,
provider,
NULL,
NO,
kCGRenderingIntentDefault
);
Here's how I render each frame:
for (int i=0; i<320*480*4; i++) {
bitmap[i] = rand()%256;
}
CGRect rect = CGRectMake(0, 0, 320, 480);
CGContextDrawImage(context, rect, ir);
Problem is this is awfully awfully slow, around 5fps. I think my path to publish the buffer must be wrong. Is it even possible to do full-screen pixel-based graphics that I could update at 30fps, without using the 3D chip?
The slowness is almost certainly in the noise generation. If you run this in Instruments you'll probably see that a ton of time is spent sitting in your loop.
Another smaller issue is your colorspace. If you use the screen's colorspace, you'll avoid a colorspace conversion which is potentially expensive.
If you can use CoreGraphics routines for your drawing, you'd be better served by creating a CGLayer for the drawing context instead of creating a new object each time.
The bytesPerRow component is also important for performance. It should be a factor of 32 IIRC. There's some code available link text that shows how to compute it.
And yeah, for raw performance, OpenGL.
I suspect doing 614400 (320*480*4) memory writes, random number generation and making a new object each frame is slowing you down.
Have you tried just writing a static bitmap to screen and seeing how fast that is? Have you perhaps tried profiling the code? Do you also need to make a new CGRect each time?
If you just want to give the effect of randomness, there is probably no need to regenerate the entire bitmap each time.
To my knowledge, OpenGL is supposed to be the fastest way to do graphics on the iPhone. This includes 2D and 3D. A UIView is backed by a core animation layer, which ends up drawing with OpenGL anyway. So why not skip the middle-man.
You can avoid the trip through CGContextDrawImage by assigning your CGImageRef to -[CALayer setContents:], just be sure not to free bitmap while you're still using it.
[[view layer] setContents:(id)ir];
Yes, I know this is old, I stumbled upon it from Google