1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.panorama;
18
19import android.util.Log;
20
21/**
22 * Class to handle the processing of each frame by Mosaicer.
23 */
24public class MosaicFrameProcessor {
25    private static final boolean LOGV = true;
26    private static final String TAG = "MosaicFrameProcessor";
27    private static final int NUM_FRAMES_IN_BUFFER = 2;
28    private static final int MAX_NUMBER_OF_FRAMES = 100;
29    private static final int MOSAIC_RET_CODE_INDEX = 10;
30    private static final int FRAME_COUNT_INDEX = 9;
31    private static final int X_COORD_INDEX = 2;
32    private static final int Y_COORD_INDEX = 5;
33    private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;
34    private static final int WINDOW_SIZE = 3;
35
36    private Mosaic mMosaicer;
37    private boolean mIsMosaicMemoryAllocated = false;
38    private final long [] mFrameTimestamp = new long[NUM_FRAMES_IN_BUFFER];
39    private float mTranslationLastX;
40    private float mTranslationLastY;
41
42    private int mFillIn = 0;
43    private int mTotalFrameCount = 0;
44    private long mLastProcessedFrameTimestamp = 0;
45    private int mLastProcessFrameIdx = -1;
46    private int mCurrProcessFrameIdx = -1;
47
48    // Panning rate is in unit of percentage of image content translation / second.
49    // Use the moving average to calculate the panning rate.
50    private float mPanningRateX;
51    private float mPanningRateY;
52
53    private float[] mDeltaX = new float[WINDOW_SIZE];
54    private float[] mDeltaY = new float[WINDOW_SIZE];
55    private float[] mDeltaTime = new float[WINDOW_SIZE];
56    private int mOldestIdx = 0;
57    private float mTotalTranslationX = 0f;
58    private float mTotalTranslationY = 0f;
59    private float mTotalDeltaTime = 0f;
60
61    private ProgressListener mProgressListener;
62
63    private int mPreviewWidth;
64    private int mPreviewHeight;
65    private int mPreviewBufferSize;
66
67    public interface ProgressListener {
68        public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
69                float progressX, float progressY);
70    }
71
72    public MosaicFrameProcessor(int previewWidth, int previewHeight, int bufSize) {
73        mMosaicer = new Mosaic();
74        mPreviewWidth = previewWidth;
75        mPreviewHeight = previewHeight;
76        mPreviewBufferSize = bufSize;
77    }
78
79    public void setProgressListener(ProgressListener listener) {
80        mProgressListener = listener;
81    }
82
83    public int reportProgress(boolean hires, boolean cancel) {
84        return mMosaicer.reportProgress(hires, cancel);
85    }
86
87    public void initialize() {
88        setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
89        setStripType(Mosaic.STRIPTYPE_WIDE);
90        reset();
91    }
92
93    public void clear() {
94        if (mIsMosaicMemoryAllocated) {
95            mIsMosaicMemoryAllocated = false;
96            mMosaicer.freeMosaicMemory();
97        }
98    }
99
100    public void setStripType(int type) {
101        mMosaicer.setStripType(type);
102    }
103
104    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
105        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
106        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
107        mIsMosaicMemoryAllocated = true;
108
109        mFillIn = 0;
110        if  (mMosaicer != null) {
111            mMosaicer.reset();
112        }
113    }
114
115    public void reset() {
116        // reset() can be called even if MosaicFrameProcessor is not initialized.
117        // Only counters will be changed.
118        mTotalFrameCount = 0;
119        mFillIn = 0;
120        mLastProcessedFrameTimestamp = 0;
121        mTotalTranslationX = 0;
122        mTranslationLastX = 0;
123        mTotalTranslationY = 0;
124        mTranslationLastY = 0;
125        mTotalDeltaTime = 0;
126        mPanningRateX = 0;
127        mPanningRateY = 0;
128        mLastProcessFrameIdx = -1;
129        mCurrProcessFrameIdx = -1;
130        for (int i = 0; i < WINDOW_SIZE; ++i) {
131            mDeltaX[i] = 0f;
132            mDeltaY[i] = 0f;
133            mDeltaTime[i] = 0f;
134        }
135        mMosaicer.reset();
136    }
137
138    public int createMosaic(boolean highRes) {
139        return mMosaicer.createMosaic(highRes);
140    }
141
142    public byte[] getFinalMosaicNV21() {
143        return mMosaicer.getFinalMosaicNV21();
144    }
145
146    // Processes the last filled image frame through the mosaicer and
147    // updates the UI to show progress.
148    // When done, processes and displays the final mosaic.
149    public void processFrame() {
150        if (!mIsMosaicMemoryAllocated) {
151            // clear() is called and buffers are cleared, stop computation.
152            // This can happen when the onPause() is called in the activity, but still some frames
153            // are not processed yet and thus the callback may be invoked.
154            return;
155        }
156        long t1 = System.currentTimeMillis();
157        mFrameTimestamp[mFillIn] = t1;
158
159        mCurrProcessFrameIdx = mFillIn;
160        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
161
162        // Check that we are trying to process a frame different from the
163        // last one processed (useful if this class was running asynchronously)
164        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
165            mLastProcessFrameIdx = mCurrProcessFrameIdx;
166
167            // Access the timestamp associated with it...
168            long timestamp = mFrameTimestamp[mCurrProcessFrameIdx];
169
170            // TODO: make the termination condition regarding reaching
171            // MAX_NUMBER_OF_FRAMES solely determined in the library.
172            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
173                // If we are still collecting new frames for the current mosaic,
174                // process the new frame.
175                calculateTranslationRate(timestamp);
176
177                // Publish progress of the ongoing processing
178                if (mProgressListener != null) {
179                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
180                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
181                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
182                }
183            } else {
184                if (mProgressListener != null) {
185                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
186                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
187                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
188                }
189            }
190        }
191    }
192
193    public void calculateTranslationRate(long now) {
194        float[] frameData = mMosaicer.setSourceImageFromGPU();
195        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
196        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
197        float translationCurrX = frameData[X_COORD_INDEX];
198        float translationCurrY = frameData[Y_COORD_INDEX];
199
200        if (mLastProcessedFrameTimestamp == 0f) {
201            // First time: no need to update delta values.
202            mTranslationLastX = translationCurrX;
203            mTranslationLastY = translationCurrY;
204            mLastProcessedFrameTimestamp = now;
205            return;
206        }
207
208        // Moving average: remove the oldest translation/deltaTime and
209        // add the newest translation/deltaTime in
210        int idx = mOldestIdx;
211        mTotalTranslationX -= mDeltaX[idx];
212        mTotalTranslationY -= mDeltaY[idx];
213        mTotalDeltaTime -= mDeltaTime[idx];
214        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
215        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
216        mDeltaTime[idx] = (now - mLastProcessedFrameTimestamp) / 1000.0f;
217        mTotalTranslationX += mDeltaX[idx];
218        mTotalTranslationY += mDeltaY[idx];
219        mTotalDeltaTime += mDeltaTime[idx];
220
221        // The panning rate is measured as the rate of the translation percentage in
222        // image width/height. Take the horizontal panning rate for example, the image width
223        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
224        // To get the horizontal translation percentage, the horizontal translation,
225        // (translationCurrX - mTranslationLastX), is divided by the
226        // image width. We then get the rate by dividing the translation percentage with deltaTime.
227        mPanningRateX = mTotalTranslationX /
228                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
229        mPanningRateY = mTotalTranslationY /
230                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / mTotalDeltaTime;
231
232        mTranslationLastX = translationCurrX;
233        mTranslationLastY = translationCurrY;
234        mLastProcessedFrameTimestamp = now;
235        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
236    }
237}
238