[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-xAndroid 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のパスにするのが良いようだ。