diff --git a/src/app.cpp b/src/app.cpp index c7e906a..8a5d7f1 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7,6 +7,7 @@ #ifdef __APPLE__ #include +#include "objc_utils.h" #endif #include "settings.h" @@ -713,17 +714,7 @@ void App::rec_clear() { rec_stop(); #if defined(__IOS__) || defined(__OSX__) - NSString *path = [NSString stringWithUTF8String:rec_path.c_str()]; - NSDirectoryEnumerator* en = [[NSFileManager defaultManager] enumeratorAtPath:path]; - NSError* err = nil; - BOOL res; - NSString* file; - while (file = [en nextObject]) - { - NSString* file_path = [path stringByAppendingPathComponent:file]; - [[NSFileManager defaultManager] removeItemAtPath:file_path error:nil]; - NSLog(@"delete: %@", file_path); - } + delete_all_files_in_path(rec_path); #endif rec_count = 0; update_rec_frames(); @@ -760,11 +751,6 @@ void App::rec_stop() } } -void stillImageDataReleaseCallback(void *releaseRefCon, const void *baseAddress) -{ - free((void *)baseAddress); -} - void App::rec_export(std::string path) { int progress = 0; @@ -774,157 +760,9 @@ void App::rec_export(std::string path) pb->m_title->set_text("Exporting MP4 movie"); #if defined(__IOS__) || defined(__OSX__) - NSString* mov_path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; - if ([[NSFileManager defaultManager] fileExistsAtPath:mov_path]) - { - NSLog(@"remove existing mp4"); - [[NSFileManager defaultManager] removeItemAtPath:mov_path error:nil]; - } - NSURL* url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]]; - AVAssetWriter* writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil]; - writer.shouldOptimizeForNetworkUse = NO; - NSDictionary *videoCompressionSettings = @{ - AVVideoCodecKey : AVVideoCodecH264, - AVVideoWidthKey : @(width), - AVVideoHeightKey : @(height), - AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(8<<20) } - }; - if (![writer canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo]) - { - NSLog(@"Couldn't add asset writer video input."); - return; - } - AVAssetWriterInput* input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo - outputSettings:videoCompressionSettings - sourceFormatHint:nil]; - input.expectsMediaDataInRealTime = YES; - - NSDictionary *adaptorDict = @{ - (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), - (id)kCVPixelBufferWidthKey : @(width), - (id)kCVPixelBufferHeightKey : @(height) - }; - - AVAssetWriterInputPixelBufferAdaptor* _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] - initWithAssetWriterInput:input - sourcePixelBufferAttributes:adaptorDict]; - - - // Add asset writer input to asset writer - if (![writer canAddInput:input]) { - NSLog(@"Couldn't add input to writer."); - return; - } - - [writer addInput:input]; - - CMTime t; - t.timescale = 30; - t.flags = kCMTimeFlags_Valid; - t.epoch = 0; - t.value = 0; - //[writer startSessionAtSourceTime:t]; - - CVPixelBufferRef buff = NULL; - uint8_t* data = (uint8_t*)calloc(1, width * height * 4); - CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32RGBA, data, - width * 4, stillImageDataReleaseCallback, nil, nil, &buff); - OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil, _pixelBufferAdaptor.pixelBufferPool, &buff); - - - if (writer.status == AVAssetWriterStatusUnknown) - { - // If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp - if ([writer startWriting]) - { - [writer startSessionAtSourceTime:t]; - } - } - - if (writer.status == AVAssetWriterStatusWriting) - { - for (int i = 0; i < tot; i++) - { - // If the asset writer status is writing, append sample buffer to its corresponding asset writer input - if (input.readyForMoreMediaData) - { - char path[256]; - snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), i); - NSString* img_path = [NSString stringWithUTF8String:path]; - NSLog(@"frame: %@", img_path); -#if __OSX__ - NSImage *image = [[NSImage alloc] initWithContentsOfFile:img_path]; - if (!image) - break; - NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height); - CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:NULL hints:nil]; -#elif __IOS__ - UIImage* image = [UIImage imageNamed:img_path]; - if (!image) - break; - CGImageRef cgImage = image.CGImage; -#endif - - NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, - [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil]; - CGSize sz = image.size; - CVPixelBufferRef pxbuffer = NULL; - { - CGImageRef image = cgImage; - CGSize size = sz; - CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); - // CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pxbuffer); - - //NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); - - CVPixelBufferLockBaseAddress(pxbuffer, 0); - void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); - //NSParameterAssert(pxdata != NULL); - - CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); - CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4*size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst); - //NSParameterAssert(context); - - CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); - - CGColorSpaceRelease(rgbColorSpace); - CGContextRelease(context); - - CVPixelBufferUnlockBaseAddress(pxbuffer, 0); - } - - t.value = i; - if (![_pixelBufferAdaptor appendPixelBuffer:pxbuffer withPresentationTime:t]) - { - NSLog(@"error %@", [writer.error localizedFailureReason]); - } - CFRelease(pxbuffer); - - progress++; - pb->m_progress->SetWidthP((float)progress / tot * 100.f); - async_update(); - } - } - [input markAsFinished]; - [writer finishWritingWithCompletionHandler:^{ - NSString* path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; - NSLog(@"saved video %@", path); -#if __IOS__ - if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) - { - NSLog(@"saving to camera roll"); - UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil); - } -#endif - }]; - } - - if (writer.status == AVAssetWriterStatusFailed) - { - NSLog(@"failed"); - } - + export_mp4(rec_path, width, height, rec_count, ^(float) { + pb->m_progress->SetWidthP((float)progress / tot * 100.f); + }); #endif pb->destroy(); diff --git a/src/canvas.cpp b/src/canvas.cpp index a51347f..8482c17 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -8,7 +8,7 @@ #ifdef __APPLE__ #include -#import +#import "objc_utils.h" #endif @@ -1863,18 +1863,7 @@ void Canvas::export_equirectangular_thread(std::string file_path) //int ret = stbi_write_png(name, m_latlong.getWidth(), m_latlong.getHeight(), 4, latlong_data.get(), m_latlong.stride()); #ifdef __IOS__ - [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ - NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:file_path.c_str()]]; - PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url]; - changeRequest.creationDate = [NSDate date]; - } completionHandler:^(BOOL success, NSError *error) { - if (success) { - NSLog(@"successfully saved"); - } - else { - NSLog(@"error saving to photos: %@", error); - } - }]; + save_image_library(file_path); #endif App::I->render_task_async([id=cube_id] @@ -2261,18 +2250,7 @@ void Canvas::export_cubes() #ifdef __IOS__ - [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ - NSURL* url = [NSURL fileURLWithPath : [NSString stringWithUTF8String : name]]; - PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL : url]; - changeRequest.creationDate = [NSDate date]; - } completionHandler: ^ (BOOL success, NSError *error) { - if (success) { - NSLog(@"successfully saved"); - } - else { - NSLog(@"error saving to photos : %@", error); - } - }]; + save_image_library(name); #endif #ifdef __OBJC__ [files addObject:[NSString stringWithUTF8String : name]]; diff --git a/src/objc_utils.cpp b/src/objc_utils.cpp index f2a8f65..0e4b7db 100644 --- a/src/objc_utils.cpp +++ b/src/objc_utils.cpp @@ -8,6 +8,7 @@ #include #ifdef __OBJC__ +#import @implementation PathWithModDate @end @implementation ObjcUtils @@ -122,3 +123,194 @@ void install_global_handlers() std::set_terminate(TerminateHandler); */ } + +void delete_all_files_in_path(const std::string& source_path) +{ + NSString *path = [NSString stringWithUTF8String:source_path.c_str()]; + NSDirectoryEnumerator* en = [[NSFileManager defaultManager] enumeratorAtPath:path]; + LOG("delete all files from %s", source_path.c_str()); + while (NSString* file = [en nextObject]) + { + NSString* file_path = [path stringByAppendingPathComponent:file]; + [[NSFileManager defaultManager] removeItemAtPath:file_path error:nil]; + LOG("delete: %s", [file_path UTF8String]); + } +} + +static void stillImageDataReleaseCallback(void *releaseRefCon, const void *baseAddress) +{ + free((void *)baseAddress); +} + +void export_mp4(const std::string& rec_path, int width, int height, int tot, void(^progress_callback)(float)) +{ + int progress = 0; + NSString* mov_path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; + if ([[NSFileManager defaultManager] fileExistsAtPath:mov_path]) + { + NSLog(@"remove existing mp4"); + [[NSFileManager defaultManager] removeItemAtPath:mov_path error:nil]; + } + NSURL* url = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]]; + AVAssetWriter* writer = [AVAssetWriter assetWriterWithURL:url fileType:AVFileTypeMPEG4 error:nil]; + writer.shouldOptimizeForNetworkUse = NO; + NSDictionary *videoCompressionSettings = @{ + AVVideoCodecKey : AVVideoCodecH264, + AVVideoWidthKey : @(width), + AVVideoHeightKey : @(height), + AVVideoCompressionPropertiesKey : @{ AVVideoAverageBitRateKey : @(8<<20) } + }; + if (![writer canApplyOutputSettings:videoCompressionSettings forMediaType:AVMediaTypeVideo]) + { + NSLog(@"Couldn't add asset writer video input."); + return; + } + AVAssetWriterInput* input = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo + outputSettings:videoCompressionSettings + sourceFormatHint:nil]; + input.expectsMediaDataInRealTime = YES; + + NSDictionary *adaptorDict = @{ + (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_32BGRA), + (id)kCVPixelBufferWidthKey : @(width), + (id)kCVPixelBufferHeightKey : @(height) + }; + + AVAssetWriterInputPixelBufferAdaptor* _pixelBufferAdaptor = [[AVAssetWriterInputPixelBufferAdaptor alloc] + initWithAssetWriterInput:input + sourcePixelBufferAttributes:adaptorDict]; + + + // Add asset writer input to asset writer + if (![writer canAddInput:input]) { + NSLog(@"Couldn't add input to writer."); + return; + } + + [writer addInput:input]; + + CMTime t; + t.timescale = 30; + t.flags = kCMTimeFlags_Valid; + t.epoch = 0; + t.value = 0; + //[writer startSessionAtSourceTime:t]; + + CVPixelBufferRef buff = NULL; + uint8_t* data = (uint8_t*)calloc(1, width * height * 4); + CVPixelBufferCreateWithBytes(kCFAllocatorDefault, width, height, kCVPixelFormatType_32RGBA, data, + width * 4, stillImageDataReleaseCallback, nil, nil, &buff); + OSStatus err = CVPixelBufferPoolCreatePixelBuffer(nil, _pixelBufferAdaptor.pixelBufferPool, &buff); + + + if (writer.status == AVAssetWriterStatusUnknown) + { + // If the asset writer status is unknown, implies writing hasn't started yet, hence start writing with start time as the buffer's presentation timestamp + if ([writer startWriting]) + { + [writer startSessionAtSourceTime:t]; + } + } + + if (writer.status == AVAssetWriterStatusWriting) + { + for (int i = 0; i < tot; i++) + { + // If the asset writer status is writing, append sample buffer to its corresponding asset writer input + if (input.readyForMoreMediaData) + { + char path[256]; + snprintf(path, sizeof(path), "%s/%04d.jpg", rec_path.c_str(), i); + NSString* img_path = [NSString stringWithUTF8String:path]; + NSLog(@"frame: %@", img_path); +#if __OSX__ + NSImage *image = [[NSImage alloc] initWithContentsOfFile:img_path]; + if (!image) + break; + NSRect imageRect = NSMakeRect(0, 0, image.size.width, image.size.height); + CGImageRef cgImage = [image CGImageForProposedRect:&imageRect context:NULL hints:nil]; +#elif __IOS__ + UIImage* image = [UIImage imageNamed:img_path]; + if (!image) + break; + CGImageRef cgImage = image.CGImage; +#endif + + NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey, + [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey, nil]; + CGSize sz = image.size; + CVPixelBufferRef pxbuffer = NULL; + { + CGImageRef image = cgImage; + CGSize size = sz; + CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, size.width, size.height, kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options, &pxbuffer); + // CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, adaptor.pixelBufferPool, &pxbuffer); + + //NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL); + + CVPixelBufferLockBaseAddress(pxbuffer, 0); + void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer); + //NSParameterAssert(pxdata != NULL); + + CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(pxdata, size.width, size.height, 8, 4*size.width, rgbColorSpace, kCGImageAlphaPremultipliedFirst); + //NSParameterAssert(context); + + CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); + + CGColorSpaceRelease(rgbColorSpace); + CGContextRelease(context); + + CVPixelBufferUnlockBaseAddress(pxbuffer, 0); + } + + t.value = i; + if (![_pixelBufferAdaptor appendPixelBuffer:pxbuffer withPresentationTime:t]) + { + NSLog(@"error %@", [writer.error localizedFailureReason]); + } + CFRelease(pxbuffer); + + progress++; + progress_callback((float)progress / tot * 100.f); + } + } + [input markAsFinished]; + [writer finishWritingWithCompletionHandler:^{ + NSString* path = [NSString stringWithFormat:@"%s/out.mp4", rec_path.c_str()]; + NSLog(@"saved video %@", path); +#if __IOS__ + if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) + { + NSLog(@"saving to camera roll"); + UISaveVideoAtPathToSavedPhotosAlbum(path, nil, nil, nil); + } +#endif + }]; + } + + if (writer.status == AVAssetWriterStatusFailed) + { + NSLog(@"failed"); + } + +} + +void save_image_library(const std::string& path) +{ +#if __IOS__ + [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ + NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path.c_str()]]; + PHAssetChangeRequest *changeRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url]; + changeRequest.creationDate = [NSDate date]; + } completionHandler:^(BOOL success, NSError *error) { + if (success) { + NSLog(@"successfully saved"); + } + else { + NSLog(@"error saving to photos: %@", error); + } + }]; +#endif +} diff --git a/src/objc_utils.h b/src/objc_utils.h index e0551b5..d10b0d0 100644 --- a/src/objc_utils.h +++ b/src/objc_utils.h @@ -11,3 +11,6 @@ #endif void install_global_handlers(); +void delete_all_files_in_path(const std::string& source_path); +void export_mp4(const std::string& rec_path, int width, int height, int tot, void(^progress_callback)(float)); +void save_image_library(const std::string& path);