その1はこちら
サービス概要
遠くの場所を、リアルタイムに360度体験できるサービスをなるべく簡単に作ります。
ゴールイメージ
- 作成したアプリの入ったiPhoneとTHETAを持って旅にでる
- 家で待っている人が、旅先の風景をOculusでリアルタイムに360°体験できる
使ったもの
Hardware
- THETA m15
- Oculus Rift DK2
- iPhone 6
- MacBook Air (13-inch, Mid 2012)
Software
- Ruby 2.1.5
- Rails 4.1.8
- Unity
- Xcode 6.1
サービス全体像
iOS App 編
- 今回は (1)iOS App 部分をつくります。
前提条件
- その1ができてること
- XCode 6.1がインストールされている、Macがあること
- iPhone実機で実行できる環境がないと寂しいです
- RICOH THETA があること
RICOH THETA SDKの準備
https://developers.theta360.com/ja/docs/sdk/download.html から RICOH_THETA_SDK_for_iOS.0.2.1.zip をダウンロードして任意の場所に解凍します。(開発者登録が必要です。)
ricoh-theta-sample-for-ios.xcodeproj を開いて、Xcodeから実機で実行してみましょう。
THETAと接続して、接続、一覧の取得・表示、写真の引取等が動作することを確認して下さい。
作成するアプリの説明
画面はSDK付属のサンプルアプリをそのまま利用します。
変更するのは ViewController.m のみです。
実現するシーケンスは下記の通りです。
- Connectをタップし、接続する。この時、一覧の取得は行わないようにします。
- Captureをタップし、インターバル撮影を開始する。
- 写真が撮影されたら、画像の引取を自動で行う
- 引取が完了したら、そのままAPIへ画像の投稿を行う
- 3,4を撮影が停止するまで繰り返す
1.接続処理
Connectタップ後の処理では、一覧の表示をなくします。
- (void)connect
{
...
// Start enum objects.
// [self enumObjects]; <- コメントアウトする
2.インターバル撮影の開始
Captureタップ後の処理では、インターバル撮影の開始をします。
撮影枚数の指定はなしにして、THETAの空きとバッテリーがある限り撮影させます。
撮影間隔は15秒に1枚にします。iPhoneの回線状態が良ければ10秒でも良いかもしれません。
- (IBAction)onCaptureClicked:(id)sender
{
...
// This block is running at PtpConnection#gcd thread.
// BOOL rtn = [session initiateCapture]; <- コメントアウト
// 追加ここから
[session setUint16PropValue:PTPDP_TIMELAPSE_NUMBER value:0]; // 撮影枚数の指定なし(撮影し続ける)
[session setUint32PropValue:PTPDP_TIMELAPSE_INTERVAL value:15000]; // 15秒に1枚撮影を指定
[session setUint16PropValue:PTPDP_STILL_CAPTURE_MODE value:PTPIP_STILL_CAPTURE_MODE_TIMELAPSE]; //インターバル撮影モードを指定
BOOL rtn = [session initiateOpenCapture]; // インターバル撮影開始
// 追加ここまで
3.撮影完了後に画像の引取をおこなう
撮影完了のイベントを受信したら、loadObject: session: を呼び出すように変更します。
-(void)ptpip_eventReceived:(int)code :(uint32_t)param1 :(uint32_t)param2 :(uint32_t)param3
{
...
[_ptpConnection operateSession:^(PtpIpSession *session) {
// コメントアウトここから
// [_objects addObject:[self loadObject:param1 session:session]];
// NSIndexPath* pos = [NSIndexPath indexPathForRow:_objects.count-1 inSection:1];
// dispatch_async_main(^{
// [_contentsView beginUpdates];
// [_contentsView insertRowsAtIndexPaths:@[pos]
// withRowAnimation:UITableViewRowAnimationRight];
// [_contentsView endUpdates];
// });
// コメントアウトここまで
// 追加ここから
[self loadObject:param1 session:session];
// 追加ここまで
}];
loadObject は戻り値が不要になるので、メソッドごと書き換えます。
画像のデータを取り出し、postEqui: を呼び出します。
// - (PtpObject*)loadObject:(uint32_t)objectHandle session:(PtpIpSession*)session { ... }
- (void)loadObject:(uint32_t)objectHandle session:(PtpIpSession*)session
{
PtpIpObjectInfo* objectInfo = [session getObjectInfo:objectHandle];
if (!objectInfo) return;
if (objectInfo.object_format==PTPIP_FORMAT_JPEG) {
NSMutableData* imageData = [NSMutableData data];
BOOL result = [session getResizedImageObject:objectHandle width:2048 height:1024
onStartData:^(NSUInteger totalLength) {
NSLog(@"getResizedImageObject(0x%08x) will received %zd bytes.", objectHandle, totalLength);
} onChunkReceived:^BOOL(NSData *data) {
[imageData appendData:data];
return YES;
}];
if (result) [self postEqui:imageData];
}
}
4.画像の引取後、APIへ画像の投稿を行う
postEqui: ではメソッド名の通り、Equirectangular画像のpostを行います。
[app name] には前回 作成したアプリの名前が入ります。
- (void)postEqui:(NSData*)equi
{
NSURL* url = [NSURL URLWithString:@"https://[app name].herokuapp.com/equi"];
const NSString* boundaryConstant = @"----------V2ymHFg03ehbqgZCaKO6jy";
const NSString* fileParamConstant = @"img";
NSURLSession* session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:@"POST"];
NSString* contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundaryConstant];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
NSMutableData* body = [NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n", boundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fileParamConstant, @"img"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Type: image/jpeg\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:equi];
[body appendData:[[NSString stringWithFormat:@"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundaryConstant] dataUsingEncoding:NSUTF8StringEncoding]];
NSString *postLength = [NSString stringWithFormat:@"%zu", (unsigned long)[body length]];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
NSURLSessionUploadTask * uploadTask = [session uploadTaskWithRequest:request fromData:body
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async_main(^{
if (!error) {
[self appendLog:[NSString stringWithFormat:@"POST failed(%@)", error]];
}
else {
[self appendLog:@"POST success"];
}
});
}];
[uploadTask resume];
}
5. 3, 4を撮影が停止するまで繰り返す
ptpip_eventReceived は画像の撮影完了時に都度呼ばれるので、特に追加で行うことはありません。
iOSアプリを動かして、アップロード後に、下記のコマンドをshellから実行して、画像がアップロードされているか確認してみましょう。ここでも [app name] は前回作成したものです。
$ curl -o theta.jpg https://[app name].herokuapp.com/equi/dl && open theta.jpg
つづき
だいぶ時間が空いてしまいましたが、最終回はUnityでAPIから画像をダウンロードしてきて、リアルタイムにOculusで画像を見るところをつくります。