Cocoa練習帳 -8ページ目

[iOS]動画を保存する(その二)

音声も動画も、同じデリゲートのメソッドで受けるようにしている。

書き込む動画の形式は、録画されたデータを受け取ってみないと分からないので、最初のサンプリング・データ受信に形式を取得している。

            if self.assetWriter == nil {
                let fileManager = NSFileManager()
                if fileManager.fileExistsAtPath(self.outputFilePath!) {
                    fileManager.removeItemAtPath(self.outputFilePath!, error: nil)
                }
                
                if captureOutput == self.videoDataOutput {
                }
                else if captureOutput == self.audioDataOutput {
                    let fmt = CMSampleBufferGetFormatDescription(sampleBuffer)
                    let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(fmt)
                    
                    var outError: NSError? = nil
                    let url = NSURL(fileURLWithPath: self.outputFilePath!)
                    self.assetWriter = AVAssetWriter(URL: url, fileType: AVFileTypeQuickTimeMovie, error: &outError)
                    
                    let videoOutputSettings: Dictionary = [
                        AVVideoCodecKey : AVVideoCodecH264,
                        AVVideoWidthKey : self.width,
                        AVVideoHeightKey : self.height
                    ]
                    self.videoAssetWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoOutputSettings)
                    self.videoAssetWriterInput!.expectsMediaDataInRealTime = true
                    self.assetWriter!.addInput(self.videoAssetWriterInput)
                    
                    let audioOutputSettings: Dictionary = [
                        AVFormatIDKey : kAudioFormatMPEG4AAC,
                        AVNumberOfChannelsKey : Int(asbd.memory.mChannelsPerFrame),
                        AVSampleRateKey : asbd.memory.mSampleRate,
                        AVEncoderBitRateKey : 128000
                    ]
                    self.audioAssetWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: audioOutputSettings)
                    self.audioAssetWriterInput!.expectsMediaDataInRealTime = true
                    self.assetWriter!.addInput(self.audioAssetWriterInput)
                }
            }

録画したサンプリングデータの追記は、以下のコードで呼び出すだけだ。

                if captureOutput == self.videoDataOutput {
                    self.videoAssetWriterInput!.appendSampleBuffer(sampleBuffer)
                }
                else if captureOutput == self.audioDataOutput {
                    self.audioAssetWriterInput!.appendSampleBuffer(sampleBuffer)
                }

終了処理で書き込んだ動画ファイルをアルバムに保存している。

            captureSession.stopRunning()
            self.assetWriter?.finishWritingWithCompletionHandler({() -> Void in
                self.assetWriter = nil
                let assetsLib = ALAssetsLibrary()
                assetsLib.writeVideoAtPathToSavedPhotosAlbum(NSURL(fileURLWithPath: self.outputFilePath!), completionBlock: {(assetURL: NSURL!, error: NSError!) -> Void in
                })
            })
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/SequenceGrabber - GitHub
関連情報 AV Foundationプログラミングガイド
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[iOS]動画を保存する(その一)

AVFoundationを使って、録画されたサンプリング・データをファイルに保存するサンプルを作成している。

前面と背面、そしてマイクのdeviceを取得する。

var frontVideoDevice: AVCaptureDevice? = nil
var backVideoDevice: AVCaptureDevice? = nil
var audioDevice: AVCaptureDevice? = nil
captureSession.sessionPreset = AVCaptureSessionPresetLow
let devices = AVCaptureDevice.devices()
for device in devices {
    if device.hasMediaType(AVMediaTypeVideo) {
        if device.position == AVCaptureDevicePosition.Back {
            backVideoDevice = device as? AVCaptureDevice
        }
        else {
            frontVideoDevice = device as? AVCaptureDevice
        }
    }
    else if device.hasMediaType(AVMediaTypeAudio) {
        audioDevice = device as? AVCaptureDevice
    }
}

デフォルトのdeviceでいいのなら、以下のコードで対応できる。 

let videoDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
let audioDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeAudio)

sessionを準備する。

var error: NSError? = nil;
if let device = frontVideoDevice {
    frontFacingCameraDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(device, error: &error) as? AVCaptureDeviceInput
}
if let device = backVideoDevice {
    backFacingCameraDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(device, error: &error) as? AVCaptureDeviceInput
}
if let device = audioDevice {
    audioDeviceInput = AVCaptureDeviceInput.deviceInputWithDevice(device, error: &error) as? AVCaptureDeviceInput
}
 
if let deviceInput = backFacingCameraDeviceInput {
    if captureSession.canAddInput(deviceInput) {
        captureSession.addInput(deviceInput)
    }
}
else if let deviceInput = frontFacingCameraDeviceInput {
    if captureSession.canAddInput(deviceInput) {
        captureSession.addInput(deviceInput)
    }
}
if let deviceInput = audioDeviceInput {
    if captureSession.canAddInput(deviceInput) {
        captureSession.addInput(deviceInput)
    }
}

録画を始めるための準備。

videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey:kCVPixelFormatType_32BGRA]
videoDataOutput.alwaysDiscardsLateVideoFrames = true
videoDataOutput.setSampleBufferDelegate(self, queue: videoAudioDataOutputQueue)
if captureSession.canAddOutput(videoDataOutput) {
    captureSession.addOutput(videoDataOutput)
}
 
audioDataOutput.setSampleBufferDelegate(self, queue: videoAudioDataOutputQueue)
if captureSession.canAddOutput(audioDataOutput) {
    captureSession.addOutput(audioDataOutput)
}
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/SequenceGrabber - GitHub
関連情報 AV Foundationプログラミングガイド
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[OSX][iOS]第71回 Cocoa勉強会 関東

3月21日に開催されたCocoa勉強会について報告する。

今回は初期設定Windowsの作成とOgre SDKの紹介、BDRuleEngineのメモ、UXデザインのコンサルティングのお話しだった。

UXデザインの作業の流れを理解できたのが収穫だったが、SIer案件の場合の話は他では聞けない内容だったので得をした!

関連情報 Cocoa勉強会
connpass
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Android]cocos2d-xをAndroid Studioでビルドするための雛形

EclipseでGradle関連ファイルをエクスポートすればいいのだが、今後、Eclipseをインストールしていない環境での作業を考えて、Android Studioでビルドするために必要なGradle関連のファイルを備忘録として記録する。

ディレクトリ構成は以下のとおり。

-+
 | settings.gradle
 | build.gradle
 +-cocos2d
 | |
 | +-cocos
 |   |
 |   +-platform
 |     |
 |     +-android
 |       |
 |       +-java
 |         build.gradle
 |
 +-proj.android
   build.gradle

上記のsettings.gradleと複数のbuild.gradleを作成すれば、Android StudioのImport Non-Android Studio projectで開けるようになる。

ルートディレクトリのsettings.gradleとbuild.gradleの内容は以下のとおり。

include ':cocos2d:cocos:platform:android:java'
include ':proj.android'
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.0.0'
    }
}

cocos2d-xのbuild.gradleの内容は以下のとおり。

apply plugin: 'android-library'
 
dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
}
 
android {
    compileSdkVersion 10
    buildToolsVersion "21.1.2"
 
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
 
        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')
 
        // Move the build types to build-types/
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src//... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}

当該アプリのbuild.gradleの内容は以下のとおり。

apply plugin: 'android'
 
dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    compile project(':cocos2d:cocos:platform:android:java')
}
 
android {
    compileSdkVersion 10
    buildToolsVersion "21.1.2"
 
    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
            jniLibs.srcDirs = ['libs']
        }
 
        // Move the tests to tests/java, tests/res, etc...
        instrumentTest.setRoot('tests')
 
        // Move the build types to build-types/
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src//... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}
関連情報 cocos2d-x
Android Developers
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[OSX][iOS]UDP

UDP通信のサンプルを作成したので、ざっと説明する。

ADCのサンプルコードUDPEchoを改造して作成した。

UDP通信する部分はクラスにしている。

@protocol UDPDelegate;
 
@interface UDP : NSObject
 
@property (nonatomic, weak,   readwrite) id<UDPDelegate>        delegate;
@property (nonatomic, assign, readonly, getter=isServer) BOOL   server;
@property (nonatomic, copy,   readonly ) NSString *             hostName;
@property (nonatomic, copy,   readonly ) NSData *               hostAddress;
@property (nonatomic, assign, readonly ) NSUInteger             port;
 
- (void)startServerOnPort:(NSUInteger)port;
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port;
- (void)sendData:(NSData *)data;
- (void)stop;
 
@end
 
@protocol UDPDelegate <NSObject>
@optional
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error;
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr;
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error;
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address;
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error;
@end
#import <sys/socket.h>
#import <netinet/in.h>
#import <fcntl.h>
#import <unistd.h>
#import <Foundation/Foundation.h>
 
#if TARGET_OS_EMBEDDED || TARGET_IPHONE_SIMULATOR
#import <CFNetwork/CFNetwork.h>
#else
#import <oreServices/CoreServices.h>
#endif
 
#import "UDP.h"
 
@interface UDP () 
@property (nonatomic, copy,   readwrite) NSString *             hostName;
@property (nonatomic, copy,   readwrite) NSData *               hostAddress;
@property (nonatomic, assign, readwrite) NSUInteger             port;
 
- (void)stopHostResolution;
- (void)stopWithError:(NSError *)error;
- (void)stopWithStreamError:(CFStreamError)streamError;
@end
 
@implementation UDP {
    CFHostRef   _cfHost;
    CFSocketRef _cfSocket;
}
 
- (id)init
{
    NSLog(@"%s", __func__);
    self = [super init];
    if (self != nil) {
    }
    return self;
}
 
- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self stop];
}
 
- (BOOL)isServer
{
    NSLog(@"%s", __func__);
    return self.hostName == nil;
}
 
