The memo blog.

About programming language and useful library.

Vuforia-II: Camera(II)

| Comments

經過比對vuforia的Sample code更加確定了幾件事情,就是很多基本的Code都是可以重複用的,例如:Initial的流程或是jni的設定等,大部份都是一樣的。除了特別用途需要用到特別的功能之外,基本上架構都是一樣。基於這個原因,我開始把可以重復用的部分抽離出來,建立一個Template,以後在用的時候就可以直接拿這個Template來新增、修改或是弄些比較有趣的Demo。我也將我所完成的Code放到github上,也趁機熟悉一下git版本控制。老實說一個main_activity就要1xxx行,對我來講,真是有點看不下去。大致上知道流程,所以趁著還記憶猶新的時候,開始備忘一下。

cameraDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 @Override
  protected void onCreate(Bundle savedInstanceState) {
      DebugLog.LOGD("cameraDemo", "cameraDemo::onCreate");
      super.onCreate(savedInstanceState);
      // Load any sample specific textures:
      // mTextures = new Vector<Texture>();
      // loadTextures();
      // Query the QCAR initialization flags:
      mQCARFlags = getInitializationFlags();
      // Creates the GestureDetector listener for processing double tap
      // mGestureDetector = new GestureDetector(this, new GestureListener());
      // Update the application status to start initializing application:
      updateApplicationStatus(APPSTATUS_INIT_APP);
  }

