1. 程式人生 > >二、VR全景圖顯示器開發 ---- Android VR視訊/Google VR for Android /VR Pano/VR Video

二、VR全景圖顯示器開發 ---- Android VR視訊/Google VR for Android /VR Pano/VR Video

這篇看下SimpleVrPanorama這個栗子

SimpleVrPanorama

其實這篇應該寫SimpleVrPanorama和simplevideowidget 兩個,但是由於篇幅過長就分開寫了

演示

用AS錄的沒有觸控點顯示,先湊合看吧

預覽圖觀看

介紹

官方在這裡介紹了VR view 、支援平臺等。我挑幾個相對重要的介紹一下:
1、影象規格

VR檢視影象可以儲存為PNG,JPEG或GIF。Google建議使用JPEG改進壓縮。  
為了獲得最大的相容性和效能,影象尺寸應該是2的倍數(例如,2048或4096)。
單個影象應為2:1縱橫比(例如4096×2048)。  
立體影象應為1:1縱橫比(例如4096×4096)。

如圖:

2、 視訊規格

VR view視訊應該被儲存為H264編碼的mp4檔案。
單個視訊應是2:1縱橫比。
立體視訊應是1:1縱橫比。
一些較舊的裝置不能解碼的視訊最大不能超過超過1080(192​​0×1080)。最大的相容性和質量是頭等大事,Google建議使用者同時提供平面視覺1920x1080的視訊和2048×2048處以上的立體視訊。  

3、如何錄製VR視訊

生活中拍攝:

360度拍攝的照片和視訊越來越方便和實惠。 VR檢視可以使用由支援上述equirect-全景格式的任何攝像機產生的影象。對於有興趣在快速入門使用者來說,我們最喜歡的解決方案如下:

Cardboard Camera App

:這個免費的Andr​​oid應用程式,允許使用者快速捕捉立體影象360。

Ricoh Theta:一個非常流行的,用於捕獲單360度的影象和視訊相對廉價的解決方案。

CG(計算機動畫)拍攝:

遙感影像資料的VR觀點並沒有從現實世界限於捕獲。 CGI軟體可以生成360影象和視訊,一切從建築到演練預演的電影。我們的一些最流行的捕獲解決方案的列舉如下:

Unreal(虛幻):UE4的最新版本內建了360捕獲解決方案。

Renderman:開源庫,用於捕捉360的內容。

Android平臺

這裡官方有這Android平臺的詳細介紹,主要內容如下:

有這表明在官方SDK中的VR View 功能的兩個示例應用程式:simplepanowidget

simplevideowidget。這兩個樣品的是顯示分別使用VrPanoramaViewVrVideoView嵌入全景影象和視訊。
這裡寫圖片描述
允許使用者通過旋轉他們的電話,看全景的不同部分。
simplevideowidget示例還允許使用者暫停(點選 VR View就暫停了。 VR View也就是視訊那個區域),可以使用進度條改變進度。允許使用者更改模式,分別是全屏模式紙板模式

全屏模式:
這裡寫圖片描述

紙板模式:
這裡寫圖片描述

程式碼分析

(^_^ 為了方便學習與理解,基於官方Demo的程式碼進行了修改 )

前言

這個栗子中需要注意幾個知識點:

 VrPanoramaView //Google提供給我們現實全景圖片的View
 Options //VrPanoramaView 所需的設定
 VrPanoramaEventListener//為 VrPanoramaView 設定監聽
 loadImageFromBitmap//載入圖片的主要方法

AndroidManifest

    <!--Demo需要的兩個許可權-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application android:label="SimpleVrPanoramaActivity"
      android:largeHeap="true"
      android:theme="@android:style/Theme.Holo.Light">
        <!-- 這裡原本有個 launchMode ,方便閱讀 把這個和Activity中相關程式碼(onNewIntent) 刪掉了 後面部介-->
        <activity android:name=".SimpleVrPanoramaActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="com.google.intent.category.CARDBOARD" />
            </intent-filter>
        </activity>
    </application>

build.gradle

dependencies {
    compile project(':libraries-common')
    compile project(':libraries-commonwidget')
    compile project(':libraries-panowidget')
}

佈局檔案

