2014年4月22日星期二

Import video from ALAsset

針對影片從 photo library copy 回來 app 裡時,如果是大於6分鐘的檔案,在 iPad 上面時,使用舊的方法會 allocate 800多MB,造成 app memory error 然後 crash.

解法是要改用其他方式來 copy file.

One of the main features of my recent app Classified is importing photo's and video's. There are a few ways you can get this done. The most obvious one is to use theUIImagePickerController. However if you use custom UI UIImagePickerController won't do it for you. You will probably use ALAsset.
Importing photo's is straight forward with ALAsset. Video's on the other hand can be tricky if you do it the wrong way like I've experienced.

The wrong way (會出問題的舊方法)

// Let's assume 'assetPresentation' is a ALAssetRepresentation object
Byte *buffer = (Byte*)malloc(assetPresentation.size);
NSUInteger buffered = [assetPresentation getBytes:buffer
                                         fromOffset:0.0
                                         length:assetRepresentation.size
                                          error:nil];    // you should check for errors tho!

// Get the data
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];

// Write data to the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *videoPath = [documentsDirectory stringByAppendingPathComponent:@"video.mov"];
NSError *error = nil;

// Actual writing
[data writeToFile:videoPath
          options:NSDataWritingFileProtectionComplete
            error:&error];

    // Check for errors etc etc...
It looks obvious, right? Except... it's wrong. Importing videos this way will crash your application at some point, especially with bigger videos. Your app will run out of memory since you are loading the whole video in memory first and then writing it to disk.

I've profiled the memory and saw crazy pikes above of 800MB before it crashed. The catchy thing about this issue is that you may not experience any problems at all if you import small videos so always test extensively.

The right way: NSFileHandle

The solution is to use NSFileHandle. You can write/copy the video directly from the source path to your destination path without loading the entire video into your memory.

沒問題的新方法,這段程式碼直接貼上就可以RUN了真棒。
 // Create a file handle to write the file at your destination path