首先我們針對onCreate的程式進入點當中來進行說明。在這邊當中,其實有些部分可以先行拿掉,如果只是要開啟Camera的功能的話,首先前面的DebugLogsuper.onCreate等說明應該不用了。在loadTextures()當中,就是(前一篇Vuforia-II Camera)[http://cychiang.github.com/blog/2013/02/04/vuforia-ii-camera/]所提到的對於電腦圖學我需要的一些東西,詳細可以參考一下Wiki的說明,簡單說就是貼皮膚上去!就像穿衣服。而在getInitializationFlags();當中,主要是偵測Device支援的OpenGL ES的版本,那這部分會再呼叫Native code來解決,這部分未來會針對Native code部分進行說明。

cameraDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
 /** Configure QCAR with the desired version of OpenGL ES. */
  private int getInitializationFlags() {
      int flags = 0;

      // Query the native code:
      if (getOpenGlEsVersionNative() == 1) {
          flags = QCAR.GL_11;
      } else {
          flags = QCAR.GL_20;
      }

      return flags;
  }

最後會去呼叫updateApplicationStatus()這部份是用來更新目前的程式狀態,Demo程式寫的挺詳細的,把每個狀態該進行哪些工作都說明的很清楚。接著我們來看updateApplicationStatus()的實作內容。有點長,但大部份都是註解,有時候真的不得不佩服一些公司的Code註解寫的相當清楚Trace沒煩惱。在程式這邊的狀態定義為:

1
2
3
4
5
6
7
8
9
10
// Application status constants:
private static final int APPSTATUS_UNINITED = -1;
private static final int APPSTATUS_INIT_APP = 0;
private static final int APPSTATUS_INIT_QCAR = 1;
private static final int APPSTATUS_INIT_TRACKER = 2;
private static final int APPSTATUS_INIT_APP_AR = 3;
private static final int APPSTATUS_LOAD_TRACKER = 4;
private static final int APPSTATUS_INITED = 5;
private static final int APPSTATUS_CAMERA_STOPPED = 6;
private static final int APPSTATUS_CAMERA_RUNNING = 7;

先釐清之後會比較了解之後程式的走向。在onCreate()當中,傳入的參數為APPSTATUS_INIT_APP,也就是0對應到接下來的updateApplicationStatus(int appStatus)當中會是在case APPSTATUS_INIT_APP:的區段裡面,而這裡面的執行內容是先執行initApplication()完成之後,執行updateApplicationSatus(APPSTATUS_INIT_QCAR),也就是說,執行完成後會再重新進入這個function而帶入的參數是APPSTATUS_INIT_QCAR接下來,我們看一下initApplication()在進行哪些事情。

cameraDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 /** Initialize application GUI elements that are not related to AR. */
  private void initApplication() {
      // Set the screen orientation:
      // NOTE: Use SCREEN_ORIENTATION_LANDSCAPE or SCREEN_ORIENTATION_PORTRAIT
      // to lock the screen orientation for this activity.
      int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR;

      // This is necessary for enabling AutoRotation in the Augmented View
      if (screenOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
          // NOTE: We use reflection here to see if the current platform
          // supports the full sensor mode (available only on Gingerbread
          // and above.
          try {
              // SCREEN_ORIENTATION_FULL_SENSOR is required to allow all
              // 4 screen rotations if API level >= 9:
              Field fullSensorField = ActivityInfo.class
                      .getField("SCREEN_ORIENTATION_FULL_SENSOR");
              screenOrientation = fullSensorField.getInt(null);
          } catch (NoSuchFieldException e) {
              // App is running on API level < 9, do nothing.
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

      // Apply screen orientation
      setRequestedOrientation(screenOrientation);

      updateActivityOrientation();

      // Query display dimensions:
      storeScreenDimensions();

      // As long as this window is visible to the user, keep the device's
      // screen turned on and bright:
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
              WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  }

在這段function似乎沒有進行什麼特別的init,全不可以看到的就是偵測目前裝置的方向就是你是拿直的還是橫的情況,而也會有一個function來記錄目前screen的狀態,應該是可以不用特別去了解細節。可能是因為要Render東西需要考慮方向性問題吧!好了,既然知道這邊是確定螢幕的方向內容,那可以先繼續下一步,也就是updateApplicationStatus(APPSTATUS_INIT_QCAR);,這邊在case APPSTATUS_INIT_QCAR當中主要是建立一個Task而這個TaskInitQCARTask(),接下來就進入我們InitQCARTask()來進行說明。然而需要注意一點就是,這邊在實現上為另外建立一個Task所以這邊是先mInitQCARTask = new InitQCARTask();接著mInitQCARTask.execute()

cameraDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 /** An async task to initialize QCAR asynchronously. */
  private class InitQCARTask extends AsyncTask<Void, Integer, Boolean> {
      // Initialize with invalid value:
      private int mProgressValue = -1;

      protected Boolean doInBackground(Void... params) {
          // Prevent the onDestroy() method to overlap with initialization:
          synchronized (mShutdownLock) {
              QCAR.setInitParameters(cameraDemo.this, mQCARFlags);

              do {
                  // QCAR.init() blocks until an initialization step is
                  // complete, then it proceeds to the next step and reports
                  // progress in percents (0 ... 100%).
                  // If QCAR.init() returns -1, it indicates an error.
                  // Initialization is done when progress has reached 100%.
                  mProgressValue = QCAR.init();

                  // Publish the progress value:
                  publishProgress(mProgressValue);

                  // We check whether the task has been canceled in the
                  // meantime (by calling AsyncTask.cancel(true)).
                  // and bail out if it has, thus stopping this thread.
                  // This is necessary as the AsyncTask will run to completion
                  // regardless of the status of the component that
                  // started is.
              } while (!isCancelled() && mProgressValue >= 0
                      && mProgressValue < 100);

              return (mProgressValue > 0);
          }
      }

      protected void onProgressUpdate(Integer... values) {
          // Do something with the progress value "values[0]", e.g. update
          // splash screen, progress bar, etc.
      }

      protected void onPostExecute(Boolean result) {
          // Done initializing QCAR, proceed to next application
          // initialization status:
          if (result) {
              DebugLog.LOGD("cameraDemo",
                      "InitQCARTask::onPostExecute: QCAR "
                              + "initialization successful");

              updateApplicationStatus(APPSTATUS_INIT_TRACKER);
          } else {
              // Create dialog box for display error:
              AlertDialog dialogError = new AlertDialog.Builder(
                      cameraDemo.this).create();

              dialogError.setButton(DialogInterface.BUTTON_POSITIVE, "Close",
                      new DialogInterface.OnClickListener() {
                          public void onClick(DialogInterface dialog,
                                  int which) {
                              // Exiting application:
                              System.exit(1);
                          }
                      });

              String logMessage;

              // NOTE: Check if initialization failed because the device is
              // not supported. At this point the user should be informed
              // with a message.
              if (mProgressValue == QCAR.INIT_DEVICE_NOT_SUPPORTED) {
                  logMessage = "Failed to initialize QCAR because this "
                          + "device is not supported.";
              } else {
                  logMessage = "Failed to initialize QCAR.";
              }

              // Log error:
              DebugLog.LOGE("cameraDemo", "InitQCARTask::onPostExecute: "
                      + logMessage + " Exiting.");

              // Show dialog box with error message:
              dialogError.setMessage(logMessage);
              dialogError.show();
          }
      }
  }
  

在這段InitQCARTask()主要的protected Boolean doInBackground(Void... params)裡面,就兩件事情比較重要QCAR.setInitParameters(cameraDemo.this, mQCARFlags);QCAR.init()但是!QCAR.setInitParameters()有點奇怪,因為在官方API當中裡面參數只有一個,這是官方API寫的int QCAR_API QCAR::setInitParameters ( int flags )所以讓我覺得有點奇怪,為什麼會多一個Activity的參數,不過既然都需要,就塞進去吧。最後還需要一個QCAR.init()在這邊官方API說明為Initializes QCAR.詳細怎樣Initializes也沒有說明很清楚。不過有一個iOS的說明,整段說明為:iOS: Called to initialize QCAR. Initialization is progressive, so this function should be called repeatedly until it returns 100 or a negative value. Returns an integer representing the percentage complete (negative on error).,所以,看來就是這一個doInBackground(Void… params)會一直執行直到mProgressValue為100!整個就是讀取條,很酷。那基本上這邊就是只有進行Initialization QCAR的功用,最後完成時會進入到onPostExecute(),然後呼叫updateApplicationStatus(APPSTATUS_INIT_TRACKER);這邊又是另外一段function的開始,我在想,不要搞Task直接用應該也是可以,只是可能比較不保險!?接下來我們看一下case APPSTATUS_INIT_TRACKER:主要有一個function就是initTracker()這個是native code不得已只好先跳到native codetrace

cameraDemo.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
JNIEXPORT int JNICALL
Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_initTracker(JNIEnv *, jobject)
{
    LOG("Java_com_qualcomm_QCARSamples_ImageTargets_ImageTargets_initTracker");

    // Initialize the image tracker:
    QCAR::TrackerManager& trackerManager = QCAR::TrackerManager::getInstance();
    QCAR::Tracker* tracker = trackerManager.initTracker(QCAR::Tracker::IMAGE_TRACKER);
    if (tracker == NULL)
    {
        LOG("Failed to initialize ImageTracker.");
        return 0;
    }

    LOG("Successfully initialized ImageTracker.");
    return 1;
}

native code當中,主要有兩個用來InitializefunctionQCAR::TrackerManagerQCAR::Tracker* tracker在這邊參考一下官方的API這個,雖然官方API有些說明很兩光或是不清楚,但是大致上還是可以理解。首先在QCAR::TrackerManager當中,主要就是需要取得一個TrackerManagerInstance()接著針對這個Instance進行操作。接著設定Tracker用來追蹤所偵測到的目標而在所追蹤的目標也需要初始化它的類型,這就是trackerManager.initTracker()所做的事情。在這邊當中只有兩種IMAGE_TRACKER-Tracks ImageTargets and MultiTargets.以及MARKER_TRACKER-Tracks Markers.這兩種,那關於Image TargetsMultiTargets以及Tracks MarkersVuforia-I有說明。OK,這邊想要補充一下什麼是Instance這是軟工的一個名詞,定義說明如下。

1
Instance:An instanceis an object created from a class. The class describes the (behavior and information)structure of the instance, while the current state of the instance is defined by the operations performed on the instance.

節錄資料來源:搞笑軟工

在完成這些動作之後,接著進入updateApplicationStatus(APPSTATUS_INIT_APP_AR);今天寫到這邊,改天繼續說明和Trace

cameraDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 /**
  * NOTE: this method is synchronized because of a potential concurrent
  * access by ImageTargets::onResume() and InitQCARTask::onPostExecute().
  */
  private synchronized void updateApplicationStatus(int appStatus) {
      // Exit if there is no change in status:
      if (mAppStatus == appStatus)
          return;

      // Store new status value:
      mAppStatus = appStatus;

      // Execute application state-specific actions:
      switch (mAppStatus) {
      case APPSTATUS_INIT_APP:
          // Initialize application elements that do not rely on QCAR
          // initialization:
          initApplication();

          // Proceed to next application initialization status:
          updateApplicationStatus(APPSTATUS_INIT_QCAR);
          break;

      case APPSTATUS_INIT_QCAR:
          // Initialize QCAR SDK asynchronously to avoid blocking the
          // main (UI) thread.
          //
          // NOTE: This task instance must be created and invoked on the
          // UI thread and it can be executed only once!
          try {
              mInitQCARTask = new InitQCARTask();
              mInitQCARTask.execute();
          } catch (Exception e) {
              DebugLog.LOGE("cameraDemo", "Initializing QCAR SDK failed");
          }
          break;

      case APPSTATUS_INIT_TRACKER:
          // Initialize the ImageTracker:
          if (initTracker() > 0) {
              // Proceed to next application initialization status:
              updateApplicationStatus(APPSTATUS_INIT_APP_AR);
          }
          break;

      case APPSTATUS_INIT_APP_AR:
          // Initialize Augmented Reality-specific application elements
          // that may rely on the fact that the QCAR SDK has been
          // already initialized:
          initApplicationAR();

          // Proceed to next application initialization status:
          updateApplicationStatus(APPSTATUS_LOAD_TRACKER);
          break;

      case APPSTATUS_LOAD_TRACKER:
          // Load the tracking data set:
          //
          // NOTE: This task instance must be created and invoked on the
          // UI thread and it can be executed only once!
          try {
              mLoadTrackerTask = new LoadTrackerTask();
              mLoadTrackerTask.execute();
          } catch (Exception e) {
              DebugLog.LOGE("cameraDemo", "Loading tracking data set failed");
          }
          break;

      case APPSTATUS_INITED:
          // Hint to the virtual machine that it would be a good time to
          // run the garbage collector:
          //
          // NOTE: This is only a hint. There is no guarantee that the
          // garbage collector will actually be run.
          System.gc();

          // Native post initialization:
          onQCARInitializedNative();

          // Activate the renderer:
          mRenderer.mIsActive = true;

          // Now add the GL surface view. It is important
          // that the OpenGL ES surface view gets added
          // BEFORE the camera is started and video
          // background is configured.
          addContentView(mGlView, new LayoutParams(LayoutParams.MATCH_PARENT,
                  LayoutParams.MATCH_PARENT));

          // Sets the UILayout to be drawn in front of the camera
          // mUILayout.bringToFront();

          // Start the camera:
          updateApplicationStatus(APPSTATUS_CAMERA_RUNNING);

          break;

      case APPSTATUS_CAMERA_STOPPED:
          // Call the native function to stop the camera:
          stopCamera();
          break;

      case APPSTATUS_CAMERA_RUNNING:
          // Call the native function to start the camera:
          startCamera();

          // Hides the Loading Dialog
          // loadingDialogHandler.sendEmptyMessage(HIDE_LOADING_DIALOG);

          // Sets the layout background to transparent
          // mUILayout.setBackgroundColor(Color.TRANSPARENT);

          // Set continuous auto-focus if supported by the device,
          // otherwise default back to regular auto-focus mode.
          // This will be activated by a tap to the screen in this
          // application.
          if (!setFocusMode(FOCUS_MODE_CONTINUOUS_AUTO)) {
              mContAutofocus = false;
              setFocusMode(FOCUS_MODE_NORMAL);
          } else {
              mContAutofocus = true;
          }
          break;

      default:
          throw new RuntimeException("Invalid application state");
      }
  }

Comments