- (void)sendData:(NSData *)data toAddress:(NSData *)addr
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     sock;
    ssize_t                 bytesWritten;
    const struct sockaddr * addrPtr;
    socklen_t               addrLen;
    
    sock = CFSocketGetNative(self->_cfSocket);
    
    if (addr == nil) {
        addr = self.hostAddress;
        addrPtr = NULL;
        addrLen = 0;
    } else {
        addrPtr = [addr bytes];
        addrLen = (socklen_t) [addr length];
    }
    
    bytesWritten = sendto(sock, [data bytes], [data length], 0, addrPtr, addrLen);
    if (bytesWritten < 0) {
        err = errno;
    } else  if (bytesWritten == 0) {
        err = EPIPE;
    } else {
        err = 0;
    }
    
    if (err == 0) {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didSendData:toAddress:)] ) {
            [self.delegate udp:self didSendData:data toAddress:addr];
        }
    } else {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didFailToSendData:toAddress:error:)] ) {
            [self.delegate udp:self
             didFailToSendData:data
                     toAddress:addr
                         error:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}
 
- (void)readData
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     sock;
    struct sockaddr_storage addr;
    socklen_t               addrLen;
    uint8_t                 buffer[65536];
    ssize_t                 bytesRead;
    
    sock = CFSocketGetNative(self->_cfSocket);
    
    addrLen = sizeof(addr);
    bytesRead = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *) &addr, &addrLen);
    if (bytesRead < 0) {
        err = errno;
    } else if (bytesRead == 0) {
        err = EPIPE;
    } else {
        NSData *dataObj;
        NSData *addrObj;
        
        err = 0;
        
        dataObj = [NSData dataWithBytes:buffer length:(NSUInteger) bytesRead];
        addrObj = [NSData dataWithBytes:&addr  length:addrLen  ];
        
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveData:fromAddress:)] ) {
            [self.delegate udp:self didReceiveData:dataObj fromAddress:addrObj];
        }
        
        if (self.isServer) {
            [self sendData:dataObj toAddress:addrObj];
        }
    }
    
    if (err != 0) {
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didReceiveError:)] ) {
            [self.delegate udp:self
               didReceiveError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
        }
    }
}
 
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    NSLog(@"%s", __func__);
    UDP *udp = (__bridge UDP *)info;
    [udp readData];
}
 
- (BOOL)setupSocketConnectedToAddress:(NSData *)address port:(NSUInteger)port error:(NSError **)errorPtr
{
    NSLog(@"%s", __func__);
    int                     err;
    int                     junk;
    int                     sock;
    const CFSocketContext   context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;
    
    err = 0;
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        err = errno;
    }
    
    if (err == 0) {
        struct sockaddr_in      addr;
        
        memset(&addr, 0, sizeof(addr));
        if (address == nil) {
            addr.sin_len         = sizeof(addr);
            addr.sin_family      = AF_INET;
            addr.sin_port        = htons(port);
            addr.sin_addr.s_addr = INADDR_ANY;
            err = bind(sock, (const struct sockaddr *) &addr, sizeof(addr));
        } else {
            if ([address length] > sizeof(addr)) {
                [address getBytes:&addr length:sizeof(addr)];
            } else {
                [address getBytes:&addr length:[address length]];
            }
            addr.sin_port = htons(port);
            err = connect(sock, (const struct sockaddr *) &addr, sizeof(addr));
        }
        if (err < 0) {
            err = errno;
        }
    }
    
    if (err == 0) {
        int flags;
        
        flags = fcntl(sock, F_GETFL);
        err = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
        if (err < 0) {
            err = errno;
        }
    }
    
    if (err == 0) {
        self->_cfSocket = CFSocketCreateWithNative(NULL, sock, kCFSocketReadCallBack, SocketReadCallback, &context);
        
        sock = -1;
        
        rls = CFSocketCreateRunLoopSource(NULL, self->_cfSocket, 0);
        
        CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
        
        CFRelease(rls);
    }
    
    if (sock != -1) {
        junk = close(sock);
    }
    if ( (self->_cfSocket == NULL) && (errorPtr != NULL) ) {
        *errorPtr = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
    }
    
    return (err == 0);
}
 
- (void)startServerOnPort:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    if (self.port == 0) {
        BOOL        success;
        NSError *   error;
        
        success = [self setupSocketConnectedToAddress:nil port:port error:&error];
        
        if (success) {
            self.port = port;
            
            if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
                CFDataRef   localAddress;
                
                localAddress = CFSocketCopyAddress(self->_cfSocket);
                
                [self.delegate udp:self didStartWithAddress:(__bridge NSData *) localAddress];
                
                CFRelease(localAddress);
            }
        } else {
            [self stopWithError:error];
        }
    }
}
 
