/*
* Copyright (C) 2014 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.example.android.hdrviewfinder;
import android.graphics.ImageFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.util.Size;
import android.view.Surface;
/**
* Renderscript-based merger for an HDR viewfinder
*/
public class ViewfinderProcessor {
private Allocation mInputHdrAllocation;
private Allocation mInputNormalAllocation;
private Allocation mPrevAllocation;
private Allocation mOutputAllocation;
private Surface mOutputSurface;
private HandlerThread mProcessingThread;
private Handler mProcessingHandler;
private ScriptC_hdr_merge mHdrMergeScript;
public ProcessingTask mHdrTask;
public ProcessingTask mNormalTask;
private Size mSize;
private int mMode;
public final static int MODE_NORMAL = 0;
public final static int MODE_SIDE_BY_SIDE = 1;
public final static int MODE_HDR = 2;
public ViewfinderProcessor(RenderScript rs, Size dimensions) {
mSize = dimensions;
Type.Builder yuvTypeBuilder = new Type.Builder(rs, Element.YUV(rs));
yuvTypeBuilder.setX(dimensions.getWidth());
yuvTypeBuilder.setY(dimensions.getHeight());
yuvTypeBuilder.setYuvFormat(ImageFormat.YUV_420_888);
mInputHdrAllocation = Allocation.createTyped(rs, yuvTypeBuilder.create(),
Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT);
mInputNormalAllocation = Allocation.createTyped(rs, yuvTypeBuilder.create(),
Allocation.USAGE_IO_INPUT | Allocation.USAGE_SCRIPT);
Type.Builder rgbTypeBuilder = new Type.Builder(rs, Element.RGBA_8888(rs));
rgbTypeBuilder.setX(dimensions.getWidth());
rgbTypeBuilder.setY(dimensions.getHeight());
mPrevAllocation = Allocation.createTyped(rs, rgbTypeBuilder.create(),
Allocation.USAGE_SCRIPT);
mOutputAllocation = Allocation.createTyped(rs, rgbTypeBuilder.create(),
Allocation.USAGE_IO_OUTPUT | Allocation.USAGE_SCRIPT);
mProcessingThread = new HandlerThread("ViewfinderProcessor");
mProcessingThread.start();
mProcessingHandler = new Handler(mProcessingThread.getLooper());
mHdrMergeScript = new ScriptC_hdr_merge(rs);
mHdrMergeScript.set_gPrevFrame(mPrevAllocation);
mHdrTask = new ProcessingTask(mInputHdrAllocation, dimensions.getWidth()/2, true);
mNormalTask = new ProcessingTask(mInputNormalAllocation, 0, false);
setRenderMode(MODE_NORMAL);
}
public Surface getInputHdrSurface() {
return mInputHdrAllocation.getSurface();
}
public Surface getInputNormalSurface() {
return mInputNormalAllocation.getSurface();
}
public void setOutputSurface(Surface output) {
mOutputAllocation.setSurface(output);
}
public void setRenderMode(int mode) {
mMode = mode;
}
/**
* Simple class to keep track of incoming frame count,
* and to process the newest one in the processing thread
*/
class ProcessingTask implements Runnable, Allocation.OnBufferAvailableListener {
private int mPendingFrames = 0;
private int mFrameCounter = 0;
private int mCutPointX;
private boolean mCheckMerge;
private Allocation mInputAllocation;
public ProcessingTask(Allocation input, int cutPointX, boolean checkMerge) {
mInputAllocation = input;
mInputAllocation.setOnBufferAvailableListener(this);
mCutPointX = cutPointX;
mCheckMerge = checkMerge;
}
@Override
public void onBufferAvailable(Allocation a) {
synchronized(this) {
mPendingFrames++;
mProcessingHandler.post(this);
}
}
@Override
public void run() {
// Find out how many frames have arrived
int pendingFrames;
synchronized(this) {
pendingFrames = mPendingFrames;
mPendingFrames = 0;
// Discard extra messages in case processing is slower than frame rate
mProcessingHandler.removeCallbacks(this);
}
// Get to newest input
for (int i = 0; i < pendingFrames; i++) {
mInputAllocation.ioReceive();
}
mHdrMergeScript.set_gFrameCounter(mFrameCounter++);
mHdrMergeScript.set_gCurrentFrame(mInputAllocation);
mHdrMergeScript.set_gCutPointX(mCutPointX);
if (mCheckMerge && mMode == MODE_HDR) {
mHdrMergeScript.set_gDoMerge(1);
} else {
mHdrMergeScript.set_gDoMerge(0);
}
// Run processing pass
mHdrMergeScript.forEach_mergeHdrFrames(mPrevAllocation, mOutputAllocation);
mOutputAllocation.ioSend();
}
}
}