只有一個主要標籤

  <com.google.vr.sdk.widgets.pano.VrPanoramaView
        android:id="@+id/pano_view"
        android:layout_width="match_parent"
        android:layout_height="250dip"
        android:layout_margin="5dip"
        android:scrollbars="@null"/>

SimpleVrPanoramaActivity


package com.google.vr.sdk.samples.simplepanowidget;

import android.app.Activity;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;

import com.google.vr.sdk.widgets.pano.VrPanoramaEventListener;
import com.google.vr.sdk.widgets.pano.VrPanoramaView;
import com.google.vr.sdk.widgets.pano.VrPanoramaView.Options;

import java.io.IOException;
import java.io.InputStream;

public class SimpleVrPanoramaActivity extends Activity {

    private static final String TAG = "VrPanorama";
    private VrPanoramaView panoWidgetView;//上面說的Google提供給我們現實全景圖片的View
    private String fileUri = "first.jpg";//assets資料夾下的檔名

    private Options panoOptions = new Options();//VrPanoramaView需要的設定
    private ImageLoaderTask backgroundImageLoaderTask;//非同步載入圖片

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);//佈局上面貼了

        panoWidgetView = (VrPanoramaView) findViewById(R.id.pano_view);//初始化VrPanoramaView
        panoWidgetView.setEventListener(new ActivityEventListener());//為VrPanoramaView新增監聽

        //如果有任務在執行則停止它
        if (backgroundImageLoaderTask != null) {
            backgroundImageLoaderTask.cancel(true);
        }
         //設定inputType 為TYPE_STEREO_OVER_UNDER. 在後面會介紹TYPE_STEREO_OVER_UNDER的,暫時當做一個圖片的顯示型別就行
        panoOptions.inputType = Options.TYPE_STEREO_OVER_UNDER;
        //建立一個任務
        backgroundImageLoaderTask = new ImageLoaderTask();
        //執行任務。將圖片名(根據專案實際情況傳吧)和設定傳入
        backgroundImageLoaderTask.execute(Pair.create(fileUri, panoOptions));
    }
        //非同步任務
    class ImageLoaderTask extends AsyncTask<Pair<String, Options>, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Pair<String, Options>... fileInformation) {//真正寫專案根據情況新增條件判斷吧

            InputStream istr = null;
            try {
                istr = getAssets().open(fileInformation[0].first);//獲取圖片的輸入流
            } catch (IOException e) {
                Log.e(TAG, "Could not decode default bitmap: " + e);
                return false;
            }

            Bitmap bitmap = BitmapFactory.decodeStream(istr);//建立bitmap
            panoWidgetView.loadImageFromBitmap(bitmap, fileInformation[0].second);//引數一為圖片的bitmap,引數二為 VrPanoramaView 所需要的設定

            try {
                istr.close();//關閉InputStream
            } catch (IOException e) {
                Log.e(TAG, "Could not close input stream: " + e);
            }

            return true;
        }
    }

    private class ActivityEventListener extends VrPanoramaEventListener {

        @Override
        public void onLoadSuccess() {//圖片載入成功
            Log.e(TAG, "onLoadSuccess");
        }


        @Override
        public void onLoadError(String errorMessage) {//圖片載入失敗
            Log.e(TAG, "Error loading pano: " + errorMessage);
        }

        @Override
        public void onClick() {//當我們點選了VrPanoramaView 時候出發
            super.onClick();
            Log.e(TAG, "onClick");
        }

        @Override
        public void onDisplayModeChanged(int newDisplayMode) {//改變顯示模式時候出發(全屏模式和紙板模式)
            super.onDisplayModeChanged(newDisplayMode);
            Log.e(TAG, "onDisplayModeChanged");
        }
    }


    @Override
    protected void onPause() {
        panoWidgetView.pauseRendering();//暫停3D渲染和跟蹤
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        panoWidgetView.resumeRendering();//恢復3D渲染和跟蹤
    }

    @Override
    protected void onDestroy() {
        panoWidgetView.shutdown();//關閉渲染下並釋放相關的記憶體

        if (backgroundImageLoaderTask != null) {
            backgroundImageLoaderTask.cancel(true);//停止非同步任務
        }
        super.onDestroy();
    }
}