- (void)hostResolutionDone
{
    NSLog(@"%s", __func__);
    NSError *           error;
    Boolean             resolved;
    NSArray *           resolvedAddresses;
    
    error = nil;
    
    resolvedAddresses = (__bridge NSArray *) CFHostGetAddressing(self->_cfHost, &resolved);
    if ( resolved && (resolvedAddresses != nil) ) {
        for (NSData * address in resolvedAddresses) {
            BOOL                    success;
            const struct sockaddr * addrPtr;
            NSUInteger              addrLen;
            
            addrPtr = (const struct sockaddr *) [address bytes];
            addrLen = [address length];
            
            success = NO;
            if (
                (addrPtr->sa_family == AF_INET)
                ) {
                success = [self setupSocketConnectedToAddress:address port:self.port error:&error];
                if (success) {
                    CFDataRef   hostAddress;
                    
                    hostAddress = CFSocketCopyPeerAddress(self->_cfSocket);
                    
                    self.hostAddress = (__bridge NSData *) hostAddress;
                    
                    CFRelease(hostAddress);
                }
            }
            if (success) {
                break;
            }
        }
    }
    
    if ( (self.hostAddress == nil) && (error == nil) ) {
        error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil];
    }
    
    if (error == nil) {
        [self stopHostResolution];
        
        if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStartWithAddress:)] ) {
            [self.delegate udp:self didStartWithAddress:self.hostAddress];
        }
    } else {
        [self stopWithError:error];
    }
}
 
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info)
{
    NSLog(@"%s", __func__);
    UDP *udp;
    
    udp = (__bridge UDP *)info;
    
    if ( (error != NULL) && (error->domain != 0) ) {
        [udp stopWithStreamError:*error];
    } else {
        [udp hostResolutionDone];
    }
}
 
- (void)startConnectedToHostName:(NSString *)hostName port:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    if (self.port == 0) {
        Boolean             success;
        CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
        CFStreamError       streamError;
        
        self->_cfHost = CFHostCreateWithName(NULL, (__bridge CFStringRef) hostName);
        
        CFHostSetClient(self->_cfHost, HostResolveCallback, &context);
        
        CFHostScheduleWithRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        
        success = CFHostStartInfoResolution(self->_cfHost, kCFHostAddresses, &streamError);
        if (success) {
            self.hostName = hostName;
            self.port = port;
        } else {
            [self stopWithStreamError:streamError];
        }
    }
}
 
- (void)sendData:(NSData *)data
{
    NSLog(@"%s", __func__);
    if (self.isServer || (self.hostAddress == nil) ) {
    } else {
        [self sendData:data toAddress:nil];
    }
}
 
- (void)stopHostResolution
{
    NSLog(@"%s", __func__);
    if (self->_cfHost != NULL) {
        CFHostSetClient(self->_cfHost, NULL, NULL);
        CFHostCancelInfoResolution(self->_cfHost, kCFHostAddresses);
        CFHostUnscheduleFromRunLoop(self->_cfHost, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
        CFRelease(self->_cfHost);
        self->_cfHost = NULL;
    }
}
 
- (void)stop
{
    NSLog(@"%s", __func__);
    self.hostName = nil;
    self.hostAddress = nil;
    self.port = 0;
    [self stopHostResolution];
    if (self->_cfSocket != NULL) {
        CFSocketInvalidate(self->_cfSocket);
        CFRelease(self->_cfSocket);
        self->_cfSocket = NULL;
    }
}
 
- (void)noop
{
    NSLog(@"%s", __func__);
}
 
- (void)stopWithError:(NSError *)error
{
    NSLog(@"%s", __func__);
    [self stop];
    if ( (self.delegate != nil) && [self.delegate respondsToSelector:@selector(udp:didStopWithError:)] ) {
        [self performSelector:@selector(noop) withObject:nil afterDelay:0.0];
        [self.delegate udp:self didStopWithError:error];
    }
}
 
- (void)stopWithStreamError:(CFStreamError)streamError
{
    NSLog(@"%s", __func__);
    NSDictionary *  userInfo;
    NSError *       error;
    
    if (streamError.domain == kCFStreamErrorDomainNetDB) {
        userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                    [NSNumber numberWithInteger:streamError.error], kCFGetAddrInfoFailureKey,
                    nil
                    ];
    } else {
        userInfo = nil;
    }
    error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
    
    [self stopWithError:error];
}
 
@end

そして、ViewControllerから呼び出す。

#import <UIKit/UIKit.h>
 
@interface ViewController : UIViewController
 
@property (weak, nonatomic) IBOutlet UITextField *inputTextField;
@property (weak, nonatomic) IBOutlet UIButton *sendButton;
@property (weak, nonatomic) IBOutlet UILabel *outputLabel;
 
- (IBAction)send:(id)sender;
 
@end
#import "ViewController.h"
#import "UDP.h"
 
@interface ViewController () <UDPDelegate>
 
@property UDP *server;
@property UDP *client;
 
- (void)runServerOnPort:(NSUInteger)port;
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port;
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self runServerOnPort:3054];
    [self runClientWithHost:@"localhost" port:3054];
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
 
- (IBAction)send:(id)sender
{
    NSLog(@"%s", __func__);
    NSData *data = [[NSString stringWithString:self.inputTextField.text] dataUsingEncoding:NSUTF8StringEncoding];
    [self.client sendData:data];
}
 
- (void)runServerOnPort:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    self.server = [[UDP alloc] init];
    self.server.delegate = self;
    [self.server startServerOnPort:port];
}
 
