/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.util.Log; /** * Class to handle the processing of each frame by Mosaicer. */ public class MosaicFrameProcessor { private static final String TAG = "MosaicFrameProcessor"; private static final int NUM_FRAMES_IN_BUFFER = 2; private static final int MAX_NUMBER_OF_FRAMES = 100; private static final int MOSAIC_RET_CODE_INDEX = 10; private static final int FRAME_COUNT_INDEX = 9; private static final int X_COORD_INDEX = 2; private static final int Y_COORD_INDEX = 5; private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4; private static final int WINDOW_SIZE = 3; private Mosaic mMosaicer; private boolean mIsMosaicMemoryAllocated = false; private float mTranslationLastX; private float mTranslationLastY; private int mFillIn = 0; private int mTotalFrameCount = 0; private int mLastProcessFrameIdx = -1; private int mCurrProcessFrameIdx = -1; private boolean mFirstRun; // Panning rate is in unit of percentage of image content translation per // frame. Use moving average to calculate the panning rate. private float mPanningRateX; private float mPanningRateY; private float[] mDeltaX = new float[WINDOW_SIZE]; private float[] mDeltaY = new float[WINDOW_SIZE]; private int mOldestIdx = 0; private float mTotalTranslationX = 0f; private float mTotalTranslationY = 0f; private ProgressListener mProgressListener; private int mPreviewWidth; private int mPreviewHeight; private int mPreviewBufferSize; private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton public interface ProgressListener { public void onProgress(boolean isFinished, float panningRateX, float panningRateY, float progressX, float progressY); } public static MosaicFrameProcessor getInstance() { if (sMosaicFrameProcessor == null) { sMosaicFrameProcessor = new MosaicFrameProcessor(); } return sMosaicFrameProcessor; } private MosaicFrameProcessor() { mMosaicer = new Mosaic(); } public void setProgressListener(ProgressListener listener) { mProgressListener = listener; } public int reportProgress(boolean hires, boolean cancel) { return mMosaicer.reportProgress(hires, cancel); } public void initialize(int previewWidth, int previewHeight, int bufSize) { mPreviewWidth = previewWidth; mPreviewHeight = previewHeight; mPreviewBufferSize = bufSize; setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize); setStripType(Mosaic.STRIPTYPE_WIDE); // no need to call reset() here. reset() should be called by the client // after this initialization before calling other methods of this object. } public void clear() { if (mIsMosaicMemoryAllocated) { mMosaicer.freeMosaicMemory(); mIsMosaicMemoryAllocated = false; } synchronized (this) { notify(); } } public boolean isMosaicMemoryAllocated() { return mIsMosaicMemoryAllocated; } public void setStripType(int type) { mMosaicer.setStripType(type); } private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) { Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize); if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!"); mIsMosaicMemoryAllocated = true; mMosaicer.allocateMosaicMemory(previewWidth, previewHeight); } public void reset() { // reset() can be called even if MosaicFrameProcessor is not initialized. // Only counters will be changed. mFirstRun = true; mTotalFrameCount = 0; mFillIn = 0; mTotalTranslationX = 0; mTranslationLastX = 0; mTotalTranslationY = 0; mTranslationLastY = 0; mPanningRateX = 0; mPanningRateY = 0; mLastProcessFrameIdx = -1; mCurrProcessFrameIdx = -1; for (int i = 0; i < WINDOW_SIZE; ++i) { mDeltaX[i] = 0f; mDeltaY[i] = 0f; } mMosaicer.reset(); } public int createMosaic(boolean highRes) { return mMosaicer.createMosaic(highRes); } public byte[] getFinalMosaicNV21() { return mMosaicer.getFinalMosaicNV21(); } // Processes the last filled image frame through the mosaicer and // updates the UI to show progress. // When done, processes and displays the final mosaic. public void processFrame() { if (!mIsMosaicMemoryAllocated) { // clear() is called and buffers are cleared, stop computation. // This can happen when the onPause() is called in the activity, but still some frames // are not processed yet and thus the callback may be invoked. return; } mCurrProcessFrameIdx = mFillIn; mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER); // Check that we are trying to process a frame different from the // last one processed (useful if this class was running asynchronously) if (mCurrProcessFrameIdx != mLastProcessFrameIdx) { mLastProcessFrameIdx = mCurrProcessFrameIdx; // TODO: make the termination condition regarding reaching // MAX_NUMBER_OF_FRAMES solely determined in the library. if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) { // If we are still collecting new frames for the current mosaic, // process the new frame. calculateTranslationRate(); // Publish progress of the ongoing processing if (mProgressListener != null) { mProgressListener.onProgress(false, mPanningRateX, mPanningRateY, mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); } } else { if (mProgressListener != null) { mProgressListener.onProgress(true, mPanningRateX, mPanningRateY, mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); } } } } public void calculateTranslationRate() { float[] frameData = mMosaicer.setSourceImageFromGPU(); int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX]; mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX]; float translationCurrX = frameData[X_COORD_INDEX]; float translationCurrY = frameData[Y_COORD_INDEX]; if (mFirstRun) { // First time: no need to update delta values. mTranslationLastX = translationCurrX; mTranslationLastY = translationCurrY; mFirstRun = false; return; } // Moving average: remove the oldest translation/deltaTime and // add the newest translation/deltaTime in int idx = mOldestIdx; mTotalTranslationX -= mDeltaX[idx]; mTotalTranslationY -= mDeltaY[idx]; mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX); mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY); mTotalTranslationX += mDeltaX[idx]; mTotalTranslationY += mDeltaY[idx]; // The panning rate is measured as the rate of the translation percentage in // image width/height. Take the horizontal panning rate for example, the image width // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR). // To get the horizontal translation percentage, the horizontal translation, // (translationCurrX - mTranslationLastX), is divided by the // image width. We then get the rate by dividing the translation percentage with the // number of frames. mPanningRateX = mTotalTranslationX / (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; mPanningRateY = mTotalTranslationY / (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; mTranslationLastX = translationCurrX; mTranslationLastY = translationCurrY; mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE; } }