I have a method that returns the data of an ALAsset. I call this method for one asset after the other to get NSData from them and upload it to a server. After every 4 or 5 calls, the code gets stuck on rep.size call below. When I pause and resume execution in XCode it starts to work again. I am completely stumped, any help would be appreciated.
Clarification: The deadlock/code gets stuck inside the ALAssetsLibrary code, not inside my code.
Additional info: I have just one instance of ALAssetsLibrary and I am making sure it is not being used from any other thread.
+(void)getDataFromAssetURL:(NSString *) myImageURL ofType:(enum ImageType)imageType andPerformBlock:(NSDataBlock)block blocking:(BOOL)blocking
{
NSConditionLock * albumReadLock = nil;
if (blocking) {
albumReadLock = [[NSConditionLock alloc] initWithCondition:PENDING];
}
#autoreleasepool {
#try {
NSURL *str = [[NSURL alloc] initWithString: myImageURL];
ALAsset * asset = [[AppManager sharedInstance].assetObjectCache objectForKey:str];
if (asset && NO) {
block( [Util getDataFromAsset:asset ofType:imageType] );
// notifies the lock that "all tasks are finished"
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
}
else {
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
#autoreleasepool {
#try {
[[AppManager sharedInstance].assetObjectCache setObject:myasset forKey:str];
NSData * assetData = [Util getDataFromAsset:myasset ofType:imageType];
block(assetData);
// notifies the lock that "all tasks are finished"
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
}
#catch (NSException *exception) {
block(nil);
// important: notifies lock that "all tasks finished" (even though they failed)
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
}
#finally {
}
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
block(nil);
// important: notifies lock that "all tasks finished" (even though they failed)
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
};
if(str)
{
NSURL *asseturl = str;
[[AppManager sharedInstance].assetslibrary assetForURL:asseturl
resultBlock:resultblock
failureBlock:failureblock];
}
else {
block(nil);
// notifies the lock that "all tasks are finished"
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
}
}
}
#catch (NSException *exception) {
block(nil);
// notifies the lock that "all tasks are finished"
[albumReadLock lock];
[albumReadLock unlockWithCondition:ALLFINISHED];
}
#finally {
}
}
if (blocking) {
// non-busy wait for the asset read to finish (specifically until the condition is "all finished")
[albumReadLock lockWhenCondition:ALLFINISHED];
[albumReadLock unlock];
}
}
+(NSData *)getDataFromAsset:(ALAsset *)myasset ofType:(enum ImageType)imageType
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = nil;
NSData *assetData = nil;
if (imageType == FULL_RESOLUTION) {
Byte *buffer = (Byte*)malloc(rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
assetData = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
}
else if (imageType == LARGE_IMAGE){
iref = [rep fullScreenImage];
UIImage * uiimage = [UIImage imageWithCGImage:iref];
////NSLog(#"%f %f", uiimage.size.width, uiimage.size.height);
assetData = UIImageJPEGRepresentation(uiimage, 1.0f);
}
else if (imageType == SQUARE_THUMB){
iref = [myasset thumbnail];
assetData = UIImageJPEGRepresentation([UIImage imageWithCGImage:iref], 1.0f);
}
else if (imageType == PERSPECTIVE_THUMB){
iref = [myasset aspectRatioThumbnail];
assetData = UIImageJPEGRepresentation([UIImage imageWithCGImage:iref], 1.0f);
}
return assetData;
}
New information: Turns out the problem only occurs on a specific device. As stated above, if I pause and un-pause the debugger in XCode the code moves forward.
You will cause a deadlock in the a ALAsset code if you initiate this block of code from the main thread. You must first drop onto a background thread because the ALAssetsLibrary requires the ability to execute code on the main thread, and if the main thread is blocked in the manner that you have outline above then it will be waiting forever to do so.
Related
I have fetched the images and videos from gallery, it works fine when the number of videos and images is minimum,but when the number of images and videos is large, it blocks the main thread.
-(void)viewDidAppear:(BOOL)animated{
[NSThread sleepForTimeInterval:1];
// call the same method on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getAllVideosAndImages];
});
}
To gat Images and Videos From Asset
-(void)getAllVideosAndImages{
NSMutableArray *assetGroups = [[NSMutableArray alloc] init];
assetGroups = [[NSMutableArray alloc] init];
library = [[ALAssetsLibrary alloc] init];
NSUInteger groupTypes = ALAssetsGroupAll;
void (^assetEnumerator)
( ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result != nil) {
if([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]||[[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
ALAssetRepresentation *rep = [result defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
//GET DATE
NSDate *myDate = [result valueForProperty:ALAssetPropertyDate];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMM dd,YYYY"];
NSString *realDateStr = [dateFormatter stringFromDate:myDate];
//GET IMG AND VIDEO
UIImage *largeimage = [UIImage imageWithCGImage:iref];
NSData *webData = UIImagePNGRepresentation(largeimage);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"img%zd.mov",imgName]];
imgName=imgName+1;
[webData writeToFile:localFilePath atomically:YES];
Para_ImageSet *objImgSet=[Para_ImageSet new];
objImgSet.imagePath=localFilePath;
objImgSet.imageDate=realDateStr;
// [yArrayOfAllImges addObject:objImgSet];
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) {
objImgSet.is_video=YES;
}
else
{
objImgSet.is_video=NO;
}
if ([tempDate isEqualToString:#""]) {
yArrayOfImagesWithDate=[NSMutableArray new];
[yArrayOfImagesWithDate addObject:objImgSet];
tempDate=realDateStr;
}else
{
//GATE IMAGE COUNT
if ([realDateStr isEqualToString:tempDate]) {
//DO NOTHING
[yArrayOfImagesWithDate addObject:objImgSet];
}
else
{
[yArrayOFNNestedImgs addObject:yArrayOfImagesWithDate];
NSLog(#" ----------ImagesWithDate =%zd, %#",yArrayOfImagesWithDate.count,tempDate);
yArrayOfImagesWithDate=[NSMutableArray new];
[yArrayOfImagesWithDate addObject:objImgSet];
tempDate=realDateStr;
}
}
}
}
}
[self.tableView reloadData];
};
void (^ assetGroupEnumerator) ( ALAssetsGroup *, BOOL *)= ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
[assetGroups addObject:group];
// NSNumber *countObj = [NSNumber numberWithInt:count];
NSLog(#" ----------ImagesWithDate =%zd, %#",yArrayOfImagesWithDate.count,tempDate);
[yArrayOFNNestedImgs addObject:yArrayOfImagesWithDate];
NSLog(#" ----------Count =%zd",yArrayOFNNestedImgs.count);
[NSThread sleepForTimeInterval:10];
// update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
};
[library enumerateGroupsWithTypes:groupTypes
usingBlock:assetGroupEnumerator
failureBlock:^(NSError *error) {NSLog(#"A problem occurred");}];
}
Please Suggest..Thanks in advance.
[NSThread sleepForTimeInterval:1]; is blocking main thread for a second. It's not needed.
You could use [self performSelectorInBackground:#selector(getAllVideosAndImages) withObject:nil];
Also, wrap first [self.tableView reloadData]; in
dispatch_async(dispatch_get_main_queue(), ^{
...
}); too
In order to lazily load the resources, here is the recommended approach:
Download URLs of all resources and store them into your container (array / objects etc)
Fire a NSURLSessionTask (post iOS 7 only) which runs async on background queue. If you are below iOS 7, you can use NSURLConnection SendAsynchronousRequest API - it's deprecated in iOS 9 so you better get rid of that soon.
Process your downloaded resources while on background queue - store audio/image files, create UIImage from NSData, and so on.
Come back to main queue, then update the relevant UI part with downloaded content. If audio/video, play it. If UIImage, display it.
Here - the entire approach is described in my tutorial.
I'm using Xcode 4.6.3 and iOS 5.5/6.1.6 .
I am using a background thread to load large quantities of jpg from a server to iOS devices.
dispatch_async(kBgQueue, ^
{
// get the array of filenames to download
NSURL* url = [NSURL URLWithString:webPath];
NSArray* theArray = [NSArray arrayWithContentsOfURL:url];
if( theArray )
{
dispatch_async(dispatch_get_main_queue(), ^{
// disable screen buttons
[self setButtons:false];
});
[self loadImagesFromList:theArray sourceBundle:bundlePath destBundle:localBundlePath manager:manager];
if (!stopFlag) {
// if no memory error has occurred
NSLog(#"calling refresh after load_images");
dispatch_async(dispatch_get_main_queue(), ^{
[self refresh];
});
}
theArray = nil;
}
else
{
NSLog(#"Error loading bundle");
}
});
The background method:
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndictor startAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self.progressIndictor setProgress:0 animated:NO];
});
NSURL *url;
NSString *srcFile;
NSString *destFile;
NSError *error = nil;
int counter = 0;
float prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
for (NSString *file in theArray)
{
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
srcFile = [bundlePath stringByAppendingPathComponent:file];
destFile = [localBundlePath stringByAppendingPathComponent:file];
counter += 1;
prog += increment;
if (counter == stepSize) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndictor.progress = prog;
});
counter = 0;
}
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
url = [NSURL URLWithString:srcFile];
data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
data = nil;
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}
}
If the files exist, the loop zips through the array and exits back to the main thread ok.
If any files are missing, the download/write part seems to chew up the RAM and cause low memory warning to trigger. It takes several thousand files to do it.
I've tried declaring the variables outside the loop, and even doing the whole thing in the main thread to test if that was causing the leak.
I tried using the alternate dataWithContentsOfURL:options:error call.
I tried Instruments, but it is really slow and crashes often. Before crashing, it does show allocation going up, up, up slowly.
After several days on this, I'm stumped.
The first thing I'd suggest is using an #autoreleasepool to control the peak amount of memory consumed. Right now, you're downloading the contents into the NSData as an autorelease object, and when done, you're nil-ing that variable, which simple flags it to be deallocated once the autorelease pool is drained (which will not happen until loadImagesFromList is done). By (a) moving the variable declarations inside the for loop; and (b) wrapping this in an #autoreleasepool, your memory will be deallocated as the individual downloads finish.
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
dispatch_async(dispatch_get_main_queue(), ^{
// your UI update here
});
int counter = 0;
float prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
for (NSString *file in theArray)
{
#autoreleasepool {
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];
counter += 1;
prog += increment;
if (counter == stepSize) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressIndictor.progress = prog;
});
counter = 0;
}
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
NSError *error = nil;
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
NSURL *url = [NSURL URLWithString:srcFile];
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}
}
}
You might want to refactor this code to use NSOperationQueue. This addresses the peak memory issue, but also let's you enjoy a degree of concurrency. Because iOS only allows 4-5 concurrent requests anyway, you want to limit the maximum number of concurrent operations to a reasonable number, and this mitigates network timeout risks if trying to run too many concurrent requests. (This maxConcurrentOperationCount feature is the main reason I suggest using operation queues.)
Anyway, that might look like:
-(void)loadImagesFromList:(NSArray *)theArray
sourceBundle:(NSString *)bundlePath
destBundle:(NSString *)localBundlePath
manager:(NSFileManager *)manager {
// initialize the progress and activity indicator
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// your UI update here
}];
int __block counter = 0;
float __block prog = 0;
float increment = 1.0 / [theArray count];
float stepSize = [theArray count] / 10;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4;
for (NSString *file in theArray)
{
[queue addOperationWithBlock:^{
if (stopFlag) {
NSLog(#"I see stopFlag = true, counter = %d, prog = %f", counter, prog);
return;
}
NSString *srcFile = [bundlePath stringByAppendingPathComponent:file];
NSString *destFile = [localBundlePath stringByAppendingPathComponent:file];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
counter += 1;
prog += increment;
if (counter == stepSize) {
self.progressIndictor.progress = prog;
counter = 0;
}
}];
// only download if file isn't already here
BOOL fileExists = [manager fileExistsAtPath:destFile]; // check if we already have it
if (!fileExists) {
NSError *error = nil;
// jpg or folder check
if ([[destFile pathExtension] isEqualToString:#"jpg"]) {
NSURL *url = [NSURL URLWithString:srcFile];
NSData *data = [NSData dataWithContentsOfURL:url
options:0
error:&error];
[data writeToFile:destFile options:NSDataWritingAtomic error:&error];
} else {
[manager createDirectoryAtPath:destFile withIntermediateDirectories:YES attributes:nil error:&error];
}
}
}];
}
}
There are other refinements I might suggest (e.g. implementing cancelation logic rather than looking at stopFlag), but I was trying to minimize the code changes. I'm just taking advantage of the fact that one can easily replace dispatch_async:
dispatch_async(dispatchQueue, ^{ ... });
with NSOperationQueue method addOperationWithBlock:
[operationQueue addOperationWithBlock:^{ ... }];
But now we can use a concurrent NSOperationQueue with maxConcurrentOperationCount of 4 or 5, and you suddenly enjoy a nice, constrained degree of concurrency. You may find that this is observably faster than downloading files sequentially.
I am ultimately trying to convert an array of photos that are in a dictionary in url rep form to base64 to be sent over json.
Here is the dictionary code and log of it:
NSDictionary *dict = [self.form dictionaryWithValuesForKeys:keys];
NSLog(#"dict::%#",dict);
NSLog:
dict::{
boardLodgingFurnished = "<null>";
caption = "<null>";
cars = "";
photos = (
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=CE8A426B-3B59-4172-8761-CC477F3BB3EE&ext=JPG";
},
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=F4B68A42-1CA0-4880-9FB5-177CB091A28C&ext=JPG";
}
);
yearsAtLocation = "";
yearsInTheBusiness = "";
}
So for each photo in the dictionary I would like to take the urlRep and convert that to a base64 string and replace the urlRep with it in the dictionary.
What I have right now..not sure if I am going in the right direction:
for (id imageURL in [dict objectForKey:#"photos"])
{
ALAssetsLibrary *library = [ALAssetsLibrary new];
ALAsset *ourAsset = [self assetForURL:imageURL withLibrary:library];
/* Check out ALAssets */
NSLog(#"%#", ourAsset);
ALAssetRepresentation *representation = [ourAsset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSLog(#"imagedata??%#", [imageData base64EncodedString]);
//need to know how to add this back to dict
}
the below method is called from above but crashes on the while loop with
-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090
2014-01-03 10:57:27.361 Inspection App[2728:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090'
method
- (ALAsset *)assetForURL:(NSURL *)url withLibrary:(ALAssetsLibrary *)assetsLibrary {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[assetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
result = asset;
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = error;
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
return result;
}
edit:
if (photoUrls.count) {
for (id photos in photoUrls){
NSString *urlString = photos;
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
}
}
else {
NSLog(#"where are my urls?");
}
NSMutableDictionary *jsonWithPhotos = [dict mutableCopy];
[jsonWithPhotos setObject:convertedImages forKey:#"photo64"];
NSLog(#"jjson photos::%#", jsonWithPhotos);
updated method
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
[convertedImages addObject:base64];
// NSLog(#"converted::%#",convertedImages);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
When i log jsonWithPhotos the object photo64 is just a blank array
The crash is due to a false assumption in the code about the dictionary. Given the posted description of the dictionary parsed as json, you'd need to get the urls like this:
// collect the photo urls in an array
NSMutableArray *photoUrls = [NSMutableArray array];
// photos is an array of dictionaries in the dictionary
NSArray *photos = dict[#"photos"];
for (NSDictionary *photo in photos) {
// photo is a dictionary containing a "caption" and a "urlRep"
[photoUrls addObject:photo[#"urlRep"]];
}
Now you can proceed with a method whose job is just the conversion. Your question might contain a lot more questions about how to do this. I'd recommend starting simple. See if you can do one conversion. Test it by writing the reverse, from base64 back to an image.
Edit 0: Without deeply checking it, I'd restructure your encoding attempt to look like this:
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
Call it like this:
if (photoUrls.count) {
NSString *urlString = photoUrls[0];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
} else {
NSLog(#"where are my urls?");
}
Once it's working, see if you can reverse it, making an image out of the base64 data. Finally, once all that's working, you can deal with potential memory issues. My advice there is to consider encoding one at a time, post one at a time to the server and release everything in between.
Edit 1 - Per followup question, if you want to replace all of the urls in the url array with base64 encodings, it might go something like this (remember that this might use a lot of memory):
- (void)base64ImagesAtUrls:(NSMutableArray *)urls result:(void (^)(void))completion {
__block NSInteger completed = 0; // this is how we'll know that we're done
// this approach doesn't depend on the asset library retrievals completing
// sequentially, even though they probably will
for (int i=0; i<urls.count; i++) {
NSString *urlString = urls[i];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
[urls replaceObjectAtIndex:i withObject:base64];
if (++completed == urls.count) completion();
}];
}
}
Hi I am capturing screen and make as video. It works well in simulator. While I run it in device. App crashes. below I put my code and error
- (void)stopRecording
{
[self.displayLink invalidate];
startTimestamp = 0.0;
dispatch_async(queue, ^
{
//if (deferImageprocess) {
if (self.writer.status != AVAssetWriterStatusCompleted && self.writer.status != AVAssetWriterStatusUnknown) {
[self.writerInput markAsFinished];
}
if ([self.writer respondsToSelector:#selector(finishWritingWithCompletionHandler:)]) {
[self.writer finishWritingWithCompletionHandler:^
{
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:self.pathStr]
completionBlock:^(NSURL *assetURL, NSError *error){
if (!error)
{
NSLog(#"Saved Successfully");
[self finishBackgroundTask];
[self.delegate recordingFinished:[NSString stringWithFormat:#"%#", self.pathStr]];
}
}];
}];
}
else {
[self.writer finishWriting];
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:[NSURL fileURLWithPath:self.pathStr]
completionBlock:^(NSURL *assetURL, NSError *error){
}];
[self finishBackgroundTask];
[self restartRecordingIfNeeded];
}
});
}
**This above to Wirte the video file in Document as well as gallery**
- (void)captureFrame:(CADisplayLink *)displayLink
{
dispatch_async(queue, ^
{
if (self.writerInput.readyForMoreMediaData) {
CVReturn status = kCVReturnSuccess;
CVPixelBufferRef buffer = NULL;
CFTypeRef backingData;
#if APPSTORE_SAFE || TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
__block UIImage *screenshot = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
screenshot = [self screenshot];
});
CGImageRef image = [[self screenshot] CGImage];
CGDataProviderRef dataProvider = CGImageGetDataProvider(image);
CFDataRef data = CGDataProviderCopyData(dataProvider);
backingData = CFDataCreateMutableCopy(kCFAllocatorDefault, CFDataGetLength(data), data);
CFRelease(data);
const UInt8 *bytePtr = CFDataGetBytePtr(backingData);
status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault,
CGImageGetWidth(image),
CGImageGetHeight(image),
kCVPixelFormatType_32BGRA,
(void *)bytePtr,
CGImageGetBytesPerRow(image),
NULL,
NULL,
NULL,
&buffer);
NSLog(#"%d", status);
NSParameterAssert(status == kCVReturnSuccess && buffer);
#else
CFTypeRef surface = [UIWindow createScreenIOSurface];
backingData = surface;
NSDictionary *pixelBufferAttributes = #{(NSString *)kCVPixelBufferPixelFormatTypeKey : #(kCVPixelFormatType_32BGRA)};
status = CVPixelBufferCreateWithIOSurface(NULL, surface, (__bridge CFDictionaryRef)(pixelBufferAttributes), &buffer);
NSLog(#"%d", status);
NSParameterAssert(status == kCVReturnSuccess && buffer);
#endif
if (buffer) {
CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
CFTimeInterval elapsedTime = currentTime - firstFrameTime;
CMTime presentTime = CMTimeMake(elapsedTime * TIME_SCALE, TIME_SCALE);
if(![self.writerInputPixelBufferAdaptor appendPixelBuffer:buffer withPresentationTime:presentTime]) {
[self stopRecording];
NSLog(#"Failed");
}
CVPixelBufferRelease(buffer);
}
CFRelease(backingData);
}
});
if (startTimestamp == 0.0) {
startTimestamp = displayLink.timestamp;
}
NSTimeInterval dalta = displayLink.timestamp - startTimestamp;
if (self.autosaveDuration > 0 && dalta > self.autosaveDuration) {
startTimestamp = 0.0;
[self rotateFile];
}
}
Above to capture frames.
Now I got this error While stopRecording.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '*** -[AVAssetWriterInput markAsFinished] Cannot call method when status is 2'.
I don't know what problem here. Please tell me any suggestion
This error causes because of
Marking writer as finished when the status is 2.
Trying to reuse your AVAssetWriter instance. Remember to always create a new instance when you need to start the recording.
Threading issues, create your queue instead as default queue provided by sdk are concurrent, they work in concurrent manner and not serial.
I have a singleton that loads up an a bunch of ALAssets when my app launches. This is causing the main thread to freeze for more then 10 seconds while it loads each image into memory. Obviously a big no no.
I tried to put it on a background thread, but it only partially executes.
+ (CCPhotos*) sharedPhotos
{
static CCPhotos* shared = nil;
if (!shared)
{
shared = [[CCPhotos alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[shared loadPhotosArray];
});
}
return shared;
}
- (void) loadPhotosArray
{
NSLog(#"Loading photos");
_photos = [[NSMutableArray alloc] init];
NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey: #"savedImages"];
if (data)
{
NSArray* storedUrls = [[NSArray alloc] initWithArray: [NSKeyedUnarchiver unarchiveObjectWithData: data]];
// reverse array
NSArray* urls = [[storedUrls reverseObjectEnumerator] allObjects];
for (NSURL* assetUrl in urls)
{
NSLog(#"Looking up %#", assetUrl);
// Block to handle image handling success
// This initializes, but doesn't get called
////-->>
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
if (iref) {
UIImage* tempImage = [UIImage imageWithCGImage:iref];
UIImage* image = [[UIImage alloc] initWithCGImage: tempImage.CGImage scale: 1.0 orientation: UIImageOrientationUp];
// Set image in imageView
[_photos addObject: image];
NSLog(#"Added photo with url: %#", [rep url]);
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
}
};
// Handles failure of getting image
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
// Load image then call appropriate block
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL: assetUrl
resultBlock: resultblock
failureBlock: failureblock];
}
}
else
{
NSLog(#"Photo storage is empty");
}
}
I've narrowed down the problem to ALAssetsLibraryAssetForURLResultBlock resultblock which doesn't get called. Multiple threads spawn at the beginning, and each one gets to this line, initializes the result block, but doesn't call it. I think it has to do with the thread safety on the block. Any thoughts?
Try doing this when you post the notification:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
});
I believe the reason it is not working for you is because this is not being called on the main thread. All UI changes/updates (which I am assuming this leads to) must be executed on the main thread. This will force the notification to happen on the necessary thread and should work properly!
This is a pretty common thing to do. I actually created a code snippet because I found myself typing this out so often:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
<#on back thread#>
dispatch_async(dispatch_get_main_queue(), ^{
<#on main thread#>
});
});
I then set the completion short cut to dispatch_async
From what I'm understanding, you are spinning up multiple instances of your singleton. In that case you will probably want to put a #synchronized block in there. For example:
+ (CCPhotos*) sharedPhotos
{
static CCPhotos* shared = nil;
#synchronized ([CCPhotos class])
{
if (!shared)
{
shared = [[CCPhotos alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[shared loadPhotosArray];
});
}
}
return shared;
}
Hope that helps.