- (void)runClientWithHost:(NSString *)host port:(NSUInteger)port
{
    NSLog(@"%s", __func__);
    self.client = [[UDP alloc] init];
    self.client.delegate = self;
    [self.client startConnectedToHostName:host port:port];
}
 
- (void)udp:(UDP *)udp didReceiveData:(NSData *)data fromAddress:(NSData *)addr
{
    NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    self.outputLabel.text = msg;
}
 
- (void)udp:(UDP *)udp didReceiveError:(NSError *)error
{
    NSLog(@"%s", __func__);
    self.outputLabel.text = [error description];
}
 
- (void)udp:(UDP *)udp didSendData:(NSData *)data toAddress:(NSData *)addr
{
    NSLog(@"%s data(%@)", __func__, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}
 
- (void)udp:(UDP *)udp didFailToSendData:(NSData *)data toAddress:(NSData *)addr error:(NSError *)error
{
    NSLog(@"%s", __func__);
    NSLog(@"failed with error: %@", [error description]);
}
 
- (void)udp:(UDP *)udp didStartWithAddress:(NSData *)address
{
    NSLog(@"%s", __func__);
}
 
- (void)udp:(UDP *)udp didStopWithError:(NSError *)error
{
    NSLog(@"%s", __func__);
    NSLog(@"failed with error: %@", [error description]);
}
 
@end
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/UDP - GitHub
関連情報 UNIXネットワークプログラミング 第2版 Vol.1
ネットワーキング・プログラミング・トピックス
UDPEcho
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[Android]AsyncTaskとUIスレッド

AsyncTaskの仕様をよく読むと気になることが説明されている。UIスレッドで使用するという箇所だ。

Threading rulesでスレッド関連の説明がされているが、クラスのロードはUIスレッド上で。ただし、JELLY_BEANからは自動で対応している。インスタンスはUIスレッドで生成すること。

ソースを確認してみよう。Android SDKが置かれているディレクトリ配下で以下のコマンドを実行。

$ cd /Applications/Development/android-sdk-macosx
$ find . -name AsyncTask.java -print
./samples/android-19/ui/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./samples/android-21/ui/DisplayingBitmaps/Application/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./sources/android-19/android/os/AsyncTask.java
./sources/android-21/android/os/AsyncTask.java

Android 5.0 Lollipopのソースを確認してみよう。

public abstract class AsyncTask<Params, Progress, Result> {
        :
    private static final InternalHandler sHandler = new InternalHandler();
        :
    /** @hide Used to force static handler to be created. */
    public static void init() {
        sHandler.getLooper();
    }
        :

AsyncTaskのHandlerはクラス変数になっている。そして、HandlerのLooperはクラスメソッドで設定されている。つまり、最初にAsyncTaskのインスタンスを生成する際にHandlerとLooperが保持されることになる。

なぜ、HandlerとLooperが必要か?それは、onCancelledなど、UIスレッドで呼び出すことになっているメソッドがUIスレッドで呼ばれるようにする為だろう。こういうわけだ、AsyncTaskはUIスレッドで生成されることになるので、最初の生成時にUIスレッドのLooperを保持しておいて、別スレッドで処理を終えた後に、このLooperを使って、UIスレッドで結果を返す。

なので、最初のAsyncTaskの生成をUIスレッド以外で行うと、そのスレッドにLooperがない場合は、myLooperがnullの例外が発生することになる。Looperが存在しても、onCencelledなどがUIスレッドとは異なるスレッドで呼ば出されることになってしまう。

気をつけよう。著者はこれでハマった。

関連情報 AsyncTask
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[iOS]動画を90度回転させる(その二)

保存が失敗する原因がわかった。AVMutableVideoCompositionLayerInstructionに設定するトラックが間違えていた!

これで安心して新年をむかえられる。

import UIKit
import AVFoundation
import AssetsLibrary
 
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
 
    @IBOutlet weak var rotateButton: UIButton!
    
    @IBAction func rotate(sender:AnyObject) {
        println(__FUNCTION__)
        
        if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false {
            return
        }
        
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = .SavedPhotosAlbum
        if let mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.SavedPhotosAlbum) {
            mediaUI.mediaTypes = mediaTypes
            NSLog("%s mediaTypes:%@", __FUNCTION__, mediaTypes)
        }
        
        // mediaUI.mediaTypes = [kUTTypeMovie]
        mediaUI.mediaTypes = ["public.movie"]
        
        mediaUI.allowsEditing = false
        
        mediaUI.delegate = self
        
        self.presentViewController(mediaUI, animated: true, completion: nil)
    }
    
    var mutableComposition: AVMutableComposition?
    
    var mutableVideoComposition: AVMutableVideoComposition?
    
    var exportSession: AVAssetExportSession?
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject: AnyObject]) {
        println(__FUNCTION__)
        if info[UIImagePickerControllerMediaURL] != nil {
            /* アセットオブジェクトの作成 */
            let url: NSURL = info[UIImagePickerControllerMediaURL] as NSURL
            var options = [String: Bool]()
            options[AVURLAssetPreferPreciseDurationAndTimingKey] = true
            var asset = AVURLAsset(URL: url, options: options)
            NSLog("%s url:%@", __FUNCTION__, url)
            
            /* アセットから動画/音声トラックを取り出す */
            let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
            let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack
            
            let insertionPoint: CMTime = kCMTimeZero
            var error: NSError? = nil
            
            /* コンポジションを作成 */
            if mutableComposition == nil {
                mutableComposition = AVMutableComposition()
                
                /* 動画コンポジショントラックの作成 */
                let compositionVideoTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 動画データをコンポジションに追加 */
                error = nil
                compositionVideoTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetVideoTrack.timeRange.duration), ofTrack: assetVideoTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertVideoTack error:%@", __FUNCTION__, error!)
                }
                
                /* 音声コンポジショントラックの作成 */
                let compositionAudioTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 音声データをコンポジションに追加 */
                error = nil
                compositionAudioTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetAudioTrack.timeRange.duration), ofTrack: assetAudioTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertAudioTrach error:%@", __FUNCTION__, error!)
                }
            }
            var compositionVideoTrack: AVMutableCompositionTrack? = nil
            var compositionAudioTrack: AVMutableCompositionTrack? = nil
            for track in mutableComposition!.tracks {
                if track.isKindOfClass(AVMutableCompositionTrack) {
                    var mutableCompositionTrack = track as AVMutableCompositionTrack
                    if track.mediaType == AVMediaTypeVideo {
                        compositionVideoTrack = mutableCompositionTrack
                    }
                    else if track.mediaType == AVMediaTypeAudio {
                        compositionAudioTrack = mutableCompositionTrack
                    }
                }
            }
            
            var instruction: AVMutableVideoCompositionInstruction
            var layerInstruction: AVMutableVideoCompositionLayerInstruction
            
            /* 移動して回転 */
            let t1: CGAffineTransform = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0)
            let t2: CGAffineTransform = CGAffineTransformRotate(t1, ((90.0 / 180.0) * 3.14159265358979323846264338327950288))
            
            if mutableVideoComposition == nil {
                /* 動画コンポジションの作成 */
                mutableVideoComposition = AVMutableVideoComposition()
                mutableVideoComposition!.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height, assetVideoTrack.naturalSize.width)
                mutableVideoComposition!.frameDuration = CMTimeMake(1, 30);
                
                /* 動画コンポジション命令 */
                instruction = AVMutableVideoCompositionInstruction()
                instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition!.duration);
                layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack);
                layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                NSLog("%s instruction:%@", __FUNCTION__, instruction)
                NSLog("%s layerInstruction:%@", __FUNCTION__, layerInstruction)
            }
            else {
                mutableVideoComposition!.renderSize = CGSizeMake(mutableVideoComposition!.renderSize.height, mutableVideoComposition!.renderSize.width);
                
                /* 動画コンポジション命令の抽出 */
                instruction = mutableVideoComposition!.instructions[0] as AVMutableVideoCompositionInstruction
                layerInstruction = instruction.layerInstructions[0] as AVMutableVideoCompositionLayerInstruction
                
                /* 内容の確認 */
                var existingTransform = CGAffineTransform(a: 0.0, b: 0.0, c: 0.0, d: 0.0, tx: 0.0, ty: 0.0)
                if layerInstruction.getTransformRampForTime(mutableComposition!.duration, startTransform: &existingTransform, endTransform: nil, timeRange: nil) == false {
                    layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                }
                else {
                    /* 原点補償 */
                    let t3: CGAffineTransform = CGAffineTransformMakeTranslation(-1.0 * assetVideoTrack.naturalSize.height / 2.0, 0.0)
                    let newTransform: CGAffineTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3))
                    layerInstruction.setTransform(newTransform, atTime: kCMTimeZero)
                }
            }
            
            /* コンポジションに命令を追加 */
            instruction.layerInstructions = [layerInstruction]
            mutableVideoComposition!.instructions = [instruction]
            
            /* 出力URL */
            var documentsPath: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
            error = nil
            NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: true, attributes: nil, error: &error)
            if error != nil {
                NSLog("%s createDir error:%@", __FUNCTION__, error!)
            }
            let now = NSDate()
            let dateFormatter = NSDateFormatter()
            dateFormatter.locale = NSLocale(localeIdentifier: "ja_JP")
            dateFormatter.dateFormat = "yyyyMMddHHmmss"
            let filename = String(format: "%@.mp4", arguments: [dateFormatter.stringFromDate(now)])
            var exportPath: NSString = documentsPath.stringByAppendingPathComponent(filename)
            error = nil
            //NSFileManager.defaultManager().removeItemAtPath(exportPath, error: &error)
            if error != nil {
                NSLog("%s removeFile error:%@", __FUNCTION__, error!)
            }
            var exportUrl: NSURL = NSURL.fileURLWithPath(exportPath)!
            
            /* セッションを作成し、フォトライブラリに書き出す */
            exportSession = AVAssetExportSession(asset: mutableComposition!.copy() as AVAsset, presetName: AVAssetExportPresetHighestQuality)
            exportSession!.videoComposition = mutableVideoComposition
            exportSession!.outputURL = exportUrl
            exportSession!.outputFileType = AVFileTypeQuickTimeMovie
            
            exportSession!.exportAsynchronouslyWithCompletionHandler({
                () -> Void in
                NSLog("%@", __FUNCTION__)
                switch self.exportSession!.status {
                case AVAssetExportSessionStatus.Completed:
                    NSLog("%@ AVAssetExportSessionStatus.Completed", __FUNCTION__)
                    let assetsLib = ALAssetsLibrary()
                    assetsLib.writeVideoAtPathToSavedPhotosAlbum(exportUrl, completionBlock: {
                        (nsurl, error) -> Void in
                        if error != nil {
                            NSLog("%@ error:%@", __FUNCTION__, error)
                        }
                    })
                case AVAssetExportSessionStatus.Failed:
                    NSLog("%@ AVAssetExportSessionStatus.Failed exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                case AVAssetExportSessionStatus.Cancelled:
                    NSLog("%@ AVAssetExportSessionStatus.Cancelled exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                default:
                    NSLog("%@ none exporter:%@", __FUNCTION__, self.exportSession!)
                }
            })
        }
        picker.dismissViewControllerAnimated(true, completion: nil);
    }
 
}
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/AVEditor - GitHub
関連情報 AV Foundationプログラミングガイド
AVSimpleEditoriOS
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[iOS]動画を90度回転させる