[[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
if (!handle)
{
    // Handle error here…
}

// Create a buffer for the asset
static const NSUInteger BufferSize = 1024*1024;
ALAssetRepresentation *rep = [asset defaultRepresentation];
uint8_t *buffer = calloc(BufferSize, sizeof(*buffer));
NSUInteger offset = 0, bytesRead = 0;

// Read the buffer and write the data to your destination path as you go
do
{
    @try
    {   
        bytesRead = [rep getBytes:buffer fromOffset:offset length:BufferSize error:&error];
        [handle writeData:[NSData dataWithBytesNoCopy:buffer length:bytesRead freeWhenDone:NO]];
        offset += bytesRead;
    }
    @catch (NSException *exception)
    {
        free(buffer);

         // Handle the exception here...
    }
} while (bytesRead > 0);

// 
free(buffer);

2014年4月15日星期二

Sample Playback for IOS

build 後 vlc 的 framework 後,先跑一下 sample playback, 結果就出錯,訊息如下:

========================
Hi, I've downloaded and built the VLC for IOS as per https://wiki.videolan.org/VLCKit/

I have run the build scripts with -s and without, and have build the library for both.

I can run the sample playback app fine on IOS simulator, but I can't get it to build for my ipad / iphone.

Here are the errors reported.

ld: warning: directory not found for option '-L/Users/austinharvey/Documents/Development/VLCKit/Examples_iOS/SimplePlayback/../../build/Release-iphonesos'
ld: warning: ignoring file /Users/austinharvey/Documents/Development/VLCKit/build/Release-iphonesimulator/libMobileVLCKit.a, missing required architecture armv7 in file /Users/austinharvey/Documents/Development/VLCKit/build/Release-iphonesimulator/libMobileVLCKit.a (2 slices)
Undefined symbols for architecture armv7:
"_OBJC_CLASS_$_VLCMedia", referenced from:
objc-class-ref in VDLViewController.o
"_OBJC_CLASS_$_VLCMediaPlayer", referenced from:
objc-class-ref in VDLViewController.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
========================

首先重build 看看 ./buildMobileVLCKit.sh
和 ./buildMobileVLCKit.sh -s

build 完後,再跑跑看程式,還是一樣出錯。


最後解法是,先remove link binary with libraries:
libMobileVLCKit.a

(理論上,用不到 LibMediaLibraryKit.a)



接著,就可以播放了,只有陽春暫停/播放功能,沒有 slider bar, 滿有趣的預告片,史密斯夫婦(Mr. & Mrs. Smith)。

史密斯任務是布萊德·彼特、安潔莉娜·裘莉在2005年聯合拍攝的一部美國喜劇動作片。

所以另一個 sample: DropIn-Player 應該會是比較好的 sample, VLC for iOS完整的功能似乎太強大了,要改很久,居然可以用手勢調亮度/聲音/進度。

VLC for iOS 2.2.2

iOS 內建的 ALMoviePlayerController 和 mpmovieplayercontroller 在播放 local file 或標準的 http 系列的 file, 是可以正常的播放,針對sound 的 stream 也可以播放,但是對 video stream 的支援,似乎就沒有想像中的好,直接放 nsurl 進去播放,會出現 Error: itemfailedtoplaytoend.

解法有2:
解法1: 下載一部份,就播放一部份。
视频边下载边播放
這個解法,會變成播放時,連到 local host (127.0.0.1:12345) 來播放下載到一半的 video.mp4, slider bar 的部份,似乎不太work, 需要自行下去調整。

解法2: 使用 VLC for iOS
http://www.videolan.org/vlc/download-ios.html

==================
首先,照這個說明書來做:
https://wiki.videolan.org/iOSCompile


先下載 git, 把 source code clone 出來:

Get the source

If you want to develop VLC for iOS, it is highly recommended that you use the git version:
git clone git://git.videolan.org/vlc-ports/ios.git


先執行一次有 -s 的,給模擬器使用,大約需要半小時。
Build it

Build it for the simulator.
sh compileVLCforiOS.sh -s
Wait, and grab a coffee.


再執行一次,沒有 -s 的,給實體測試機用,我跑了整整4個小時!咖啡都喝到飽了。
Manually assign the code sign in Xcode, and build it for devices.
sh compileVLCforiOS.sh
Grab a second coffee. :)


Deploy

Open the project in Xcode and click on Run.
If you want to run in the simulator after running compileVLCforiOS.sh without the -s option, you need to run it again with the -s option, and vice versa.


下圖是跑 -s, 04:00~04:24 跑完。



接著跑沒有 -s, 從04:24~08:40, build 完。。。。,天呀。

2014年4月10日星期四

NSURL & NSString 相互轉換

NSURL to NSString

NSString * urlStr = [url absoluteString];

NSString to NSURL

NSURL * url = [NSURL URLWithString:urlStr];
NSURL * url = [[NSURL alloc] initWithString:urlStr];

2014年4月8日星期二

Objective-C - NSString 和 NSDate 互相轉換


很簡單,使用 NSDateFormatter 就可以令 NSString  NSDate 互相轉換。



NSDate 轉換為 NSString:
  1. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  2. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  3. NSString *strDate = [dateFormatter stringFromDate:[NSDate date]];
  4. NSLog(@"%@", strDate);
  5. [dateFormatter release];

 NSString 轉換為 NSDate:
  1. NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  2. [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
  3. NSDate *date = [dateFormatter dateFromString:@"2010-08-04 16:01:03"];
  4. NSLog(@"%@", date);
  5. [dateFormatter release];

相關書籍:

  Objective-C

================
NSString *dateString = @"01-02-2010";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
// this is imporant - we set our input date format to match our input string
// if format doesn't match you'll get nil from your string, so be careful
[dateFormatter setDateFormat:@"dd-MM-yyyy"];
NSDate *dateFromString = [[NSDate alloc] init];
// voila!
dateFromString = [dateFormatter dateFromString:dateString];
[dateFormatter release];
NSDate convert to NSString:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd-MM-yyyy"];
NSString *strDate = [dateFormatter stringFromDate:[NSDate date]];
NSLog(@"%@", strDate);
[dateFormatter release];


2014年4月2日星期三

multi targets 共同主程式,置換掉資源檔案

App 開發到後來,需要用同一份程式碼build 出不一樣的 App name 時,如何在 iOS/Mac 的 xCode 裡共同主程式,置換掉資源檔案(圖片/字串定義)。步驟如下:


Step1: 在 xCode 裡建立新的 Targets.

Step 2: 設定 Flag 在 Preprocessing block:


 修改後,加入 TITLE1=1


Step 3: 修改你的 Header 檔,加入 define 的 #ifdef TITLE1
#elif TITLE2

2014年3月24日星期一

iOS MD5編碼

在 iOS 裡把 NSData 或 String 轉 md5 還滿簡單的, 網路上有一大堆的方法, 我後來是使用 [string MD5] 就轉換成功. 請先下載副程式:


副程式 source code:
=====================
#import <CommonCrypto/CommonDigest.h>
@implementation NSString(MD5)
- (NSString*)MD5
{
// Create pointer to the string as UTF8
const char *ptr = [self UTF8String];
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(ptr, strlen(ptr), md5Buffer);
// Convert MD5 value in the buffer to NSString of hex values
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x",md5Buffer[i]];
return output;
}
// returns, nsdata for actual md5 bytes not hex string
- (NSData*)MD5CharData
{
// Create pointer to the string as UTF8
const char *ptr = [self UTF8String];
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(ptr, strlen(ptr), md5Buffer);
NSData *data = [NSData dataWithBytes:(const void *)md5Buffer length:sizeof(unsigned char)*CC_MD5_DIGEST_LENGTH];
return data;
}
@end
from url: