OculusとTHETAでリアルタイムの旅をしよう〜その2

その1は[こちら](http://qiita.com/sandinist/items/979c741ef5223e9637a0)

# サービス概要

遠くの場所を、リアルタイムに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

## サービス全体像
![サービス全体像.png](https://qiita-image-store.s3.amazonaws.com/0/2798/19367e26-6122-0a2c-6c53-2bea33c41f5d.png)

# 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 のみです。

実現するシーケンスは下記の通りです。

1. Connectをタップし、接続する。この時、一覧の取得は行わないようにします。
2. Captureをタップし、インターバル撮影を開始する。
3. 写真が撮影されたら、画像の引取を自動で行う
4. 引取が完了したら、そのままAPIへ画像の投稿を行う
5. 3,4を撮影が停止するまで繰り返す

### 1.接続処理
Connectタップ後の処理では、一覧の表示をなくします。

```objc
- (void)connect
{
  ...

  // Start enum objects.
  // [self enumObjects]; <- コメントアウトする

```

### 2.インターバル撮影の開始
Captureタップ後の処理では、インターバル撮影の開始をします。
撮影枚数の指定はなしにして、THETAの空きとバッテリーがある限り撮影させます。
撮影間隔は15秒に1枚にします。iPhoneの回線状態が良ければ10秒でも良いかもしれません。

```objc
- (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: を呼び出すように変更します。

```objc
-(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: を呼び出します。

```objc
// - (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] には[前回](http://qiita.com/sandinist/items/979c741ef5223e9637a0) 作成したアプリの名前が入ります。

```objc
- (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で画像を見るところをつくります。

その3: http://qiita.com/sandinist/items/6e2b9856c9273d580fa2