今年最後の投稿が中途半端な内容となり残念だ。

動画の画面を90度回転させて保存するアプリをSwiftで書いてみたのだが、保存で失敗しまう。なぜだろう。

import UIKit
import AVFoundation
import AssetsLibrary
 
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
    override func viewDidLoad() {
        super.viewDidLoad()
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
 
    @IBOutlet weak var rotateButton: UIButton!
    
    @IBAction func rotate(sender:AnyObject) {
        println(__FUNCTION__)
        
        if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false {
            return
        }
        
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = .SavedPhotosAlbum
        if let mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.SavedPhotosAlbum) {
            mediaUI.mediaTypes = mediaTypes
        }
        
        //mediaUI.mediaTypes = [kUTTypeMovie as NSString]
        //mediaUI.mediaTypes = [kUTTypeMovie!]
        //mediaUI.mediaTypes = [kUTTypeMovie]
        //mediaUI.mediaTypes = NSArray(object: kUTTypeImage)
        //mediaUI.mediaTypes = [String(kUTTypeMovie)]
        
        mediaUI.allowsEditing = false
        
        mediaUI.delegate = self
        
        self.presentViewController(mediaUI, animated: true, completion: nil)
    }
    
    var mutableComposition: AVMutableComposition?
    
    var mutableVideoComposition: AVMutableVideoComposition?
    
    var exportSession: AVAssetExportSession?
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject: AnyObject]) {
        println(__FUNCTION__)
        if info[UIImagePickerControllerMediaURL] != nil {
            /* アセットオブジェクトの作成 */
            let url: NSURL = info[UIImagePickerControllerMediaURL] as NSURL
            var options = [String: Bool]()
            options[AVURLAssetPreferPreciseDurationAndTimingKey] = true
            var asset = AVURLAsset(URL: url, options: options)
            
            /* アセットから動画/音声トラックを取り出す */
            let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
            let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack
            
            let insertionPoint: CMTime = kCMTimeZero
            var error: NSError? = nil
            
            /* コンポジションを作成 */
            if mutableComposition == nil {
                mutableComposition = AVMutableComposition()
                
                /* 動画コンポジショントラックの作成 */
                let compositionVideoTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 動画データをコンポジションに追加 */
                compositionVideoTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetVideoTrack.timeRange.duration), ofTrack: assetVideoTrack, atTime: kCMTimeZero, error: &error)
                
                /* 音声コンポジショントラックの作成 */
                let compositionAudioTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 音声データをコンポジションに追加 */
                compositionAudioTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetAudioTrack.timeRange.duration), ofTrack: assetAudioTrack, atTime: kCMTimeZero, error: &error)
            }
            
            var instruction: AVMutableVideoCompositionInstruction
            var layerInstruction: AVMutableVideoCompositionLayerInstruction
            
            /* 移動して回転 */
            let t1: CGAffineTransform = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0)
            let t2: CGAffineTransform = CGAffineTransformRotate(t1, ((90.0 / 180.0) * 3.14159265358979323846264338327950288))
            
            if mutableVideoComposition == nil {
                /* 動画コンポジションの作成 */
                mutableVideoComposition = AVMutableVideoComposition()
                mutableVideoComposition!.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height, assetVideoTrack.naturalSize.width)
                mutableVideoComposition!.frameDuration = CMTimeMake(1, 30);
                
                /* 動画コンポジション命令 */
                instruction = AVMutableVideoCompositionInstruction()
                instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition!.duration);
                layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: assetVideoTrack);
                layerInstruction.setTransform(t2, atTime: kCMTimeZero)
            }
            else {
                mutableVideoComposition!.renderSize = CGSizeMake(mutableVideoComposition!.renderSize.height, mutableVideoComposition!.renderSize.width);
                
                /* 動画コンポジション命令の抽出 */
                instruction = mutableVideoComposition!.instructions[0] as AVMutableVideoCompositionInstruction
                layerInstruction = instruction.layerInstructions[0] as AVMutableVideoCompositionLayerInstruction
                
                /* 内容の確認 */
                var existingTransform = CGAffineTransform(a: 0.0, b: 0.0, c: 0.0, d: 0.0, tx: 0.0, ty: 0.0)
                if layerInstruction.getTransformRampForTime(mutableComposition!.duration, startTransform: &existingTransform, endTransform: nil, timeRange: nil) == false {
                    layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                }
                else {
                    /* 原点補償 */
                    let t3: CGAffineTransform = CGAffineTransformMakeTranslation(-1.0 * assetVideoTrack.naturalSize.height / 2.0, 0.0)
                    let newTransform: CGAffineTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3))
                    layerInstruction.setTransform(newTransform, atTime: kCMTimeZero)
                }
            }
            
            /* コンポジションに命令を追加 */
            instruction.layerInstructions = [layerInstruction]
            mutableVideoComposition!.instructions = [instruction]
            
            /* 出力URL */
            var documentsPath: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
            //NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: true, attributes: nil, error: &error)
            var exportPath: NSString = documentsPath.stringByAppendingPathComponent("output.mp4")
            //NSFileManager.defaultManager().removeItemAtPath(exportPath, error: &error)
            var exportUrl: NSURL = NSURL.fileURLWithPath(exportPath)!
            
            /* セッションを作成し、フォトライブラリに書き出す */
            exportSession = AVAssetExportSession(asset: mutableComposition!.copy() as AVAsset, presetName: AVAssetExportPresetHighestQuality)
            exportSession!.videoComposition = mutableVideoComposition
            exportSession!.outputURL = exportUrl
            exportSession!.outputFileType = AVFileTypeQuickTimeMovie
            
            exportSession!.exportAsynchronouslyWithCompletionHandler({
                () -> Void in
                NSLog("%@", __FUNCTION__)
                //NSFileManager.defaultManager().removeItemAtPath(self.filePath(self.mov_extenstion), error: nil)
                switch self.exportSession!.status {
                case AVAssetExportSessionStatus.Completed:
                    NSLog("%@ AVAssetExportSessionStatus.Completed", __FUNCTION__)
                    let assetsLib = ALAssetsLibrary()
                    assetsLib.writeVideoAtPathToSavedPhotosAlbum(exportUrl, completionBlock: {
                        (nsurl, error) -> Void in
                        NSLog("%@ error:%@", __FUNCTION__, error)
                    })
                case AVAssetExportSessionStatus.Failed:
                    NSLog("%@ AVAssetExportSessionStatus.Failed exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                case AVAssetExportSessionStatus.Cancelled:
                    NSLog("%@ AVAssetExportSessionStatus.Cancelled exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                default:
                    NSLog("%@ none exporter:%@", __FUNCTION__, self.exportSession!)
                }
            })
        }
        picker.dismissViewControllerAnimated(true, completion: nil);
    }
 
}
ソースコード GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/AVEditor - GitHub
関連情報 AV Foundationプログラミングガイド
AVSimpleEditoriOS
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

[OSX][iOS]Swiftをコマンドラインで利用する

SwiftのPlaygroundは、試行錯誤する際に便利なのだが、コマンドラインを利用する身としては、標準入出力を使ったデータの受け渡しが楽でないことに不満があった。

以前から、Swiftをコマンドラインで利用する方法の情報はネットにあったが、自分にとってスマートな方法でないように思えて、利用する気にならなかったのだが、Swift Blogで、コマンドラインでの利用方法について説明があり、自分にとって気に入った方法なので紹介することにする。

Introduction to the Swift REPL

以下のコマンドを叩けば、対話型のインタフェースが利用できる。

$ xcrun swift
Welcome to Swift!  Type :help for assistance.
  1>  

抜けたい場合は、Ctrl+Dという事だと思う。

では、スクリプト・ファイルのように記述するには?
以下のように記述すればいい。

$ cat demo.swift


#!/usr/bin/env xcrun swift
 
var str = "Hello World"
 
println(str)
 
/* End Of File */  

実行。

$ chmod +x demo.swift 
$ ./demo.swift 
Hello World
関連情報 Swift Blog
【Cocoa練習帳】 http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)
note

[Android]EclipseのWorkspace

恥ずかしながら最近知った。EclipseのWorkspaceを各種プロジェクト置き場と考えていたが、プロジェクト毎にWorkspaceを用意するのが適切なようだ。


Cocos2d-xのプロジェクトも、例えば、「/Users/ユーザ名/Documents/Development/Projects/MyGame」というパスのプロジェクトを生成した場合、ここをEclipseのWorkspaceのパスにするのが良いようだ。


関連情報

【Cocoa練習帳】
http://ameblo.jp/bitz/(ミラー・サイト)