看完了有木有感覺炒雞簡單啊?現在你已經掌握瞭如何使用 VrPanoramaView 了吧。

用 VrPanoramaView 的確簡單,但是侷限性特別大,後面有機會 會詳細說的。

再介紹下程式碼中沒提到的兩個方法:

setFullscreenButtonEnabled (false); //隱藏全屏模式按鈕
setVrModeButtonEnabled(false); //隱藏VR模式按鈕

Options

接下來看看剛剛的VrPanoramaView.Options吧,上文中 是這麼設定的

panoOptions.inputType = Options.TYPE_STEREO_OVER_UNDER;

那麼為什麼要這樣設定呢?先看官方對Options中標籤的介紹:

public static final int TYPE_MONO = 1;

    影象被預期以覆蓋沿著其水平軸360度,而垂直範圍是根據影象的寬高比來計算。例如,如果一個1000x250畫素的影象,給出所述全景將覆蓋360x90度與垂直範圍是-45至+45度。  

public static final int TYPE_STEREO_OVER_UNDER = 2;
    包含兩個大小相等的投影 全景圖垂直疊加。頂部影象被顯示給左眼、底部影象被顯示給右眼。//看下圖你就懂了   

    影象將覆蓋沿水平軸360度,而垂直範圍是根據影象的寬高比來計算。例如,如果一個1000x500畫素的影象中給出(即1000x250畫素每個眼睛),全景將覆蓋360x90度與垂直範圍是-45至+45度。

這裡寫圖片描述

我要顯示的圖片是下圖這樣的,所以就要設定為 ‘TYPE_STEREO_OVER_UNDER’
這裡寫圖片描述

那麼什麼樣的圖片設定為 ‘TYPE_MONO’ 呢?
請看:
這裡寫圖片描述

不知道有沒有眼神好的同學發現這個問題:TYPE_STEREO_OVER_UNDER型別的圖片每次切換模式時候 圖片中間都會有一條垂直於水平線的分割線(很淺 很淺 然後逐漸消失),TYPE_MONO 就沒有 ^_^

Options類中的程式碼也十分簡單

public static class Options {
        private static final int TYPE_START_MARKER = 0;//起始標記
        public static final int TYPE_MONO = 1;
        public static final int TYPE_STEREO_OVER_UNDER = 2;
        private static final int TYPE_END_MARKER = 3;//結束標記
        public int inputType = 1;//預設為一

        public Options() {
        }

        void validate() {
            if(this.inputType <= 0 || this.inputType >= 3) {//標記錯誤處理
                String var10000 = VrPanoramaView.TAG;
                int var1 = this.inputType;
                Log.e(var10000, (new StringBuilder(38)).append("Invalid Options.inputType: ").append(var1).toString());
                this.inputType = 1;
            }

        }
    }

調皮的你如果在loadImageFromBitmap(bitmap,options)方法中 將options不小心設定為null了,也沒關係。我在原始碼中我發現下面的程式碼,感覺挺溫馨的

public void loadImageFromBitmap(Bitmap bitmap, VrPanoramaView.Options options) {
        //有木有那裡暖暖的 ^_^
        if(options == null) {
            options = new VrPanoramaView.Options();
        } else {
            options.validate();
        }
    //重點不在這裡 可以無視它
        this.renderer.loadImageFromBitmap(bitmap, options, this.eventListener);
    }

至此com.google.vr.sdk.widgets.common包、com.google.vr.sdk.widgets.pano包和com.google.vr.sdk.widgets.video包(程式碼下一篇介紹) 的主要內容都介紹完了,總結下吧

總結

總結下如何在Android裝置上用Google的SDK做一款全景圖的顯示器(播放器?檢視器?… 不知道叫什麼合適):

  1. 匯入google的庫
  2. 在相應的佈局檔案中引入控制元件 com.google.vr.sdk.widgets.pano.VrPanoramaView
  3. 初始化控制元件
  4. 為VrPanoramaView設定options
  5. 找到圖片的Bitmap
  6. 呼叫VrPanoramaView的loadImageFromBitmap方法
  7. 在onPause、onResume、onDestroy中做出相應處理