diff options
Diffstat (limited to 'media')
245 files changed, 31004 insertions, 0 deletions
diff --git a/media/mca/Android.mk b/media/mca/Android.mk new file mode 100644 index 0000000..b1ce91e --- /dev/null +++ b/media/mca/Android.mk @@ -0,0 +1,21 @@ +# 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. +# + +# +# Build all native libraries +# +include $(call all-subdir-makefiles) + + diff --git a/media/mca/effect/java/android/media/effect/Effect.java b/media/mca/effect/java/android/media/effect/Effect.java new file mode 100644 index 0000000..b2b4427 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/Effect.java @@ -0,0 +1,111 @@ +/* + * 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 android.media.effect; + + +/** + * <p>Effects are high-performance transformations that can be applied to image frames. These are + * passed in the form of OpenGL ES 2.0 texture names. Typical frames could be images loaded from + * disk, or frames from the camera or other video streams.</p> + * + * <p>To create an Effect you must first create an EffectContext. You can obtain an instance of the + * context's EffectFactory by calling + * {@link android.media.effect.EffectContext#getFactory() getFactory()}. The EffectFactory allows + * you to instantiate specific Effects.</p> + * + * <p>The application is responsible for creating an EGL context, and making it current before + * applying an effect. An effect is bound to a single EffectContext, which in turn is bound to a + * single EGL context. If your EGL context is destroyed, the EffectContext becomes invalid and any + * effects bound to this context can no longer be used.</p> + * + */ +public abstract class Effect { + + /** + * Get the effect name. + * + * Returns the unique name of the effect, which matches the name used for instantiating this + * effect by the EffectFactory. + * + * @return The name of the effect. + */ + public abstract String getName(); + + /** + * Apply an effect to GL textures. + * + * <p>Apply the Effect on the specified input GL texture, and write the result into the + * output GL texture. The texture names passed must be valid in the current GL context.</p> + * + * <p>The input texture must be a valid texture name with the given width and height and must be + * bound to a GL_TEXTURE_2D texture image (usually done by calling the glTexImage2D() function). + * Multiple mipmap levels may be provided.</p> + * + * <p>If the output texture has not been bound to a texture image, it will be automatically + * bound by the effect as a GL_TEXTURE_2D. It will contain one mipmap level (0), which will have + * the same size as the input. No other mipmap levels are defined. If the output texture was + * bound already, and its size does not match the input texture size, the result may be clipped + * or only partially fill the texture.</p> + * + * <p>Note, that regardless of whether a texture image was originally provided or not, both the + * input and output textures are owned by the caller. That is, the caller is responsible for + * calling glDeleteTextures() to deallocate the input and output textures.</p> + * + * @param inputTexId The GL texture name of a valid and bound input texture. + * @param width The width of the input texture in pixels. + * @param height The height of the input texture in pixels. + * @param outputTexId The GL texture name of the output texture. + */ + public abstract void apply(int inputTexId, int width, int height, int outputTexId); + + /** + * Set a filter parameter. + * + * Consult the effect documentation for a list of supported parameter keys for each effect. + * + * @param parameterKey The name of the parameter to adjust. + * @param value The new value to set the parameter to. + * @throws InvalidArgumentException if parameterName is not a recognized name, or the value is + * not a valid value for this parameter. + */ + public abstract void setParameter(String parameterKey, Object value); + + /** + * Set an effect listener. + * + * Some effects may report state changes back to the host, if a listener is set. Consult the + * individual effect documentation for more details. + * + * @param listener The listener to receive update callbacks on. + */ + public void setUpdateListener(EffectUpdateListener listener) { + } + + /** + * Release an effect. + * + * <p>Releases the effect and any resources associated with it. You may call this if you need to + * make sure acquired resources are no longer held by the effect. Releasing an effect makes it + * invalid for reuse.</p> + * + * <p>Note that this method must be called with the EffectContext and EGL context current, as + * the effect may release internal GL resources.</p> + */ + public abstract void release(); +} + diff --git a/media/mca/effect/java/android/media/effect/EffectContext.java b/media/mca/effect/java/android/media/effect/EffectContext.java new file mode 100644 index 0000000..ef03229 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/EffectContext.java @@ -0,0 +1,131 @@ +/* + * 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 android.media.effect; + +import android.filterfw.core.CachedFrameManager; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.FrameManager; +import android.opengl.GLES20; + +/** + * <p>An EffectContext keeps all necessary state information to run Effects within a Open GL ES 2.0 + * context.</p> + * + * <p>Every EffectContext is bound to one GL context. The application is responsible for creating + * this EGL context, and making it current before applying any effect. If your EGL context is + * destroyed, the EffectContext becomes invalid and any effects bound to this context can no longer + * be used. If you switch to another EGL context, you must create a new EffectContext. Each Effect + * is bound to a single EffectContext, and can only be executed in that context.</p> + */ +public class EffectContext { + + private final int GL_STATE_FBO = 0; + private final int GL_STATE_PROGRAM = 1; + private final int GL_STATE_ARRAYBUFFER = 2; + private final int GL_STATE_COUNT = 3; + + FilterContext mFilterContext; + + private EffectFactory mFactory; + + private int[] mOldState = new int[GL_STATE_COUNT]; + + /** + * Creates a context within the current GL context. + * + * <p>Binds the EffectContext to the current OpenGL context. All subsequent calls to the + * EffectContext must be made in the GL context that was active during creation. + * When you have finished using a context, you must call {@link #release()}. to dispose of all + * resources associated with this context.</p> + */ + public static EffectContext createWithCurrentGlContext() { + EffectContext result = new EffectContext(); + result.initInCurrentGlContext(); + return result; + } + + /** + * Returns the EffectFactory for this context. + * + * <p>The EffectFactory returned from this method allows instantiating new effects within this + * context.</p> + * + * @return The EffectFactory instance for this context. + */ + public EffectFactory getFactory() { + return mFactory; + } + + /** + * Releases the context. + * + * <p>Releases all the resources and effects associated with the EffectContext. This renders the + * context and all the effects bound to this context invalid. You must no longer use the context + * or any of its bound effects after calling release().</p> + * + * <p>Note that this method must be called with the proper EGL context made current, as the + * EffectContext and its effects may release internal GL resources.</p> + */ + public void release() { + mFilterContext.tearDown(); + mFilterContext = null; + } + + private EffectContext() { + mFilterContext = new FilterContext(); + mFilterContext.setFrameManager(new CachedFrameManager()); + mFactory = new EffectFactory(this); + } + + private void initInCurrentGlContext() { + if (!GLEnvironment.isAnyContextActive()) { + throw new RuntimeException("Attempting to initialize EffectContext with no active " + + "GL context!"); + } + GLEnvironment glEnvironment = new GLEnvironment(); + glEnvironment.initWithCurrentContext(); + mFilterContext.initGLEnvironment(glEnvironment); + } + + final void assertValidGLState() { + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv == null || !glEnv.isContextActive()) { + if (GLEnvironment.isAnyContextActive()) { + throw new RuntimeException("Applying effect in wrong GL context!"); + } else { + throw new RuntimeException("Attempting to apply effect without valid GL context!"); + } + } + } + + final void saveGLState() { + GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, mOldState, GL_STATE_FBO); + GLES20.glGetIntegerv(GLES20.GL_CURRENT_PROGRAM, mOldState, GL_STATE_PROGRAM); + GLES20.glGetIntegerv(GLES20.GL_ARRAY_BUFFER_BINDING, mOldState, GL_STATE_ARRAYBUFFER); + } + + final void restoreGLState() { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mOldState[GL_STATE_FBO]); + GLES20.glUseProgram(mOldState[GL_STATE_PROGRAM]); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mOldState[GL_STATE_ARRAYBUFFER]); + } +} + diff --git a/media/mca/effect/java/android/media/effect/EffectFactory.java b/media/mca/effect/java/android/media/effect/EffectFactory.java new file mode 100644 index 0000000..4330279 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/EffectFactory.java @@ -0,0 +1,517 @@ +/* + * 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 android.media.effect; + +import java.lang.reflect.Constructor; +import java.util.HashMap; + +/** + * <p>The EffectFactory class defines the list of available Effects, and provides functionality to + * inspect and instantiate them. Some effects may not be available on all platforms, so before + * creating a certain effect, the application should confirm that the effect is supported on this + * platform by calling {@link #isEffectSupported(String)}.</p> + */ +public class EffectFactory { + + private EffectContext mEffectContext; + + private final static String[] EFFECT_PACKAGES = { + "android.media.effect.effects.", // Default effect package + "" // Allows specifying full class path + }; + + /** List of Effects */ + /** + * <p>Copies the input texture to the output.</p> + * <p>Available parameters: None</p> + * @hide + */ + public final static String EFFECT_IDENTITY = "IdentityEffect"; + + /** + * <p>Adjusts the brightness of the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>brightness</code></td> + * <td>The brightness multiplier.</td> + * <td>Positive float. 1.0 means no change; + larger values will increase brightness.</td> + * </tr> + * </table> + */ + public final static String EFFECT_BRIGHTNESS = + "android.media.effect.effects.BrightnessEffect"; + + /** + * <p>Adjusts the contrast of the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>contrast</code></td> + * <td>The contrast multiplier.</td> + * <td>Float. 1.0 means no change; + larger values will increase contrast.</td> + * </tr> + * </table> + */ + public final static String EFFECT_CONTRAST = + "android.media.effect.effects.ContrastEffect"; + + /** + * <p>Applies a fisheye lens distortion to the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The scale of the distortion.</td> + * <td>Float, between 0 and 1. Zero means no distortion.</td> + * </tr> + * </table> + */ + public final static String EFFECT_FISHEYE = + "android.media.effect.effects.FisheyeEffect"; + + /** + * <p>Replaces the background of the input frames with frames from a + * selected video. Requires an initial learning period with only the + * background visible before the effect becomes active. The effect will wait + * until it does not see any motion in the scene before learning the + * background and starting the effect.</p> + * + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>source</code></td> + * <td>A URI for the background video to use. This parameter must be + * supplied before calling apply() for the first time.</td> + * <td>String, such as from + * {@link android.net.Uri#toString Uri.toString()}</td> + * </tr> + * </table> + * + * <p>If the update listener is set for this effect using + * {@link Effect#setUpdateListener}, it will be called when the effect has + * finished learning the background, with a null value for the info + * parameter.</p> + */ + public final static String EFFECT_BACKDROPPER = + "android.media.effect.effects.BackDropperEffect"; + + /** + * <p>Attempts to auto-fix the image based on histogram equalization.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The scale of the adjustment.</td> + * <td>Float, between 0 and 1. Zero means no adjustment, while 1 indicates the maximum + * amount of adjustment.</td> + * </tr> + * </table> + */ + public final static String EFFECT_AUTOFIX = + "android.media.effect.effects.AutoFixEffect"; + + /** + * <p>Adjusts the range of minimal and maximal color pixel intensities.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>black</code></td> + * <td>The value of the minimal pixel.</td> + * <td>Float, between 0 and 1.</td> + * </tr> + * <tr><td><code>white</code></td> + * <td>The value of the maximal pixel.</td> + * <td>Float, between 0 and 1.</td> + * </tr> + * </table> + */ + public final static String EFFECT_BLACKWHITE = + "android.media.effect.effects.BlackWhiteEffect"; + + /** + * <p>Crops an upright rectangular area from the image. If the crop region falls outside of + * the image bounds, the results are undefined.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>xorigin</code></td> + * <td>The origin's x-value.</td> + * <td>Integer, between 0 and width of the image.</td> + * </tr> + * <tr><td><code>yorigin</code></td> + * <td>The origin's y-value.</td> + * <td>Integer, between 0 and height of the image.</td> + * </tr> + * <tr><td><code>width</code></td> + * <td>The width of the cropped image.</td> + * <td>Integer, between 1 and the width of the image minus xorigin.</td> + * </tr> + * <tr><td><code>height</code></td> + * <td>The height of the cropped image.</td> + * <td>Integer, between 1 and the height of the image minus yorigin.</td> + * </tr> + * </table> + */ + public final static String EFFECT_CROP = + "android.media.effect.effects.CropEffect"; + + /** + * <p>Applies a cross process effect on image, in which the red and green channels are + * enhanced while the blue channel is restricted.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_CROSSPROCESS = + "android.media.effect.effects.CrossProcessEffect"; + + /** + * <p>Applies black and white documentary style effect on image..</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_DOCUMENTARY = + "android.media.effect.effects.DocumentaryEffect"; + + + /** + * <p>Overlays a bitmap (with premultiplied alpha channel) onto the input image. The bitmap + * is stretched to fit the input image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>bitmap</code></td> + * <td>The overlay bitmap.</td> + * <td>A non-null Bitmap instance.</td> + * </tr> + * </table> + */ + public final static String EFFECT_BITMAPOVERLAY = + "android.media.effect.effects.BitmapOverlayEffect"; + + /** + * <p>Representation of photo using only two color tones.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>first_color</code></td> + * <td>The first color tone.</td> + * <td>Integer, representing an ARGB color with 8 bits per channel. May be created using + * {@link android.graphics.Color Color} class.</td> + * </tr> + * <tr><td><code>second_color</code></td> + * <td>The second color tone.</td> + * <td>Integer, representing an ARGB color with 8 bits per channel. May be created using + * {@link android.graphics.Color Color} class.</td> + * </tr> + * </table> + */ + public final static String EFFECT_DUOTONE = + "android.media.effect.effects.DuotoneEffect"; + + /** + * <p>Applies back-light filling to the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>strength</code></td> + * <td>The strength of the backlight.</td> + * <td>Float, between 0 and 1. Zero means no change.</td> + * </tr> + * </table> + */ + public final static String EFFECT_FILLLIGHT = + "android.media.effect.effects.FillLightEffect"; + + /** + * <p>Flips image vertically and/or horizontally.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>vertical</code></td> + * <td>Whether to flip image vertically.</td> + * <td>Boolean</td> + * </tr> + * <tr><td><code>horizontal</code></td> + * <td>Whether to flip image horizontally.</td> + * <td>Boolean</td> + * </tr> + * </table> + */ + public final static String EFFECT_FLIP = + "android.media.effect.effects.FlipEffect"; + + /** + * <p>Applies film grain effect to image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>strength</code></td> + * <td>The strength of the grain effect.</td> + * <td>Float, between 0 and 1. Zero means no change.</td> + * </tr> + * </table> + */ + public final static String EFFECT_GRAIN = + "android.media.effect.effects.GrainEffect"; + + /** + * <p>Converts image to grayscale.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_GRAYSCALE = + "android.media.effect.effects.GrayscaleEffect"; + + /** + * <p>Applies lomo-camera style effect to image.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_LOMOISH = + "android.media.effect.effects.LomoishEffect"; + + /** + * <p>Inverts the image colors.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_NEGATIVE = + "android.media.effect.effects.NegativeEffect"; + + /** + * <p>Applies posterization effect to image.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_POSTERIZE = + "android.media.effect.effects.PosterizeEffect"; + + /** + * <p>Removes red eyes on specified region.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>centers</code></td> + * <td>Multiple center points (x, y) of the red eye regions.</td> + * <td>An array of floats, where (f[2*i], f[2*i+1]) specifies the center of the i'th eye. + * Coordinate values are expected to be normalized between 0 and 1.</td> + * </tr> + * </table> + */ + public final static String EFFECT_REDEYE = + "android.media.effect.effects.RedEyeEffect"; + + /** + * <p>Rotates the image. The output frame size must be able to fit the rotated version of + * the input image. Note that the rotation snaps to a the closest multiple of 90 degrees.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>angle</code></td> + * <td>The angle of rotation in degrees.</td> + * <td>Integer value. This will be rounded to the nearest multiple of 90.</td> + * </tr> + * </table> + */ + public final static String EFFECT_ROTATE = + "android.media.effect.effects.RotateEffect"; + + /** + * <p>Adjusts color saturation of image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The scale of color saturation.</td> + * <td>Float, between -1 and 1. 0 means no change, while -1 indicates full desaturation, + * i.e. grayscale.</td> + * </tr> + * </table> + */ + public final static String EFFECT_SATURATE = + "android.media.effect.effects.SaturateEffect"; + + /** + * <p>Converts image to sepia tone.</p> + * <p>Available parameters: None</p> + */ + public final static String EFFECT_SEPIA = + "android.media.effect.effects.SepiaEffect"; + + /** + * <p>Sharpens the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The degree of sharpening.</td> + * <td>Float, between 0 and 1. 0 means no change.</td> + * </tr> + * </table> + */ + public final static String EFFECT_SHARPEN = + "android.media.effect.effects.SharpenEffect"; + + /** + * <p>Rotates the image according to the specified angle, and crops the image so that no + * non-image portions are visible.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>angle</code></td> + * <td>The angle of rotation.</td> + * <td>Float, between -45 and +45.</td> + * </tr> + * </table> + */ + public final static String EFFECT_STRAIGHTEN = + "android.media.effect.effects.StraightenEffect"; + + /** + * <p>Adjusts color temperature of the image.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The value of color temperature.</td> + * <td>Float, between 0 and 1, with 0 indicating cool, and 1 indicating warm. A value of + * of 0.5 indicates no change.</td> + * </tr> + * </table> + */ + public final static String EFFECT_TEMPERATURE = + "android.media.effect.effects.ColorTemperatureEffect"; + + /** + * <p>Tints the photo with specified color.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>tint</code></td> + * <td>The color of the tint.</td> + * <td>Integer, representing an ARGB color with 8 bits per channel. May be created using + * {@link android.graphics.Color Color} class.</td> + * </tr> + * </table> + */ + public final static String EFFECT_TINT = + "android.media.effect.effects.TintEffect"; + + /** + * <p>Adds a vignette effect to image, i.e. fades away the outer image edges.</p> + * <p>Available parameters:</p> + * <table> + * <tr><td>Parameter name</td><td>Meaning</td><td>Valid values</td></tr> + * <tr><td><code>scale</code></td> + * <td>The scale of vignetting.</td> + * <td>Float, between 0 and 1. 0 means no change.</td> + * </tr> + * </table> + */ + public final static String EFFECT_VIGNETTE = + "android.media.effect.effects.VignetteEffect"; + + EffectFactory(EffectContext effectContext) { + mEffectContext = effectContext; + } + + /** + * Instantiate a new effect with the given effect name. + * + * <p>The effect's parameters will be set to their default values.</p> + * + * <p>Note that the EGL context associated with the current EffectContext need not be made + * current when creating an effect. This allows the host application to instantiate effects + * before any EGL context has become current.</p> + * + * @param effectName The name of the effect to create. + * @return A new Effect instance. + * @throws IllegalArgumentException if the effect with the specified name is not supported or + * not known. + */ + public Effect createEffect(String effectName) { + Class effectClass = getEffectClassByName(effectName); + if (effectClass == null) { + throw new IllegalArgumentException("Cannot instantiate unknown effect '" + + effectName + "'!"); + } + return instantiateEffect(effectClass, effectName); + } + + /** + * Check if an effect is supported on this platform. + * + * <p>Some effects may only be available on certain platforms. Use this method before + * instantiating an effect to make sure it is supported.</p> + * + * @param effectName The name of the effect. + * @return true, if the effect is supported on this platform. + * @throws IllegalArgumentException if the effect name is not known. + */ + public static boolean isEffectSupported(String effectName) { + return getEffectClassByName(effectName) != null; + } + + private static Class getEffectClassByName(String className) { + Class effectClass = null; + + // Get context's classloader; otherwise cannot load non-framework effects + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + + // Look for the class in the imported packages + for (String packageName : EFFECT_PACKAGES) { + try { + effectClass = contextClassLoader.loadClass(packageName + className); + } catch (ClassNotFoundException e) { + continue; + } + // Exit loop if class was found. + if (effectClass != null) { + break; + } + } + return effectClass; + } + + private Effect instantiateEffect(Class effectClass, String name) { + // Make sure this is an Effect subclass + try { + effectClass.asSubclass(Effect.class); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Attempting to allocate effect '" + effectClass + + "' which is not a subclass of Effect!", e); + } + + // Look for the correct constructor + Constructor effectConstructor = null; + try { + effectConstructor = effectClass.getConstructor(EffectContext.class, String.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException("The effect class '" + effectClass + "' does not have " + + "the required constructor.", e); + } + + // Construct the effect + Effect effect = null; + try { + effect = (Effect)effectConstructor.newInstance(mEffectContext, name); + } catch (Throwable t) { + throw new RuntimeException("There was an error constructing the effect '" + effectClass + + "'!", t); + } + + return effect; + } +} diff --git a/media/mca/effect/java/android/media/effect/EffectUpdateListener.java b/media/mca/effect/java/android/media/effect/EffectUpdateListener.java new file mode 100644 index 0000000..155fe49 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/EffectUpdateListener.java @@ -0,0 +1,36 @@ +/* + * 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 android.media.effect; + +/** + * Some effects may issue callbacks to inform the host of changes to the effect state. This is the + * listener interface for receiving those callbacks. + */ +public interface EffectUpdateListener { + + /** + * Called when the effect state is updated. + * + * @param effect The effect that has been updated. + * @param info A value that gives more information about the update. See the effect's + * documentation for more details on what this object is. + */ + public void onEffectUpdated(Effect effect, Object info); + +} + diff --git a/media/mca/effect/java/android/media/effect/FilterEffect.java b/media/mca/effect/java/android/media/effect/FilterEffect.java new file mode 100644 index 0000000..d7c319e --- /dev/null +++ b/media/mca/effect/java/android/media/effect/FilterEffect.java @@ -0,0 +1,101 @@ +/* + * 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 android.media.effect; + +import android.filterfw.core.CachedFrameManager; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.format.ImageFormat; + +/** + * The FilterEffect class is the base class for all Effects based on Filters from the Mobile + * Filter Framework (MFF). + * @hide + */ +public abstract class FilterEffect extends Effect { + + protected EffectContext mEffectContext; + private String mName; + + /** + * Protected constructor as FilterEffects should be created by Factory. + */ + protected FilterEffect(EffectContext context, String name) { + mEffectContext = context; + mName = name; + } + + /** + * Get the effect name. + * + * Returns the unique name of the effect, which matches the name used for instantiating this + * effect by the EffectFactory. + * + * @return The name of the effect. + */ + @Override + public String getName() { + return mName; + } + + // Helper Methods for subclasses /////////////////////////////////////////////////////////////// + /** + * Call this before manipulating the GL context. Will assert that the GL environment is in a + * valid state, and save it. + */ + protected void beginGLEffect() { + mEffectContext.assertValidGLState(); + mEffectContext.saveGLState(); + } + + /** + * Call this after manipulating the GL context. Restores the previous GL state. + */ + protected void endGLEffect() { + mEffectContext.restoreGLState(); + } + + /** + * Returns the active filter context for this effect. + */ + protected FilterContext getFilterContext() { + return mEffectContext.mFilterContext; + } + + /** + * Converts a texture into a Frame. + */ + protected Frame frameFromTexture(int texId, int width, int height) { + FrameManager manager = getFilterContext().getFrameManager(); + FrameFormat format = ImageFormat.create(width, height, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + Frame frame = manager.newBoundFrame(format, + GLFrame.EXISTING_TEXTURE_BINDING, + texId); + frame.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + return frame; + } + +} + diff --git a/media/mca/effect/java/android/media/effect/FilterGraphEffect.java b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java new file mode 100644 index 0000000..b18bea8 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/FilterGraphEffect.java @@ -0,0 +1,120 @@ +/* + * 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 android.media.effect; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.GraphRunner; +import android.filterfw.core.SimpleScheduler; +import android.filterfw.core.SyncRunner; +import android.media.effect.Effect; +import android.media.effect.FilterEffect; +import android.media.effect.EffectContext; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.GraphReader; +import android.filterfw.io.TextGraphReader; + +import android.util.Log; + +/** + * Effect subclass for effects based on a single Filter. Subclasses need only invoke the + * constructor with the correct arguments to obtain an Effect implementation. + * + * @hide + */ +public class FilterGraphEffect extends FilterEffect { + + private static final String TAG = "FilterGraphEffect"; + + protected String mInputName; + protected String mOutputName; + protected GraphRunner mRunner; + protected FilterGraph mGraph; + protected Class mSchedulerClass; + + /** + * Constructs a new FilterGraphEffect. + * + * @param name The name of this effect (used to create it in the EffectFactory). + * @param graphString The graph string to create the graph. + * @param inputName The name of the input GLTextureSource filter. + * @param outputName The name of the output GLTextureSource filter. + */ + public FilterGraphEffect(EffectContext context, + String name, + String graphString, + String inputName, + String outputName, + Class scheduler) { + super(context, name); + + mInputName = inputName; + mOutputName = outputName; + mSchedulerClass = scheduler; + createGraph(graphString); + + } + + private void createGraph(String graphString) { + GraphReader reader = new TextGraphReader(); + try { + mGraph = reader.readGraphString(graphString); + } catch (GraphIOException e) { + throw new RuntimeException("Could not setup effect", e); + } + + if (mGraph == null) { + throw new RuntimeException("Could not setup effect"); + } + mRunner = new SyncRunner(getFilterContext(), mGraph, mSchedulerClass); + } + + @Override + public void apply(int inputTexId, int width, int height, int outputTexId) { + beginGLEffect(); + Filter src = mGraph.getFilter(mInputName); + if (src != null) { + src.setInputValue("texId", inputTexId); + src.setInputValue("width", width); + src.setInputValue("height", height); + } else { + throw new RuntimeException("Internal error applying effect"); + } + Filter dest = mGraph.getFilter(mOutputName); + if (dest != null) { + dest.setInputValue("texId", outputTexId); + } else { + throw new RuntimeException("Internal error applying effect"); + } + try { + mRunner.run(); + } catch (RuntimeException e) { + throw new RuntimeException("Internal error applying effect: ", e); + } + endGLEffect(); + } + + @Override + public void setParameter(String parameterKey, Object value) { + } + + @Override + public void release() { + mGraph.tearDown(getFilterContext()); + mGraph = null; + } +} diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java new file mode 100644 index 0000000..6f85861 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java @@ -0,0 +1,98 @@ +/* + * 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 android.media.effect; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterFunction; +import android.filterfw.core.Frame; +import android.media.effect.Effect; +import android.media.effect.EffectContext; + +import android.util.Log; + +/** + * Effect subclass for effects based on a single Filter. Subclasses need only invoke the + * constructor with the correct arguments to obtain an Effect implementation. + * + * @hide + */ +public class SingleFilterEffect extends FilterEffect { + + protected FilterFunction mFunction; + protected String mInputName; + protected String mOutputName; + + /** + * Constructs a new FilterFunctionEffect. + * + * @param name The name of this effect (used to create it in the EffectFactory). + * @param filterClass The class of the filter to wrap. + * @param inputName The name of the input image port. + * @param outputName The name of the output image port. + * @param finalParameters Key-value pairs of final input port assignments. + */ + public SingleFilterEffect(EffectContext context, + String name, + Class filterClass, + String inputName, + String outputName, + Object... finalParameters) { + super(context, name); + + mInputName = inputName; + mOutputName = outputName; + + String filterName = filterClass.getSimpleName(); + FilterFactory factory = FilterFactory.sharedFactory(); + Filter filter = factory.createFilterByClass(filterClass, filterName); + filter.initWithAssignmentList(finalParameters); + + mFunction = new FilterFunction(getFilterContext(), filter); + } + + @Override + public void apply(int inputTexId, int width, int height, int outputTexId) { + beginGLEffect(); + + Frame inputFrame = frameFromTexture(inputTexId, width, height); + Frame outputFrame = frameFromTexture(outputTexId, width, height); + + Frame resultFrame = mFunction.executeWithArgList(mInputName, inputFrame); + + outputFrame.setDataFromFrame(resultFrame); + + inputFrame.release(); + outputFrame.release(); + resultFrame.release(); + + endGLEffect(); + } + + @Override + public void setParameter(String parameterKey, Object value) { + mFunction.setInputValue(parameterKey, value); + } + + @Override + public void release() { + mFunction.tearDown(); + mFunction = null; + } +} + diff --git a/media/mca/effect/java/android/media/effect/SizeChangeEffect.java b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java new file mode 100644 index 0000000..4d27bae --- /dev/null +++ b/media/mca/effect/java/android/media/effect/SizeChangeEffect.java @@ -0,0 +1,65 @@ +/* + * 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 android.media.effect; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterFunction; +import android.filterfw.core.Frame; +import android.media.effect.Effect; +import android.media.effect.EffectContext; + +import android.util.Log; + +/** + * Effect subclass for effects based on a single Filter with output size differnet + * from input. Subclasses need only invoke the constructor with the correct arguments + * to obtain an Effect implementation. + * + * @hide + */ +public class SizeChangeEffect extends SingleFilterEffect { + + public SizeChangeEffect(EffectContext context, + String name, + Class filterClass, + String inputName, + String outputName, + Object... finalParameters) { + super(context, name, filterClass, inputName, outputName, finalParameters); + } + + @Override + public void apply(int inputTexId, int width, int height, int outputTexId) { + beginGLEffect(); + + Frame inputFrame = frameFromTexture(inputTexId, width, height); + Frame resultFrame = mFunction.executeWithArgList(mInputName, inputFrame); + + int outputWidth = resultFrame.getFormat().getWidth(); + int outputHeight = resultFrame.getFormat().getHeight(); + + Frame outputFrame = frameFromTexture(outputTexId, outputWidth, outputHeight); + outputFrame.setDataFromFrame(resultFrame); + + inputFrame.release(); + outputFrame.release(); + resultFrame.release(); + + endGLEffect(); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java b/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java new file mode 100644 index 0000000..44a141b --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.AutoFixFilter; + +/** + * @hide + */ +public class AutoFixEffect extends SingleFilterEffect { + public AutoFixEffect(EffectContext context, String name) { + super(context, name, AutoFixFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java new file mode 100644 index 0000000..d5c7aaa --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java @@ -0,0 +1,102 @@ +/* + * 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 android.media.effect.effects; + +import android.filterfw.core.Filter; +import android.filterfw.core.OneShotScheduler; +import android.media.effect.EffectContext; +import android.media.effect.FilterGraphEffect; +import android.media.effect.EffectUpdateListener; + +import android.filterpacks.videoproc.BackDropperFilter; +import android.filterpacks.videoproc.BackDropperFilter.LearningDoneListener; + +/** + * Background replacement Effect. + * + * Replaces the background of the input video stream with a selected video + * Learns the background when it first starts up; + * needs unobstructed view of background when this happens. + * + * Effect parameters: + * source: A URI for the background video + * Listener: Called when learning period is complete + * + * @hide + */ +public class BackDropperEffect extends FilterGraphEffect { + private static final String mGraphDefinition = + "@import android.filterpacks.base;\n" + + "@import android.filterpacks.videoproc;\n" + + "@import android.filterpacks.videosrc;\n" + + "\n" + + "@filter GLTextureSource foreground {\n" + + " texId = 0;\n" + // Will be set by base class + " width = 0;\n" + + " height = 0;\n" + + " repeatFrame = true;\n" + + "}\n" + + "\n" + + "@filter MediaSource background {\n" + + " sourceUrl = \"no_file_specified\";\n" + + " waitForNewFrame = false;\n" + + " sourceIsUrl = true;\n" + + "}\n" + + "\n" + + "@filter BackDropperFilter replacer {\n" + + " autowbToggle = 1;\n" + + "}\n" + + "\n" + + "@filter GLTextureTarget output {\n" + + " texId = 0;\n" + + "}\n" + + "\n" + + "@connect foreground[frame] => replacer[video];\n" + + "@connect background[video] => replacer[background];\n" + + "@connect replacer[video] => output[frame];\n"; + + private EffectUpdateListener mEffectListener = null; + + private LearningDoneListener mLearningListener = new LearningDoneListener() { + public void onLearningDone(BackDropperFilter filter) { + if (mEffectListener != null) { + mEffectListener.onEffectUpdated(BackDropperEffect.this, null); + } + } + }; + + public BackDropperEffect(EffectContext context, String name) { + super(context, name, mGraphDefinition, "foreground", "output", OneShotScheduler.class); + + Filter replacer = mGraph.getFilter("replacer"); + replacer.setInputValue("learningDoneListener", mLearningListener); + } + + @Override + public void setParameter(String parameterKey, Object value) { + if (parameterKey.equals("source")) { + Filter background = mGraph.getFilter("background"); + background.setInputValue("sourceUrl", value); + } + } + + @Override + public void setUpdateListener(EffectUpdateListener listener) { + mEffectListener = listener; + } + +}
\ No newline at end of file diff --git a/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java b/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java new file mode 100644 index 0000000..43f461c --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java @@ -0,0 +1,32 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.BitmapOverlayFilter; + +/** + * @hide + */ +public class BitmapOverlayEffect extends SingleFilterEffect { + public BitmapOverlayEffect(EffectContext context, String name) { + super(context, name, BitmapOverlayFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java b/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java new file mode 100644 index 0000000..771afff --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.BlackWhiteFilter; + +/** + * @hide + */ +public class BlackWhiteEffect extends SingleFilterEffect { + public BlackWhiteEffect(EffectContext context, String name) { + super(context, name, BlackWhiteFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java b/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java new file mode 100644 index 0000000..774e72f --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java @@ -0,0 +1,32 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.BrightnessFilter; + +/** + * @hide + */ +public class BrightnessEffect extends SingleFilterEffect { + public BrightnessEffect(EffectContext context, String name) { + super(context, name, BrightnessFilter.class, "image", "image"); + } +} + diff --git a/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java b/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java new file mode 100644 index 0000000..62d98ce --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.ColorTemperatureFilter; + +/** + * @hide + */ +public class ColorTemperatureEffect extends SingleFilterEffect { + public ColorTemperatureEffect(EffectContext context, String name) { + super(context, name, ColorTemperatureFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java b/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java new file mode 100644 index 0000000..d5bfc21 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/ContrastEffect.java @@ -0,0 +1,32 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.ContrastFilter; + +/** + * @hide + */ +public class ContrastEffect extends SingleFilterEffect { + public ContrastEffect(EffectContext context, String name) { + super(context, name, ContrastFilter.class, "image", "image"); + } +} + diff --git a/media/mca/effect/java/android/media/effect/effects/CropEffect.java b/media/mca/effect/java/android/media/effect/effects/CropEffect.java new file mode 100644 index 0000000..3e8d78a --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/CropEffect.java @@ -0,0 +1,33 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SizeChangeEffect; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.CropRectFilter; + +/** + * @hide + */ +//public class CropEffect extends SingleFilterEffect { +public class CropEffect extends SizeChangeEffect { + public CropEffect(EffectContext context, String name) { + super(context, name, CropRectFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java b/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java new file mode 100644 index 0000000..d7a7df5 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.CrossProcessFilter; + +/** + * @hide + */ +public class CrossProcessEffect extends SingleFilterEffect { + public CrossProcessEffect(EffectContext context, String name) { + super(context, name, CrossProcessFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java b/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java new file mode 100644 index 0000000..1a5ea35 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java @@ -0,0 +1,30 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.DocumentaryFilter; + +/** + * @hide + */ +public class DocumentaryEffect extends SingleFilterEffect { + public DocumentaryEffect(EffectContext context, String name) { + super(context, name, DocumentaryFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java b/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java new file mode 100644 index 0000000..1391b1f --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.DuotoneFilter; + +/** + * @hide + */ +public class DuotoneEffect extends SingleFilterEffect { + public DuotoneEffect(EffectContext context, String name) { + super(context, name, DuotoneFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java b/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java new file mode 100644 index 0000000..5260de3 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/FillLightEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.FillLightFilter; + +/** + * @hide + */ +public class FillLightEffect extends SingleFilterEffect { + public FillLightEffect(EffectContext context, String name) { + super(context, name, FillLightFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java b/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java new file mode 100644 index 0000000..6abfe42 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java @@ -0,0 +1,32 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.FisheyeFilter; + +/** + * @hide + */ +public class FisheyeEffect extends SingleFilterEffect { + public FisheyeEffect(EffectContext context, String name) { + super(context, name, FisheyeFilter.class, "image", "image"); + } +} + diff --git a/media/mca/effect/java/android/media/effect/effects/FlipEffect.java b/media/mca/effect/java/android/media/effect/effects/FlipEffect.java new file mode 100644 index 0000000..0f5c421 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/FlipEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.FlipFilter; + +/** + * @hide + */ +public class FlipEffect extends SingleFilterEffect { + public FlipEffect(EffectContext context, String name) { + super(context, name, FlipFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/GrainEffect.java b/media/mca/effect/java/android/media/effect/effects/GrainEffect.java new file mode 100644 index 0000000..2fda7e9 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/GrainEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.GrainFilter; + +/** + * @hide + */ +public class GrainEffect extends SingleFilterEffect { + public GrainEffect(EffectContext context, String name) { + super(context, name, GrainFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java b/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java new file mode 100644 index 0000000..26ca081 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.ToGrayFilter; + +/** + * @hide + */ +public class GrayscaleEffect extends SingleFilterEffect { + public GrayscaleEffect(EffectContext context, String name) { + super(context, name, ToGrayFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java b/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java new file mode 100644 index 0000000..d07779e --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/IdentityEffect.java @@ -0,0 +1,58 @@ +/* + * 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 android.media.effect.effects; + +import android.filterfw.core.Frame; +import android.media.effect.EffectContext; +import android.media.effect.FilterEffect; + +/** + * @hide + */ +public class IdentityEffect extends FilterEffect { + + public IdentityEffect(EffectContext context, String name) { + super(context, name); + } + + @Override + public void apply(int inputTexId, int width, int height, int outputTexId) { + beginGLEffect(); + + Frame inputFrame = frameFromTexture(inputTexId, width, height); + Frame outputFrame = frameFromTexture(outputTexId, width, height); + + outputFrame.setDataFromFrame(inputFrame); + + inputFrame.release(); + outputFrame.release(); + + endGLEffect(); + } + + @Override + public void setParameter(String parameterKey, Object value) { + throw new IllegalArgumentException("Unknown parameter " + parameterKey + + " for IdentityEffect!"); + } + + @Override + public void release() { + } +} + diff --git a/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java b/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java new file mode 100644 index 0000000..776e53c --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/LomoishEffect.java @@ -0,0 +1,30 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.LomoishFilter; + +/** + * @hide + */ +public class LomoishEffect extends SingleFilterEffect { + public LomoishEffect(EffectContext context, String name) { + super(context, name, LomoishFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java b/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java new file mode 100644 index 0000000..29fc94a --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/NegativeEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.NegativeFilter; + +/** + * @hide + */ +public class NegativeEffect extends SingleFilterEffect { + public NegativeEffect(EffectContext context, String name) { + super(context, name, NegativeFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java b/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java new file mode 100644 index 0000000..20a8a37 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.PosterizeFilter; + +/** + * @hide + */ +public class PosterizeEffect extends SingleFilterEffect { + public PosterizeEffect(EffectContext context, String name) { + super(context, name, PosterizeFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java b/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java new file mode 100644 index 0000000..8ed9909 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java @@ -0,0 +1,32 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.RedEyeFilter; + +/** + * @hide + */ +public class RedEyeEffect extends SingleFilterEffect { + public RedEyeEffect(EffectContext context, String name) { + super(context, name, RedEyeFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/RotateEffect.java b/media/mca/effect/java/android/media/effect/effects/RotateEffect.java new file mode 100644 index 0000000..2340015 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/RotateEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SizeChangeEffect; +import android.filterpacks.imageproc.RotateFilter; + +/** + * @hide + */ +public class RotateEffect extends SizeChangeEffect { + public RotateEffect(EffectContext context, String name) { + super(context, name, RotateFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java b/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java new file mode 100644 index 0000000..fe9250a --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/SaturateEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.SaturateFilter; + +/** + * @hide + */ +public class SaturateEffect extends SingleFilterEffect { + public SaturateEffect(EffectContext context, String name) { + super(context, name, SaturateFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java b/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java new file mode 100644 index 0000000..de85b2d --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/SepiaEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.SepiaFilter; + +/** + * @hide + */ +public class SepiaEffect extends SingleFilterEffect { + public SepiaEffect(EffectContext context, String name) { + super(context, name, SepiaFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java b/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java new file mode 100644 index 0000000..46776eb --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/SharpenEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.SharpenFilter; + +/** + * @hide + */ +public class SharpenEffect extends SingleFilterEffect { + public SharpenEffect(EffectContext context, String name) { + super(context, name, SharpenFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java b/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java new file mode 100644 index 0000000..49253a0 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/StraightenEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.StraightenFilter; + +/** + * @hide + */ +public class StraightenEffect extends SingleFilterEffect { + public StraightenEffect(EffectContext context, String name) { + super(context, name, StraightenFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/TintEffect.java b/media/mca/effect/java/android/media/effect/effects/TintEffect.java new file mode 100644 index 0000000..6de9ea8 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/TintEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.TintFilter; + +/** + * @hide + */ +public class TintEffect extends SingleFilterEffect { + public TintEffect(EffectContext context, String name) { + super(context, name, TintFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java b/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java new file mode 100644 index 0000000..b143d77 --- /dev/null +++ b/media/mca/effect/java/android/media/effect/effects/VignetteEffect.java @@ -0,0 +1,31 @@ +/* + * 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 android.media.effect.effects; + +import android.media.effect.EffectContext; +import android.media.effect.SingleFilterEffect; +import android.filterpacks.imageproc.VignetteFilter; + +/** + * @hide + */ +public class VignetteEffect extends SingleFilterEffect { + public VignetteEffect(EffectContext context, String name) { + super(context, name, VignetteFilter.class, "image", "image"); + } +} diff --git a/media/mca/effect/java/android/media/effect/package-info.java b/media/mca/effect/java/android/media/effect/package-info.java new file mode 100644 index 0000000..b2c14ff --- /dev/null +++ b/media/mca/effect/java/android/media/effect/package-info.java @@ -0,0 +1,28 @@ +/* + * 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 android.media.effect; + +/** + * <h1>Effect Framework</h1> + * + * This package includes a collection of high-performance visual effects that make use of the + * mobile filter framework subsystem. + * + * TODO: More Documentation + * + */ diff --git a/media/mca/effect/java/android/media/effect/package.html b/media/mca/effect/java/android/media/effect/package.html new file mode 100644 index 0000000..8a210fd --- /dev/null +++ b/media/mca/effect/java/android/media/effect/package.html @@ -0,0 +1,54 @@ +<HTML> +<BODY> +<p>Provides classes that allow you to apply a variety of visual effects to images and +videos. For example, you can easily fix red-eye, convert an image to grayscale, +adjust brightness, adjust saturation, rotate an image, apply a fisheye effect, and much more. The +system performs all effects processing on the GPU to obtain maximum performance.</p> + +<p>For maximum performance, effects are applied directly to OpenGL textures, so your application +must have a valid OpenGL context before it can use the effects APIs. The textures to which you apply +effects may be from bitmaps, videos or even the camera. However, there are certain restrictions that +textures must meet:</p> +<ol> +<li>They must be bound to a {@link android.opengl.GLES20#GL_TEXTURE_2D} texture image</li> +<li>They must contain at least one mipmap level</li> +</ol> + +<p>An {@link android.media.effect.Effect} object defines a single media effect that you can apply to +an image frame. The basic workflow to create an {@link android.media.effect.Effect} is:</p> + +<ol> +<li>Call {@link android.media.effect.EffectContext#createWithCurrentGlContext +EffectContext.createWithCurrentGlContext()} from your OpenGL ES 2.0 context.</li> +<li>Use the returned {@link android.media.effect.EffectContext} to call {@link +android.media.effect.EffectContext#getFactory EffectContext.getFactory()}, which returns an instance +of {@link android.media.effect.EffectFactory}.</li> +<li>Call {@link android.media.effect.EffectFactory#createEffect createEffect()}, passing it an +effect name from @link android.media.effect.EffectFactory}, such as {@link +android.media.effect.EffectFactory#EFFECT_FISHEYE} or {@link +android.media.effect.EffectFactory#EFFECT_VIGNETTE}.</li> +</ol> + +<p>You can adjust an effect’s parameters by calling {@link android.media.effect.Effect#setParameter +setParameter()} and passing a parameter name and parameter value. Each type of effect accepts +different parameters, which are documented with the effect name. For example, {@link +android.media.effect.EffectFactory#EFFECT_FISHEYE} has one parameter for the {@code scale} of the +distortion.</p> + +<p>To apply an effect on a texture, call {@link android.media.effect.Effect#apply apply()} on the +{@link +android.media.effect.Effect} and pass in the input texture, its width and height, and the output +texture. The input texture must be bound to a {@link android.opengl.GLES20#GL_TEXTURE_2D} texture +image (usually done by calling the {@link android.opengl.GLES20#glTexImage2D glTexImage2D()} +function). You may provide multiple mipmap levels. If the output texture has not been bound to a +texture image, it will be automatically bound by the effect as a {@link +android.opengl.GLES20#GL_TEXTURE_2D} and with one mipmap level (0), which will have the same +size as the input.</p> + +<p class="note"><strong>Note:</strong> All effects listed in {@link +android.media.effect.EffectFactory} are guaranteed to be supported. However, some additional effects +available from external libraries are not supported by all devices, so you must first check if the +desired effect from the external library is supported by calling {@link +android.media.effect.EffectFactory#isEffectSupported isEffectSupported()}.</p> +</BODY> +</HTML> diff --git a/media/mca/filterfw/Android.mk b/media/mca/filterfw/Android.mk new file mode 100644 index 0000000..b822e99 --- /dev/null +++ b/media/mca/filterfw/Android.mk @@ -0,0 +1,53 @@ +# 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. +# + +##################### +# Build native sublibraries + +include $(all-subdir-makefiles) + +##################### +# Build main libfilterfw + +include $(CLEAR_VARS) + +LOCAL_MODULE := libfilterfw + +LOCAL_MODULE_TAGS := optional + +LOCAL_WHOLE_STATIC_LIBRARIES := libfilterfw_jni \ + libfilterfw_native + +LOCAL_SHARED_LIBRARIES := libstlport \ + libGLESv2 \ + libEGL \ + libgui \ + libdl \ + libcutils \ + libutils \ + libandroid \ + libjnigraphics \ + libmedia \ + libmedia_native + +# Don't prelink this library. For more efficient code, you may want +# to add this library to the prelink map and set this to true. However, +# it's difficult to do this for applications that are not supplied as +# part of a system image. +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java new file mode 100644 index 0000000..3f36d98 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java @@ -0,0 +1,99 @@ +/* + * 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 android.filterfw; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterFunction; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameManager; + +/** + * A FilterFunctionEnvironment provides a simple functional front-end to manually executing + * filters. Use this environment if a graph-based approach is not convenient for your case. + * Typically, a FilterFunctionEnvironment is used as follows: + * 1. Instantiate a new FilterFunctionEnvironment instance. + * 2. Perform any configuration, such as setting a GL environment. + * 3. Wrap Filters into FilterFunctions by calling createFunction(). + * 4. Execute FilterFunctions individually and use the results for further processing. + * Additionally, there is a convenience method to execute a number of filters in sequence. + * @hide + */ +public class FilterFunctionEnvironment extends MffEnvironment { + + /** + * Create a new FilterFunctionEnvironment with the default components. + */ + public FilterFunctionEnvironment() { + super(null); + } + + /** + * Create a new FilterFunctionEnvironment with a custom FrameManager. Pass null to auto-create + * a FrameManager. + * + * @param frameManager The FrameManager to use, or null to auto-create one. + */ + public FilterFunctionEnvironment(FrameManager frameManager) { + super(frameManager); + } + + /** + * Create a new FilterFunction from a specific filter class. The function is initialized with + * the given key-value list of parameters. Note, that this function uses the default shared + * FilterFactory to create the filter instance. + * + * @param filterClass The class of the filter to wrap. This must be a Filter subclass. + * @param parameters An argument list of alternating key-value filter parameters. + * @return A new FilterFunction instance. + */ + public FilterFunction createFunction(Class filterClass, Object... parameters) { + String filterName = "FilterFunction(" + filterClass.getSimpleName() + ")"; + Filter filter = FilterFactory.sharedFactory().createFilterByClass(filterClass, filterName); + filter.initWithAssignmentList(parameters); + return new FilterFunction(getContext(), filter); + } + + /** + * Convenience method to execute a sequence of filter functions. Note that every function in + * the list MUST have one input and one output port, except the first filter (which must not + * have any input ports) and the last filter (which may not have any output ports). + * + * @param functions A list of filter functions. The first filter must be a source filter. + * @return The result of the last filter executed, or null if the last filter did not + produce any output. + * + public Frame executeSequence(FilterFunction[] functions) { + Frame oldFrame = null; + Frame newFrame = null; + for (FilterFunction filterFunction : functions) { + if (oldFrame == null) { + newFrame = filterFunction.executeWithArgList(); + } else { + newFrame = filterFunction.executeWithArgList(oldFrame); + oldFrame.release(); + } + oldFrame = newFrame; + } + if (oldFrame != null) { + oldFrame.release(); + } + return newFrame; + }*/ + +} diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java new file mode 100644 index 0000000..5f6d45c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java @@ -0,0 +1,197 @@ +/* + * 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 android.filterfw; + +import android.content.Context; +import android.filterfw.core.AsyncRunner; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GraphRunner; +import android.filterfw.core.RoundRobinScheduler; +import android.filterfw.core.SyncRunner; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.GraphReader; +import android.filterfw.io.TextGraphReader; + +import java.util.ArrayList; + +/** + * A GraphEnvironment provides a simple front-end to filter graph setup and execution using the + * mobile filter framework. Typically, you use a GraphEnvironment in the following fashion: + * 1. Instantiate a new GraphEnvironment instance. + * 2. Perform any configuration, such as adding graph references and setting a GL environment. + * 3. Load a graph file using loadGraph() or add a graph using addGraph(). + * 4. Obtain a GraphRunner instance using getRunner(). + * 5. Execute the obtained runner. + * Note that it is possible to add multiple graphs and runners to a single GraphEnvironment. + * + * @hide + */ +public class GraphEnvironment extends MffEnvironment { + + public static final int MODE_ASYNCHRONOUS = 1; + public static final int MODE_SYNCHRONOUS = 2; + + private GraphReader mGraphReader; + private ArrayList<GraphHandle> mGraphs = new ArrayList<GraphHandle>(); + + private class GraphHandle { + private FilterGraph mGraph; + private AsyncRunner mAsyncRunner; + private SyncRunner mSyncRunner; + + public GraphHandle(FilterGraph graph) { + mGraph = graph; + } + + public FilterGraph getGraph() { + return mGraph; + } + + public AsyncRunner getAsyncRunner(FilterContext environment) { + if (mAsyncRunner == null) { + mAsyncRunner = new AsyncRunner(environment, RoundRobinScheduler.class); + mAsyncRunner.setGraph(mGraph); + } + return mAsyncRunner; + } + + public GraphRunner getSyncRunner(FilterContext environment) { + if (mSyncRunner == null) { + mSyncRunner = new SyncRunner(environment, mGraph, RoundRobinScheduler.class); + } + return mSyncRunner; + } + } + + /** + * Create a new GraphEnvironment with default components. + */ + public GraphEnvironment() { + super(null); + } + + /** + * Create a new GraphEnvironment with a custom FrameManager and GraphReader. Specifying null + * for either of these, will auto-create a default instance. + * + * @param frameManager The FrameManager to use, or null to auto-create one. + * @param reader The GraphReader to use for graph loading, or null to auto-create one. + * Note, that the reader will not be created until it is required. Pass + * null if you will not load any graph files. + */ + public GraphEnvironment(FrameManager frameManager, GraphReader reader) { + super(frameManager); + mGraphReader = reader; + } + + /** + * Returns the used graph reader. This will create one, if a reader has not been set already. + */ + public GraphReader getGraphReader() { + if (mGraphReader == null) { + mGraphReader = new TextGraphReader(); + } + return mGraphReader; + } + + /** + * Add graph references to resolve during graph reading. The references added here are shared + * among all graphs. + * + * @param references An alternating argument list of keys (Strings) and values. + */ + public void addReferences(Object... references) { + getGraphReader().addReferencesByKeysAndValues(references); + } + + /** + * Loads a graph file from the specified resource and adds it to this environment. + * + * @param context The context in which to read the resource. + * @param resourceId The ID of the graph resource to load. + * @return A unique ID for the graph. + */ + public int loadGraph(Context context, int resourceId) { + // Read the file into a graph + FilterGraph graph = null; + try { + graph = getGraphReader().readGraphResource(context, resourceId); + } catch (GraphIOException e) { + throw new RuntimeException("Could not read graph: " + e.getMessage()); + } + + // Add graph to our list of graphs + return addGraph(graph); + } + + /** + * Add a graph to the environment. Consider using loadGraph() if you are loading a graph from + * a graph file. + * + * @param graph The graph to add to the environment. + * @return A unique ID for the added graph. + */ + public int addGraph(FilterGraph graph) { + GraphHandle graphHandle = new GraphHandle(graph); + mGraphs.add(graphHandle); + return mGraphs.size() - 1; + } + + /** + * Access a specific graph of this environment given a graph ID (previously returned from + * loadGraph() or addGraph()). Throws an InvalidArgumentException if no graph with the + * specified ID could be found. + * + * @param graphId The ID of the graph to get. + * @return The graph with the specified ID. + */ + public FilterGraph getGraph(int graphId) { + if (graphId < 0 || graphId >= mGraphs.size()) { + throw new IllegalArgumentException( + "Invalid graph ID " + graphId + " specified in runGraph()!"); + } + return mGraphs.get(graphId).getGraph(); + } + + /** + * Get a GraphRunner instance for the graph with the specified ID. The GraphRunner instance can + * be used to execute the graph. Throws an InvalidArgumentException if no graph with the + * specified ID could be found. + * + * @param graphId The ID of the graph to get. + * @param executionMode The mode of graph execution. Currently this can be either + MODE_SYNCHRONOUS or MODE_ASYNCHRONOUS. + * @return A GraphRunner instance for this graph. + */ + public GraphRunner getRunner(int graphId, int executionMode) { + switch (executionMode) { + case MODE_ASYNCHRONOUS: + return mGraphs.get(graphId).getAsyncRunner(getContext()); + + case MODE_SYNCHRONOUS: + return mGraphs.get(graphId).getSyncRunner(getContext()); + + default: + throw new RuntimeException( + "Invalid execution mode " + executionMode + " specified in getRunner()!"); + } + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/MffEnvironment.java b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java new file mode 100644 index 0000000..1ab416a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java @@ -0,0 +1,106 @@ +/* + * 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 android.filterfw; + +import android.filterfw.core.CachedFrameManager; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLEnvironment; + +/** + * Base class for mobile filter framework (MFF) frontend environments. These convenience classes + * allow using the filter framework without the requirement of performing manual setup of its + * required components. + * + * @hide + */ +public class MffEnvironment { + + private FilterContext mContext; + + /** + * Protected constructor to initialize the environment's essential components. These are the + * frame-manager and the filter-context. Passing in null for the frame-manager causes this + * to be auto-created. + * + * @param frameManager The FrameManager to use or null to auto-create one. + */ + protected MffEnvironment(FrameManager frameManager) { + // Get or create the frame manager + if (frameManager == null) { + frameManager = new CachedFrameManager(); + } + + // Setup the environment + mContext = new FilterContext(); + mContext.setFrameManager(frameManager); + + } + + /** + * Returns the environment's filter-context. + */ + public FilterContext getContext() { + return mContext; + } + + /** + * Set the environment's GL environment to the specified environment. This does not activate + * the environment. + */ + public void setGLEnvironment(GLEnvironment glEnvironment) { + mContext.initGLEnvironment(glEnvironment); + } + + /** + * Create and activate a new GL environment for use in this filter context. + */ + public void createGLEnvironment() { + GLEnvironment glEnvironment = new GLEnvironment(); + glEnvironment.initWithNewContext(); + setGLEnvironment(glEnvironment); + } + + /** + * Activate the GL environment for use in the current thread. A GL environment must have been + * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this after + * having switched to a new thread for GL filter execution. + */ + public void activateGLEnvironment() { + GLEnvironment glEnv = mContext.getGLEnvironment(); + if (glEnv != null) { + mContext.getGLEnvironment().activate(); + } else { + throw new NullPointerException("No GLEnvironment in place to activate!"); + } + } + + /** + * Deactivate the GL environment from use in the current thread. A GL environment must have been + * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this before + * running GL filters in another thread. + */ + public void deactivateGLEnvironment() { + GLEnvironment glEnv = mContext.getGLEnvironment(); + if (glEnv != null) { + mContext.getGLEnvironment().deactivate(); + } else { + throw new NullPointerException("No GLEnvironment in place to deactivate!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java new file mode 100644 index 0000000..70cbad4 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java @@ -0,0 +1,247 @@ +/* + * 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 android.filterfw.core; + +import android.os.AsyncTask; +import android.os.Handler; + +import android.util.Log; + +import java.lang.InterruptedException; +import java.lang.Runnable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; + +/** + * @hide + */ +public class AsyncRunner extends GraphRunner{ + + private Class mSchedulerClass; + private SyncRunner mRunner; + private AsyncRunnerTask mRunTask; + + private OnRunnerDoneListener mDoneListener; + private boolean isProcessing; + + private Exception mException; + + private class RunnerResult { + public int status = RESULT_UNKNOWN; + public Exception exception; + } + + private class AsyncRunnerTask extends AsyncTask<SyncRunner, Void, RunnerResult> { + + private static final String TAG = "AsyncRunnerTask"; + + @Override + protected RunnerResult doInBackground(SyncRunner... runner) { + RunnerResult result = new RunnerResult(); + try { + if (runner.length > 1) { + throw new RuntimeException("More than one runner received!"); + } + + runner[0].assertReadyToStep(); + + // Preparation + if (mLogVerbose) Log.v(TAG, "Starting background graph processing."); + activateGlContext(); + + if (mLogVerbose) Log.v(TAG, "Preparing filter graph for processing."); + runner[0].beginProcessing(); + + if (mLogVerbose) Log.v(TAG, "Running graph."); + + // Run loop + result.status = RESULT_RUNNING; + while (!isCancelled() && result.status == RESULT_RUNNING) { + if (!runner[0].performStep()) { + result.status = runner[0].determinePostRunState(); + if (result.status == GraphRunner.RESULT_SLEEPING) { + runner[0].waitUntilWake(); + result.status = RESULT_RUNNING; + } + } + } + + // Cleanup + if (isCancelled()) { + result.status = RESULT_STOPPED; + } + } catch (Exception exception) { + result.exception = exception; + result.status = RESULT_ERROR; + } + + // Deactivate context. + try { + deactivateGlContext(); + } catch (Exception exception) { + result.exception = exception; + result.status = RESULT_ERROR; + } + + if (mLogVerbose) Log.v(TAG, "Done with background graph processing."); + return result; + } + + @Override + protected void onCancelled(RunnerResult result) { + onPostExecute(result); + } + + @Override + protected void onPostExecute(RunnerResult result) { + if (mLogVerbose) Log.v(TAG, "Starting post-execute."); + setRunning(false); + if (result == null) { + // Cancelled before got to doInBackground + result = new RunnerResult(); + result.status = RESULT_STOPPED; + } + setException(result.exception); + if (result.status == RESULT_STOPPED || result.status == RESULT_ERROR) { + if (mLogVerbose) Log.v(TAG, "Closing filters."); + try { + mRunner.close(); + } catch (Exception exception) { + result.status = RESULT_ERROR; + setException(exception); + } + } + if (mDoneListener != null) { + if (mLogVerbose) Log.v(TAG, "Calling graph done callback."); + mDoneListener.onRunnerDone(result.status); + } + if (mLogVerbose) Log.v(TAG, "Completed post-execute."); + } + } + + private boolean mLogVerbose; + private static final String TAG = "AsyncRunner"; + + /** Create a new asynchronous graph runner with the given filter + * context, and the given scheduler class. + * + * Must be created on the UI thread. + */ + public AsyncRunner(FilterContext context, Class schedulerClass) { + super(context); + + mSchedulerClass = schedulerClass; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Create a new asynchronous graph runner with the given filter + * context. Uses a default scheduler. + * + * Must be created on the UI thread. + */ + public AsyncRunner(FilterContext context) { + super(context); + + mSchedulerClass = SimpleScheduler.class; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Set a callback to be called in the UI thread once the AsyncRunner + * completes running a graph, whether the completion is due to a stop() call + * or the filters running out of data to process. + */ + @Override + public void setDoneCallback(OnRunnerDoneListener listener) { + mDoneListener = listener; + } + + /** Sets the graph to be run. Will call prepare() on graph. Cannot be called + * when a graph is already running. + */ + synchronized public void setGraph(FilterGraph graph) { + if (isRunning()) { + throw new RuntimeException("Graph is already running!"); + } + mRunner = new SyncRunner(mFilterContext, graph, mSchedulerClass); + } + + @Override + public FilterGraph getGraph() { + return mRunner != null ? mRunner.getGraph() : null; + } + + /** Execute the graph in a background thread. */ + @Override + synchronized public void run() { + if (mLogVerbose) Log.v(TAG, "Running graph."); + setException(null); + + if (isRunning()) { + throw new RuntimeException("Graph is already running!"); + } + if (mRunner == null) { + throw new RuntimeException("Cannot run before a graph is set!"); + } + mRunTask = this.new AsyncRunnerTask(); + + setRunning(true); + mRunTask.execute(mRunner); + } + + /** Stop graph execution. This is an asynchronous call; register a callback + * with setDoneCallback to be notified of when the background processing has + * been completed. Calling stop will close the filter graph. */ + @Override + synchronized public void stop() { + if (mRunTask != null && !mRunTask.isCancelled() ) { + if (mLogVerbose) Log.v(TAG, "Stopping graph."); + mRunTask.cancel(false); + } + } + + @Override + synchronized public void close() { + if (isRunning()) { + throw new RuntimeException("Cannot close graph while it is running!"); + } + if (mLogVerbose) Log.v(TAG, "Closing filters."); + mRunner.close(); + } + + /** Check if background processing is happening */ + @Override + synchronized public boolean isRunning() { + return isProcessing; + } + + @Override + synchronized public Exception getError() { + return mException; + } + + synchronized private void setRunning(boolean running) { + isProcessing = running; + } + + synchronized private void setException(Exception exception) { + mException = exception; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java new file mode 100644 index 0000000..a2cf2a0 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java @@ -0,0 +1,155 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.SimpleFrameManager; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * @hide + */ +public class CachedFrameManager extends SimpleFrameManager { + + private SortedMap<Integer, Frame> mAvailableFrames; + private int mStorageCapacity = 24 * 1024 * 1024; // Cap default storage to 24MB + private int mStorageSize = 0; + private int mTimeStamp = 0; + + public CachedFrameManager() { + super(); + mAvailableFrames = new TreeMap<Integer, Frame>(); + } + + @Override + public Frame newFrame(FrameFormat format) { + Frame result = findAvailableFrame(format, Frame.NO_BINDING, 0); + if (result == null) { + result = super.newFrame(format); + } + result.setTimestamp(Frame.TIMESTAMP_NOT_SET); + return result; + } + + @Override + public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) { + Frame result = findAvailableFrame(format, bindingType, bindingId); + if (result == null) { + result = super.newBoundFrame(format, bindingType, bindingId); + } + result.setTimestamp(Frame.TIMESTAMP_NOT_SET); + return result; + } + + @Override + public Frame retainFrame(Frame frame) { + return super.retainFrame(frame); + } + + @Override + public Frame releaseFrame(Frame frame) { + if (frame.isReusable()) { + int refCount = frame.decRefCount(); + if (refCount == 0 && frame.hasNativeAllocation()) { + if (!storeFrame(frame)) { + frame.releaseNativeAllocation(); + } + return null; + } else if (refCount < 0) { + throw new RuntimeException("Frame reference count dropped below 0!"); + } + } else { + super.releaseFrame(frame); + } + return frame; + } + + public void clearCache() { + for (Frame frame : mAvailableFrames.values()) { + frame.releaseNativeAllocation(); + } + mAvailableFrames.clear(); + } + + @Override + public void tearDown() { + clearCache(); + } + + private boolean storeFrame(Frame frame) { + synchronized(mAvailableFrames) { + // Make sure this frame alone does not exceed capacity + int frameSize = frame.getFormat().getSize(); + if (frameSize > mStorageCapacity) { + return false; + } + + // Drop frames if adding this frame would exceed capacity + int newStorageSize = mStorageSize + frameSize; + while (newStorageSize > mStorageCapacity) { + dropOldestFrame(); + newStorageSize = mStorageSize + frameSize; + } + + // Store new frame + frame.onFrameStore(); + mStorageSize = newStorageSize; + mAvailableFrames.put(mTimeStamp, frame); + ++mTimeStamp; + return true; + } + } + + private void dropOldestFrame() { + int oldest = mAvailableFrames.firstKey(); + Frame frame = mAvailableFrames.get(oldest); + mStorageSize -= frame.getFormat().getSize(); + frame.releaseNativeAllocation(); + mAvailableFrames.remove(oldest); + } + + private Frame findAvailableFrame(FrameFormat format, int bindingType, long bindingId) { + // Look for a frame that is compatible with the requested format + synchronized(mAvailableFrames) { + for (Map.Entry<Integer, Frame> entry : mAvailableFrames.entrySet()) { + Frame frame = entry.getValue(); + // Check that format is compatible + if (frame.getFormat().isReplaceableBy(format)) { + // Check that binding is compatible (if frame is bound) + if ((bindingType == frame.getBindingType()) + && (bindingType == Frame.NO_BINDING || bindingId == frame.getBindingId())) { + // We found one! Take it out of the set of available frames and attach the + // requested format to it. + super.retainFrame(frame); + mAvailableFrames.remove(entry.getKey()); + frame.onFrameFetch(); + frame.reset(format); + mStorageSize -= format.getSize(); + return frame; + } + } + } + } + return null; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FieldPort.java b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java new file mode 100644 index 0000000..b0350cc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java @@ -0,0 +1,111 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class FieldPort extends InputPort { + + protected Field mField; + protected boolean mHasFrame; + protected boolean mValueWaiting = false; + protected Object mValue; + + public FieldPort(Filter filter, String name, Field field, boolean hasDefault) { + super(filter, name); + mField = field; + mHasFrame = hasDefault; + } + + @Override + public void clear() { + } + + @Override + public void pushFrame(Frame frame) { + setFieldFrame(frame, false); + } + + @Override + public void setFrame(Frame frame) { + setFieldFrame(frame, true); + } + + @Override + public Object getTarget() { + try { + return mField.get(mFilter); + } catch (IllegalAccessException e) { + return null; + } + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mValueWaiting) { + try { + mField.set(mFilter, mValue); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access to field '" + mField.getName() + "' was denied!"); + } + mValueWaiting = false; + if (context != null) { + mFilter.notifyFieldPortValueUpdated(mName, context); + } + } + } + + @Override + public synchronized Frame pullFrame() { + throw new RuntimeException("Cannot pull frame on " + this + "!"); + } + + @Override + public synchronized boolean hasFrame() { + return mHasFrame; + } + + @Override + public synchronized boolean acceptsFrame() { + return !mValueWaiting; + } + + @Override + public String toString() { + return "field " + super.toString(); + } + + protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) { + assertPortIsOpen(); + checkFrameType(frame, isAssignment); + + // Store the object value + Object value = frame.getObjectValue(); + if ((value == null && mValue != null) || !value.equals(mValue)) { + mValue = value; + mValueWaiting = true; + } + + // Since a frame was set, mark this port as having a frame to pull + mHasFrame = true; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java new file mode 100644 index 0000000..73b009d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java @@ -0,0 +1,709 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.io.TextGraphReader; +import android.filterfw.io.GraphIOException; +import android.filterfw.format.ObjectFormat; +import android.util.Log; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.Thread; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.LinkedList; +import java.util.Set; + +/** + * @hide + */ +public abstract class Filter { + + static final int STATUS_PREINIT = 0; + static final int STATUS_UNPREPARED = 1; + static final int STATUS_PREPARED = 2; + static final int STATUS_PROCESSING = 3; + static final int STATUS_SLEEPING = 4; + static final int STATUS_FINISHED = 5; + static final int STATUS_ERROR = 6; + static final int STATUS_RELEASED = 7; + + private String mName; + + private int mInputCount = -1; + private int mOutputCount = -1; + + private HashMap<String, InputPort> mInputPorts; + private HashMap<String, OutputPort> mOutputPorts; + + private HashSet<Frame> mFramesToRelease; + private HashMap<String, Frame> mFramesToSet; + + private int mStatus = 0; + private boolean mIsOpen = false; + private int mSleepDelay; + + private long mCurrentTimestamp; + + private boolean mLogVerbose; + private static final String TAG = "Filter"; + + public Filter(String name) { + mName = name; + mFramesToRelease = new HashSet<Frame>(); + mFramesToSet = new HashMap<String, Frame>(); + mStatus = STATUS_PREINIT; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Tests to see if a given filter is installed on the system. Requires + * full filter package name, including filterpack. + */ + public static final boolean isAvailable(String filterName) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + Class filterClass; + // First see if a class of that name exists + try { + filterClass = contextClassLoader.loadClass(filterName); + } catch (ClassNotFoundException e) { + return false; + } + // Then make sure it's a subclass of Filter. + try { + filterClass.asSubclass(Filter.class); + } catch (ClassCastException e) { + return false; + } + return true; + } + + public final void initWithValueMap(KeyValueMap valueMap) { + // Initialization + initFinalPorts(valueMap); + + // Setup remaining ports + initRemainingPorts(valueMap); + + // This indicates that final ports can no longer be set + mStatus = STATUS_UNPREPARED; + } + + public final void initWithAssignmentString(String assignments) { + try { + KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments); + initWithValueMap(valueMap); + } catch (GraphIOException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + public final void initWithAssignmentList(Object... keyValues) { + KeyValueMap valueMap = new KeyValueMap(); + valueMap.setKeyValues(keyValues); + initWithValueMap(valueMap); + } + + public final void init() throws ProtocolException { + KeyValueMap valueMap = new KeyValueMap(); + initWithValueMap(valueMap); + } + + public String getFilterClassName() { + return getClass().getSimpleName(); + } + + public final String getName() { + return mName; + } + + public boolean isOpen() { + return mIsOpen; + } + + public void setInputFrame(String inputName, Frame frame) { + FilterPort port = getInputPort(inputName); + if (!port.isOpen()) { + port.open(); + } + port.setFrame(frame); + } + + public final void setInputValue(String inputName, Object value) { + setInputFrame(inputName, wrapInputValue(inputName, value)); + } + + protected void prepare(FilterContext context) { + } + + protected void parametersUpdated(Set<String> updated) { + } + + protected void delayNextProcess(int millisecs) { + mSleepDelay = millisecs; + mStatus = STATUS_SLEEPING; + } + + public abstract void setupPorts(); + + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return null; + } + + public final FrameFormat getInputFormat(String portName) { + InputPort inputPort = getInputPort(portName); + return inputPort.getSourceFormat(); + } + + public void open(FilterContext context) { + } + + public abstract void process(FilterContext context); + + public final int getSleepDelay() { + return 250; + } + + public void close(FilterContext context) { + } + + public void tearDown(FilterContext context) { + } + + public final int getNumberOfConnectedInputs() { + int c = 0; + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.isConnected()) { + ++c; + } + } + return c; + } + + public final int getNumberOfConnectedOutputs() { + int c = 0; + for (OutputPort outputPort : mOutputPorts.values()) { + if (outputPort.isConnected()) { + ++c; + } + } + return c; + } + + public final int getNumberOfInputs() { + return mOutputPorts == null ? 0 : mInputPorts.size(); + } + + public final int getNumberOfOutputs() { + return mInputPorts == null ? 0 : mOutputPorts.size(); + } + + public final InputPort getInputPort(String portName) { + if (mInputPorts == null) { + throw new NullPointerException("Attempting to access input port '" + portName + + "' of " + this + " before Filter has been initialized!"); + } + InputPort result = mInputPorts.get(portName); + if (result == null) { + throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter " + + this + "!"); + } + return result; + } + + public final OutputPort getOutputPort(String portName) { + if (mInputPorts == null) { + throw new NullPointerException("Attempting to access output port '" + portName + + "' of " + this + " before Filter has been initialized!"); + } + OutputPort result = mOutputPorts.get(portName); + if (result == null) { + throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter " + + this + "!"); + } + return result; + } + + protected final void pushOutput(String name, Frame frame) { + if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) { + if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp); + frame.setTimestamp(mCurrentTimestamp); + } + getOutputPort(name).pushFrame(frame); + } + + protected final Frame pullInput(String name) { + Frame result = getInputPort(name).pullFrame(); + if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) { + mCurrentTimestamp = result.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp); + } + // As result is retained, we add it to the release pool here + mFramesToRelease.add(result); + + return result; + } + + public void fieldPortValueUpdated(String name, FilterContext context) { + } + + /** + * Transfers any frame from an input port to its destination. This is useful to force a + * transfer from a FieldPort or ProgramPort to its connected target (field or program variable). + */ + protected void transferInputPortFrame(String name, FilterContext context) { + getInputPort(name).transfer(context); + } + + /** + * Assigns all program variables to the ports they are connected to. Call this after + * constructing a Program instance with attached ProgramPorts. + */ + protected void initProgramInputs(Program program, FilterContext context) { + if (program != null) { + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.getTarget() == program) { + inputPort.transfer(context); + } + } + } + } + + /** + * Adds an input port to the filter. You should call this from within setupPorts, if your + * filter has input ports. No type-checking is performed on the input. If you would like to + * check against a type mask, use + * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead. + * + * @param name the name of the input port + */ + protected void addInputPort(String name) { + addMaskedInputPort(name, null); + } + + /** + * Adds an input port to the filter. You should call this from within setupPorts, if your + * filter has input ports. When type-checking is performed, the input format is + * checked against the provided format mask. An exception is thrown in case of a conflict. + * + * @param name the name of the input port + * @param formatMask a format mask, which filters the allowable input types + */ + protected void addMaskedInputPort(String name, FrameFormat formatMask) { + InputPort port = new StreamPort(this, name); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + mInputPorts.put(name, port); + port.setPortFormat(formatMask); + } + + /** + * Adds an output port to the filter with a fixed output format. You should call this from + * within setupPorts, if your filter has output ports. You cannot use this method, if your + * output format depends on the input format (e.g. in a pass-through filter). In this case, use + * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead. + * + * @param name the name of the output port + * @param format the fixed output format of this port + */ + protected void addOutputPort(String name, FrameFormat format) { + OutputPort port = new OutputPort(this, name); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + port.setPortFormat(format); + mOutputPorts.put(name, port); + } + + /** + * Adds an output port to the filter. You should call this from within setupPorts, if your + * filter has output ports. Using this method indicates that the output format for this + * particular port, depends on the format of an input port. You MUST also override + * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your + * filter will output for a given input. If the output format of your filter port does not + * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead. + * + * @param outputName the name of the output port + * @param inputName the name of the input port, that this output depends on + */ + protected void addOutputBasedOnInput(String outputName, String inputName) { + OutputPort port = new OutputPort(this, outputName); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + port.setBasePort(getInputPort(inputName)); + mOutputPorts.put(outputName, port); + } + + protected void addFieldPort(String name, + Field field, + boolean hasDefault, + boolean isFinal) { + // Make sure field is accessible + field.setAccessible(true); + + // Create port for this input + InputPort fieldPort = isFinal + ? new FinalPort(this, name, field, hasDefault) + : new FieldPort(this, name, field, hasDefault); + + // Create format for this input + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort); + MutableFrameFormat format = ObjectFormat.fromClass(field.getType(), + FrameFormat.TARGET_SIMPLE); + fieldPort.setPortFormat(format); + + // Add port + mInputPorts.put(name, fieldPort); + } + + protected void addProgramPort(String name, + String varName, + Field field, + Class varType, + boolean hasDefault) { + // Make sure field is accessible + field.setAccessible(true); + + // Create port for this input + InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault); + + // Create format for this input + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort); + MutableFrameFormat format = ObjectFormat.fromClass(varType, + FrameFormat.TARGET_SIMPLE); + programPort.setPortFormat(format); + + // Add port + mInputPorts.put(name, programPort); + } + + protected void closeOutputPort(String name) { + getOutputPort(name).close(); + } + + /** + * Specifies whether the filter should not be scheduled until a frame is available on that + * input port. Note, that setting this to false, does not block a new frame from coming in + * (though there is no necessity to pull that frame for processing). + * @param portName the name of the input port. + * @param waits true, if the filter should wait for a frame on this port. + */ + protected void setWaitsOnInputPort(String portName, boolean waits) { + getInputPort(portName).setBlocking(waits); + } + + /** + * Specifies whether the filter should not be scheduled until the output port is free, i.e. + * there is no frame waiting on that output. + * @param portName the name of the output port. + * @param waits true, if the filter should wait for the port to become free. + */ + protected void setWaitsOnOutputPort(String portName, boolean waits) { + getOutputPort(portName).setBlocking(waits); + } + + public String toString() { + return "'" + getName() + "' (" + getFilterClassName() + ")"; + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + final Collection<InputPort> getInputPorts() { + return mInputPorts.values(); + } + + final Collection<OutputPort> getOutputPorts() { + return mOutputPorts.values(); + } + + final synchronized int getStatus() { + return mStatus; + } + + final synchronized void unsetStatus(int flag) { + mStatus &= ~flag; + } + + final synchronized void performOpen(FilterContext context) { + if (!mIsOpen) { + if (mStatus == STATUS_UNPREPARED) { + if (mLogVerbose) Log.v(TAG, "Preparing " + this); + prepare(context); + mStatus = STATUS_PREPARED; + } + if (mStatus == STATUS_PREPARED) { + if (mLogVerbose) Log.v(TAG, "Opening " + this); + open(context); + mStatus = STATUS_PROCESSING; + } + if (mStatus != STATUS_PROCESSING) { + throw new RuntimeException("Filter " + this + " was brought into invalid state during " + + "opening (state: " + mStatus + ")!"); + } + mIsOpen = true; + } + } + + final synchronized void performProcess(FilterContext context) { + if (mStatus == STATUS_RELEASED) { + throw new RuntimeException("Filter " + this + " is already torn down!"); + } + transferInputFrames(context); + if (mStatus < STATUS_PROCESSING) { + performOpen(context); + } + if (mLogVerbose) Log.v(TAG, "Processing " + this); + mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN; + process(context); + releasePulledFrames(context); + if (filterMustClose()) { + performClose(context); + } + } + + final synchronized void performClose(FilterContext context) { + if (mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Closing " + this); + mIsOpen = false; + mStatus = STATUS_PREPARED; + close(context); + closePorts(); + } + } + + final synchronized void performTearDown(FilterContext context) { + performClose(context); + if (mStatus != STATUS_RELEASED) { + tearDown(context); + mStatus = STATUS_RELEASED; + } + } + + synchronized final boolean canProcess() { + if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ")."); + if (mStatus <= STATUS_PROCESSING) { + return inputConditionsMet() && outputConditionsMet(); + } else { + return false; + } + } + + final void openOutputs() { + if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!"); + for (OutputPort outputPort : mOutputPorts.values()) { + if (!outputPort.isOpen()) { + outputPort.open(); + } + } + } + + final void clearInputs() { + for (InputPort inputPort : mInputPorts.values()) { + inputPort.clear(); + } + } + + final void clearOutputs() { + for (OutputPort outputPort : mOutputPorts.values()) { + outputPort.clear(); + } + } + + final void notifyFieldPortValueUpdated(String name, FilterContext context) { + if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) { + fieldPortValueUpdated(name, context); + } + } + + final synchronized void pushInputFrame(String inputName, Frame frame) { + FilterPort port = getInputPort(inputName); + if (!port.isOpen()) { + port.open(); + } + port.pushFrame(frame); + } + + final synchronized void pushInputValue(String inputName, Object value) { + pushInputFrame(inputName, wrapInputValue(inputName, value)); + } + + // Filter internal methods ///////////////////////////////////////////////////////////////////// + private final void initFinalPorts(KeyValueMap values) { + mInputPorts = new HashMap<String, InputPort>(); + mOutputPorts = new HashMap<String, OutputPort>(); + addAndSetFinalPorts(values); + } + + private final void initRemainingPorts(KeyValueMap values) { + addAnnotatedPorts(); + setupPorts(); // TODO: rename to addFilterPorts() ? + setInitialInputValues(values); + } + + private final void addAndSetFinalPorts(KeyValueMap values) { + Class filterClass = getClass(); + Annotation annotation; + for (Field field : filterClass.getDeclaredFields()) { + if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) { + GenerateFinalPort generator = (GenerateFinalPort)annotation; + String name = generator.name().isEmpty() ? field.getName() : generator.name(); + boolean hasDefault = generator.hasDefault(); + addFieldPort(name, field, hasDefault, true); + if (values.containsKey(name)) { + setImmediateInputValue(name, values.get(name)); + values.remove(name); + } else if (!generator.hasDefault()) { + throw new RuntimeException("No value specified for final input port '" + + name + "' of filter " + this + "!"); + } + } + } + } + + private final void addAnnotatedPorts() { + Class filterClass = getClass(); + Annotation annotation; + for (Field field : filterClass.getDeclaredFields()) { + if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) { + GenerateFieldPort generator = (GenerateFieldPort)annotation; + addFieldGenerator(generator, field); + } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) { + GenerateProgramPort generator = (GenerateProgramPort)annotation; + addProgramGenerator(generator, field); + } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) { + GenerateProgramPorts generators = (GenerateProgramPorts)annotation; + for (GenerateProgramPort generator : generators.value()) { + addProgramGenerator(generator, field); + } + } + } + } + + private final void addFieldGenerator(GenerateFieldPort generator, Field field) { + String name = generator.name().isEmpty() ? field.getName() : generator.name(); + boolean hasDefault = generator.hasDefault(); + addFieldPort(name, field, hasDefault, false); + } + + private final void addProgramGenerator(GenerateProgramPort generator, Field field) { + String name = generator.name(); + String varName = generator.variableName().isEmpty() ? name + : generator.variableName(); + Class varType = generator.type(); + boolean hasDefault = generator.hasDefault(); + addProgramPort(name, varName, field, varType, hasDefault); + } + + private final void setInitialInputValues(KeyValueMap values) { + for (Entry<String, Object> entry : values.entrySet()) { + setInputValue(entry.getKey(), entry.getValue()); + } + } + + private final void setImmediateInputValue(String name, Object value) { + if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!"); + FilterPort port = getInputPort(name); + port.open(); + port.setFrame(SimpleFrame.wrapObject(value, null)); + } + + private final void transferInputFrames(FilterContext context) { + for (InputPort inputPort : mInputPorts.values()) { + inputPort.transfer(context); + } + } + + private final Frame wrapInputValue(String inputName, Object value) { + MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_SIMPLE); + if (value == null) { + // If the value is null, the format cannot guess the class, so we adjust it to the + // class of the input port here + FrameFormat portFormat = getInputPort(inputName).getPortFormat(); + Class portClass = (portFormat == null) ? null : portFormat.getObjectClass(); + inputFormat.setObjectClass(portClass); + } + + // Serialize if serializable, and type is not an immutable primitive. + boolean shouldSerialize = !(value instanceof Number) + && !(value instanceof Boolean) + && !(value instanceof String) + && value instanceof Serializable; + + // Create frame wrapper + Frame frame = shouldSerialize + ? new SerializedFrame(inputFormat, null) + : new SimpleFrame(inputFormat, null); + frame.setObjectValue(value); + return frame; + } + + private final void releasePulledFrames(FilterContext context) { + for (Frame frame : mFramesToRelease) { + context.getFrameManager().releaseFrame(frame); + } + mFramesToRelease.clear(); + } + + private final boolean inputConditionsMet() { + for (FilterPort port : mInputPorts.values()) { + if (!port.isReady()) { + if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!"); + return false; + } + } + return true; + } + + private final boolean outputConditionsMet() { + for (FilterPort port : mOutputPorts.values()) { + if (!port.isReady()) { + if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!"); + return false; + } + } + return true; + } + + private final void closePorts() { + if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!"); + for (InputPort inputPort : mInputPorts.values()) { + inputPort.close(); + } + for (OutputPort outputPort : mOutputPorts.values()) { + outputPort.close(); + } + } + + private final boolean filterMustClose() { + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.filterMustClose()) { + if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort); + return true; + } + } + for (OutputPort outputPort : mOutputPorts.values()) { + if (outputPort.filterMustClose()) { + if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort); + return true; + } + } + return false; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java new file mode 100644 index 0000000..3c79d1b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java @@ -0,0 +1,126 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLEnvironment; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public class FilterContext { + + private FrameManager mFrameManager; + private GLEnvironment mGLEnvironment; + private HashMap<String, Frame> mStoredFrames = new HashMap<String, Frame>(); + private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>(); + + public FrameManager getFrameManager() { + return mFrameManager; + } + + public void setFrameManager(FrameManager manager) { + if (manager == null) { + throw new NullPointerException("Attempting to set null FrameManager!"); + } else if (manager.getContext() != null) { + throw new IllegalArgumentException("Attempting to set FrameManager which is already " + + "bound to another FilterContext!"); + } else { + mFrameManager = manager; + mFrameManager.setContext(this); + } + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + public void initGLEnvironment(GLEnvironment environment) { + if (mGLEnvironment == null) { + mGLEnvironment = environment; + } else { + throw new RuntimeException("Attempting to re-initialize GL Environment for " + + "FilterContext!"); + } + } + + public interface OnFrameReceivedListener { + public void onFrameReceived(Filter filter, Frame frame, Object userData); + } + + public synchronized void storeFrame(String key, Frame frame) { + Frame storedFrame = fetchFrame(key); + if (storedFrame != null) { + storedFrame.release(); + } + frame.onFrameStore(); + mStoredFrames.put(key, frame.retain()); + } + + public synchronized Frame fetchFrame(String key) { + Frame frame = mStoredFrames.get(key); + if (frame != null) { + frame.onFrameFetch(); + } + return frame; + } + + public synchronized void removeFrame(String key) { + Frame frame = mStoredFrames.get(key); + if (frame != null) { + mStoredFrames.remove(key); + frame.release(); + } + } + + public synchronized void tearDown() { + // Release stored frames + for (Frame frame : mStoredFrames.values()) { + frame.release(); + } + mStoredFrames.clear(); + + // Release graphs + for (FilterGraph graph : mGraphs) { + graph.tearDown(this); + } + mGraphs.clear(); + + // Release frame manager + if (mFrameManager != null) { + mFrameManager.tearDown(); + mFrameManager = null; + } + + // Release GL context + if (mGLEnvironment != null) { + mGLEnvironment.tearDown(); + mGLEnvironment = null; + } + } + + final void addGraph(FilterGraph graph) { + mGraphs.add(graph); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java new file mode 100644 index 0000000..779df99 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java @@ -0,0 +1,145 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.util.Log; + +import dalvik.system.PathClassLoader; + +import java.lang.reflect.Constructor; +import java.lang.ClassLoader; +import java.lang.Thread; +import java.util.HashSet; + +/** + * @hide + */ +public class FilterFactory { + + private static FilterFactory mSharedFactory; + private HashSet<String> mPackages = new HashSet<String>(); + + private static ClassLoader mCurrentClassLoader; + private static HashSet<String> mLibraries; + private static Object mClassLoaderGuard; + + static { + mCurrentClassLoader = Thread.currentThread().getContextClassLoader(); + mLibraries = new HashSet<String>(); + mClassLoaderGuard = new Object(); + } + + private static final String TAG = "FilterFactory"; + private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + public static FilterFactory sharedFactory() { + if (mSharedFactory == null) { + mSharedFactory = new FilterFactory(); + } + return mSharedFactory; + } + + /** + * Adds a new Java library to the list to be scanned for filters. + * libraryPath must be an absolute path of the jar file. This needs to be + * static because only one classloader per process can open a shared native + * library, which a filter may well have. + */ + public static void addFilterLibrary(String libraryPath) { + if (mLogVerbose) Log.v(TAG, "Adding filter library " + libraryPath); + synchronized(mClassLoaderGuard) { + if (mLibraries.contains(libraryPath)) { + if (mLogVerbose) Log.v(TAG, "Library already added"); + return; + } + mLibraries.add(libraryPath); + // Chain another path loader to the current chain + mCurrentClassLoader = new PathClassLoader(libraryPath, mCurrentClassLoader); + } + } + + public void addPackage(String packageName) { + if (mLogVerbose) Log.v(TAG, "Adding package " + packageName); + /* TODO: This should use a getPackage call in the caller's context, but no such method exists. + Package pkg = Package.getPackage(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown filter package '" + packageName + "'!"); + } + */ + mPackages.add(packageName); + } + + public Filter createFilterByClassName(String className, String filterName) { + if (mLogVerbose) Log.v(TAG, "Looking up class " + className); + Class filterClass = null; + + // Look for the class in the imported packages + for (String packageName : mPackages) { + try { + if (mLogVerbose) Log.v(TAG, "Trying "+packageName + "." + className); + synchronized(mClassLoaderGuard) { + filterClass = mCurrentClassLoader.loadClass(packageName + "." + className); + } + } catch (ClassNotFoundException e) { + continue; + } + // Exit loop if class was found. + if (filterClass != null) { + break; + } + } + if (filterClass == null) { + throw new IllegalArgumentException("Unknown filter class '" + className + "'!"); + } + return createFilterByClass(filterClass, filterName); + } + + public Filter createFilterByClass(Class filterClass, String filterName) { + // Make sure this is a Filter subclass + try { + filterClass.asSubclass(Filter.class); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Attempting to allocate class '" + filterClass + + "' which is not a subclass of Filter!"); + } + + // Look for the correct constructor + Constructor filterConstructor = null; + try { + filterConstructor = filterClass.getConstructor(String.class); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("The filter class '" + filterClass + + "' does not have a constructor of the form <init>(String name)!"); + } + + // Construct the filter + Filter filter = null; + try { + filter = (Filter)filterConstructor.newInstance(filterName); + } catch (Throwable t) { + // Condition checked below + } + + if (filter == null) { + throw new IllegalArgumentException("Could not construct the filter '" + + filterName + "'!"); + } + return filter; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java new file mode 100644 index 0000000..ce81a18 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java @@ -0,0 +1,139 @@ +/* + * 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 android.filterfw.core; + +import java.util.Map.Entry; + +/** + * @hide + */ +public class FilterFunction { + + private Filter mFilter; + private FilterContext mFilterContext; + private boolean mFilterIsSetup = false; + private FrameHolderPort[] mResultHolders; + + private class FrameHolderPort extends StreamPort { + public FrameHolderPort() { + super(null, "holder"); + } + } + + public FilterFunction(FilterContext context, Filter filter) { + mFilterContext = context; + mFilter = filter; + } + + public Frame execute(KeyValueMap inputMap) { + int filterOutCount = mFilter.getNumberOfOutputs(); + + // Sanity checks + if (filterOutCount > 1) { + throw new RuntimeException("Calling execute on filter " + mFilter + " with multiple " + + "outputs! Use executeMulti() instead!"); + } + + // Setup filter + if (!mFilterIsSetup) { + connectFilterOutputs(); + mFilterIsSetup = true; + } + + // Make sure GL environment is active + boolean didActivateGLEnv = false; + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null && !glEnv.isActive()) { + glEnv.activate(); + didActivateGLEnv = true; + } + + // Setup the inputs + for (Entry<String, Object> entry : inputMap.entrySet()) { + if (entry.getValue() instanceof Frame) { + mFilter.pushInputFrame(entry.getKey(), (Frame)entry.getValue()); + } else { + mFilter.pushInputValue(entry.getKey(), entry.getValue()); + } + } + + // Process the filter + if (mFilter.getStatus() != Filter.STATUS_PROCESSING) { + mFilter.openOutputs(); + } + + mFilter.performProcess(mFilterContext); + + // Create result handle + Frame result = null; + if (filterOutCount == 1 && mResultHolders[0].hasFrame()) { + result = mResultHolders[0].pullFrame(); + } + + // Deactivate GL environment if activated + if (didActivateGLEnv) { + glEnv.deactivate(); + } + + return result; + } + + public Frame executeWithArgList(Object... inputs) { + return execute(KeyValueMap.fromKeyValues(inputs)); + } + + public void close() { + mFilter.performClose(mFilterContext); + } + + public FilterContext getContext() { + return mFilterContext; + } + + public Filter getFilter() { + return mFilter; + } + + public void setInputFrame(String input, Frame frame) { + mFilter.setInputFrame(input, frame); + } + + public void setInputValue(String input, Object value) { + mFilter.setInputValue(input, value); + } + + public void tearDown() { + mFilter.performTearDown(mFilterContext); + mFilter = null; + } + + @Override + public String toString() { + return mFilter.getName(); + } + + private void connectFilterOutputs() { + int i = 0; + mResultHolders = new FrameHolderPort[mFilter.getNumberOfOutputs()]; + for (OutputPort outputPort : mFilter.getOutputPorts()) { + mResultHolders[i] = new FrameHolderPort(); + outputPort.connectTo(mResultHolders[i]); + ++i; + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java new file mode 100644 index 0000000..12f7892 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java @@ -0,0 +1,363 @@ +/* + * 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 android.filterfw.core; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; + +import android.filterfw.core.FilterContext; +import android.filterfw.core.KeyValueMap; +import android.filterpacks.base.FrameBranch; +import android.filterpacks.base.NullFilter; + +import android.util.Log; + +/** + * @hide + */ +public class FilterGraph { + + private HashSet<Filter> mFilters = new HashSet<Filter>(); + private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>(); + private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new + HashMap<OutputPort, LinkedList<InputPort>>(); + + public static final int AUTOBRANCH_OFF = 0; + public static final int AUTOBRANCH_SYNCED = 1; + public static final int AUTOBRANCH_UNSYNCED = 2; + + public static final int TYPECHECK_OFF = 0; + public static final int TYPECHECK_DYNAMIC = 1; + public static final int TYPECHECK_STRICT = 2; + + private boolean mIsReady = false; + private int mAutoBranchMode = AUTOBRANCH_OFF; + private int mTypeCheckMode = TYPECHECK_STRICT; + private boolean mDiscardUnconnectedOutputs = false; + + private boolean mLogVerbose; + private String TAG = "FilterGraph"; + + public FilterGraph() { + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + public boolean addFilter(Filter filter) { + if (!containsFilter(filter)) { + mFilters.add(filter); + mNameMap.put(filter.getName(), filter); + return true; + } + return false; + } + + public boolean containsFilter(Filter filter) { + return mFilters.contains(filter); + } + + public Filter getFilter(String name) { + return mNameMap.get(name); + } + + public void connect(Filter source, + String outputName, + Filter target, + String inputName) { + if (source == null || target == null) { + throw new IllegalArgumentException("Passing null Filter in connect()!"); + } else if (!containsFilter(source) || !containsFilter(target)) { + throw new RuntimeException("Attempting to connect filter not in graph!"); + } + + OutputPort outPort = source.getOutputPort(outputName); + InputPort inPort = target.getInputPort(inputName); + if (outPort == null) { + throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " + + source + "!"); + } else if (inPort == null) { + throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " + + target + "!"); + } + + preconnect(outPort, inPort); + } + + public void connect(String sourceName, + String outputName, + String targetName, + String inputName) { + Filter source = getFilter(sourceName); + Filter target = getFilter(targetName); + if (source == null) { + throw new RuntimeException( + "Attempting to connect unknown source filter '" + sourceName + "'!"); + } else if (target == null) { + throw new RuntimeException( + "Attempting to connect unknown target filter '" + targetName + "'!"); + } + connect(source, outputName, target, inputName); + } + + public Set<Filter> getFilters() { + return mFilters; + } + + public void beginProcessing() { + if (mLogVerbose) Log.v(TAG, "Opening all filter connections..."); + for (Filter filter : mFilters) { + filter.openOutputs(); + } + mIsReady = true; + } + + public void flushFrames() { + for (Filter filter : mFilters) { + filter.clearOutputs(); + } + } + + public void closeFilters(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing all filters..."); + for (Filter filter : mFilters) { + filter.performClose(context); + } + mIsReady = false; + } + + public boolean isReady() { + return mIsReady; + } + + public void setAutoBranchMode(int autoBranchMode) { + mAutoBranchMode = autoBranchMode; + } + + public void setDiscardUnconnectedOutputs(boolean discard) { + mDiscardUnconnectedOutputs = discard; + } + + public void setTypeCheckMode(int typeCheckMode) { + mTypeCheckMode = typeCheckMode; + } + + public void tearDown(FilterContext context) { + if (!mFilters.isEmpty()) { + flushFrames(); + for (Filter filter : mFilters) { + filter.performTearDown(context); + } + mFilters.clear(); + mNameMap.clear(); + mIsReady = false; + } + } + + private boolean readyForProcessing(Filter filter, Set<Filter> processed) { + // Check if this has been already processed + if (processed.contains(filter)) { + return false; + } + + // Check if all dependencies have been processed + for (InputPort port : filter.getInputPorts()) { + Filter dependency = port.getSourceFilter(); + if (dependency != null && !processed.contains(dependency)) { + return false; + } + } + return true; + } + + private void runTypeCheck() { + Stack<Filter> filterStack = new Stack<Filter>(); + Set<Filter> processedFilters = new HashSet<Filter>(); + filterStack.addAll(getSourceFilters()); + + while (!filterStack.empty()) { + // Get current filter and mark as processed + Filter filter = filterStack.pop(); + processedFilters.add(filter); + + // Anchor output formats + updateOutputs(filter); + + // Perform type check + if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "..."); + runTypeCheckOn(filter); + + // Push connected filters onto stack + for (OutputPort port : filter.getOutputPorts()) { + Filter target = port.getTargetFilter(); + if (target != null && readyForProcessing(target, processedFilters)) { + filterStack.push(target); + } + } + } + + // Make sure all ports were setup + if (processedFilters.size() != getFilters().size()) { + throw new RuntimeException("Could not schedule all filters! Is your graph malformed?"); + } + } + + private void updateOutputs(Filter filter) { + for (OutputPort outputPort : filter.getOutputPorts()) { + InputPort inputPort = outputPort.getBasePort(); + if (inputPort != null) { + FrameFormat inputFormat = inputPort.getSourceFormat(); + FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(), + inputFormat); + if (outputFormat == null) { + throw new RuntimeException("Filter did not return an output format for " + + outputPort + "!"); + } + outputPort.setPortFormat(outputFormat); + } + } + } + + private void runTypeCheckOn(Filter filter) { + for (InputPort inputPort : filter.getInputPorts()) { + if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort); + FrameFormat sourceFormat = inputPort.getSourceFormat(); + FrameFormat targetFormat = inputPort.getPortFormat(); + if (sourceFormat != null && targetFormat != null) { + if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + "."); + + boolean compatible = true; + switch (mTypeCheckMode) { + case TYPECHECK_OFF: + inputPort.setChecksType(false); + break; + case TYPECHECK_DYNAMIC: + compatible = sourceFormat.mayBeCompatibleWith(targetFormat); + inputPort.setChecksType(true); + break; + case TYPECHECK_STRICT: + compatible = sourceFormat.isCompatibleWith(targetFormat); + inputPort.setChecksType(false); + break; + } + + if (!compatible) { + throw new RuntimeException("Type mismatch: Filter " + filter + " expects a " + + "format of type " + targetFormat + " but got a format of type " + + sourceFormat + "!"); + } + } + } + } + + private void checkConnections() { + // TODO + } + + private void discardUnconnectedOutputs() { + // Connect unconnected ports to Null filters + LinkedList<Filter> addedFilters = new LinkedList<Filter>(); + for (Filter filter : mFilters) { + int id = 0; + for (OutputPort port : filter.getOutputPorts()) { + if (!port.isConnected()) { + if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter."); + NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id); + nullFilter.init(); + addedFilters.add(nullFilter); + port.connectTo(nullFilter.getInputPort("frame")); + ++id; + } + } + } + // Add all added filters to this graph + for (Filter filter : addedFilters) { + addFilter(filter); + } + } + + private void removeFilter(Filter filter) { + mFilters.remove(filter); + mNameMap.remove(filter.getName()); + } + + private void preconnect(OutputPort outPort, InputPort inPort) { + LinkedList<InputPort> targets; + targets = mPreconnections.get(outPort); + if (targets == null) { + targets = new LinkedList<InputPort>(); + mPreconnections.put(outPort, targets); + } + targets.add(inPort); + } + + private void connectPorts() { + int branchId = 1; + for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) { + OutputPort outputPort = connection.getKey(); + LinkedList<InputPort> inputPorts = connection.getValue(); + if (inputPorts.size() == 1) { + outputPort.connectTo(inputPorts.get(0)); + } else if (mAutoBranchMode == AUTOBRANCH_OFF) { + throw new RuntimeException("Attempting to connect " + outputPort + " to multiple " + + "filter ports! Enable auto-branching to allow this."); + } else { + if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!"); + FrameBranch branch = null; + if (mAutoBranchMode == AUTOBRANCH_SYNCED) { + branch = new FrameBranch("branch" + branchId++); + } else { + throw new RuntimeException("TODO: Unsynced branches not implemented yet!"); + } + KeyValueMap branchParams = new KeyValueMap(); + branch.initWithAssignmentList("outputs", inputPorts.size()); + addFilter(branch); + outputPort.connectTo(branch.getInputPort("in")); + Iterator<InputPort> inputPortIter = inputPorts.iterator(); + for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) { + branchOutPort.connectTo(inputPortIter.next()); + } + } + } + mPreconnections.clear(); + } + + private HashSet<Filter> getSourceFilters() { + HashSet<Filter> sourceFilters = new HashSet<Filter>(); + for (Filter filter : getFilters()) { + if (filter.getNumberOfConnectedInputs() == 0) { + if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter); + sourceFilters.add(filter); + } + } + return sourceFilters; + } + + // Core internal methods ///////////////////////////////////////////////////////////////////////// + void setupFilters() { + if (mDiscardUnconnectedOutputs) { + discardUnconnectedOutputs(); + } + connectPorts(); + checkConnections(); + runTypeCheck(); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterPort.java b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java new file mode 100644 index 0000000..9734b89 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java @@ -0,0 +1,134 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.FrameFormat; +import android.util.Log; + +/** + * @hide + */ +public abstract class FilterPort { + + protected Filter mFilter; + protected String mName; + protected FrameFormat mPortFormat; + protected boolean mIsBlocking = true; + protected boolean mIsOpen = false; + protected boolean mChecksType = false; + private boolean mLogVerbose; + private static final String TAG = "FilterPort"; + + public FilterPort(Filter filter, String name) { + mName = name; + mFilter = filter; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + public boolean isAttached() { + return mFilter != null; + } + + public FrameFormat getPortFormat() { + return mPortFormat; + } + + public void setPortFormat(FrameFormat format) { + mPortFormat = format; + } + + public Filter getFilter() { + return mFilter; + } + + public String getName() { + return mName; + } + + public void setBlocking(boolean blocking) { + mIsBlocking = blocking; + } + + public void setChecksType(boolean checksType) { + mChecksType = checksType; + } + + public void open() { + if (!mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Opening " + this); + } + mIsOpen = true; + } + + public void close() { + if (mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Closing " + this); + } + mIsOpen = false; + } + + public boolean isOpen() { + return mIsOpen; + } + + public boolean isBlocking() { + return mIsBlocking; + } + + public abstract boolean filterMustClose(); + + public abstract boolean isReady(); + + public abstract void pushFrame(Frame frame); + + public abstract void setFrame(Frame frame); + + public abstract Frame pullFrame(); + + public abstract boolean hasFrame(); + + public abstract void clear(); + + public String toString() { + return "port '" + mName + "' of " + mFilter; + } + + protected void assertPortIsOpen() { + if (!isOpen()) { + throw new RuntimeException("Illegal operation on closed " + this + "!"); + } + } + + protected void checkFrameType(Frame frame, boolean forceCheck) { + if ((mChecksType || forceCheck) + && mPortFormat != null + && !frame.getFormat().isCompatibleWith(mPortFormat)) { + throw new RuntimeException("Frame passed to " + this + " is of incorrect type! " + + "Expected " + mPortFormat + " but got " + frame.getFormat()); + } + } + + protected void checkFrameManager(Frame frame, FilterContext context) { + if (frame.getFrameManager() != null + && frame.getFrameManager() != context.getFrameManager()) { + throw new RuntimeException("Frame " + frame + " is managed by foreign FrameManager! "); + } + } +} + diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java new file mode 100644 index 0000000..49306b2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java @@ -0,0 +1,157 @@ +/* + * 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 android.filterfw.core; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +/** + * @hide + */ +public class FilterSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + + private static int STATE_ALLOCATED = 0; + private static int STATE_CREATED = 1; + private static int STATE_INITIALIZED = 2; + + private int mState = STATE_ALLOCATED; + private SurfaceHolder.Callback mListener; + private GLEnvironment mGLEnv; + private int mFormat; + private int mWidth; + private int mHeight; + private int mSurfaceId = -1; + + public FilterSurfaceView(Context context) { + super(context); + getHolder().addCallback(this); + } + + public FilterSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + getHolder().addCallback(this); + } + + public synchronized void bindToListener(SurfaceHolder.Callback listener, GLEnvironment glEnv) { + // Make sure we are not bound already + if (listener == null) { + throw new NullPointerException("Attempting to bind null filter to SurfaceView!"); + } else if (mListener != null && mListener != listener) { + throw new RuntimeException( + "Attempting to bind filter " + listener + " to SurfaceView with another open " + + "filter " + mListener + " attached already!"); + } + + // Set listener + mListener = listener; + + // Set GLEnv + if (mGLEnv != null && mGLEnv != glEnv) { + mGLEnv.unregisterSurfaceId(mSurfaceId); + } + mGLEnv = glEnv; + + // Check if surface has been created already + if (mState >= STATE_CREATED) { + // Register with env (double registration will be ignored by GLEnv, so we can simply + // try to do it here). + registerSurface(); + + // Forward surface created to listener + mListener.surfaceCreated(getHolder()); + + // Forward surface changed to listener + if (mState == STATE_INITIALIZED) { + mListener.surfaceChanged(getHolder(), mFormat, mWidth, mHeight); + } + } + } + + public synchronized void unbind() { + mListener = null; + } + + public synchronized int getSurfaceId() { + return mSurfaceId; + } + + public synchronized GLEnvironment getGLEnv() { + return mGLEnv; + } + + @Override + public synchronized void surfaceCreated(SurfaceHolder holder) { + mState = STATE_CREATED; + + // Register with GLEnvironment if we have it already + if (mGLEnv != null) { + registerSurface(); + } + + // Forward callback to listener + if (mListener != null) { + mListener.surfaceCreated(holder); + } + } + + @Override + public synchronized void surfaceChanged(SurfaceHolder holder, + int format, + int width, + int height) { + // Remember these values + mFormat = format; + mWidth = width; + mHeight = height; + mState = STATE_INITIALIZED; + + // Forward to renderer + if (mListener != null) { + mListener.surfaceChanged(holder, format, width, height); + } + } + + @Override + public synchronized void surfaceDestroyed(SurfaceHolder holder) { + mState = STATE_ALLOCATED; + + // Forward to renderer + if (mListener != null) { + mListener.surfaceDestroyed(holder); + } + + // Get rid of internal objects associated with this surface + unregisterSurface(); + } + + private void registerSurface() { + mSurfaceId = mGLEnv.registerSurface(getHolder().getSurface()); + if (mSurfaceId < 0) { + throw new RuntimeException("Could not register Surface: " + getHolder().getSurface() + + " in FilterSurfaceView!"); + } + } + private void unregisterSurface() { + if (mGLEnv != null && mSurfaceId > 0) { + mGLEnv.unregisterSurfaceId(mSurfaceId); + } + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FinalPort.java b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java new file mode 100644 index 0000000..ad65169 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java @@ -0,0 +1,48 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class FinalPort extends FieldPort { + + public FinalPort(Filter filter, String name, Field field, boolean hasDefault) { + super(filter, name, field, hasDefault); + } + + @Override + protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) { + assertPortIsOpen(); + checkFrameType(frame, isAssignment); + if (mFilter.getStatus() != Filter.STATUS_PREINIT) { + throw new RuntimeException("Attempting to modify " + this + "!"); + } else { + super.setFieldFrame(frame, isAssignment); + super.transfer(null); + } + } + + @Override + public String toString() { + return "final " + super.toString(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java new file mode 100644 index 0000000..ef8c542 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java @@ -0,0 +1,236 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.graphics.Bitmap; +import android.util.Log; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public abstract class Frame { + + public final static int NO_BINDING = 0; + + public final static long TIMESTAMP_NOT_SET = -2; + public final static long TIMESTAMP_UNKNOWN = -1; + + private FrameFormat mFormat; + private FrameManager mFrameManager; + private boolean mReadOnly = false; + private boolean mReusable = false; + private int mRefCount = 1; + private int mBindingType = NO_BINDING; + private long mBindingId = 0; + private long mTimestamp = TIMESTAMP_NOT_SET; + + Frame(FrameFormat format, FrameManager frameManager) { + mFormat = format.mutableCopy(); + mFrameManager = frameManager; + } + + Frame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) { + mFormat = format.mutableCopy(); + mFrameManager = frameManager; + mBindingType = bindingType; + mBindingId = bindingId; + } + + public FrameFormat getFormat() { + return mFormat; + } + + public int getCapacity() { + return getFormat().getSize(); + } + + public boolean isReadOnly() { + return mReadOnly; + } + + public int getBindingType() { + return mBindingType; + } + + public long getBindingId() { + return mBindingId; + } + + public void setObjectValue(Object object) { + assertFrameMutable(); + + // Attempt to set the value using a specific setter (which may be more optimized), and + // fall back to the setGenericObjectValue(...) in case of no match. + if (object instanceof int[]) { + setInts((int[])object); + } else if (object instanceof float[]) { + setFloats((float[])object); + } else if (object instanceof ByteBuffer) { + setData((ByteBuffer)object); + } else if (object instanceof Bitmap) { + setBitmap((Bitmap)object); + } else { + setGenericObjectValue(object); + } + } + + public abstract Object getObjectValue(); + + public abstract void setInts(int[] ints); + + public abstract int[] getInts(); + + public abstract void setFloats(float[] floats); + + public abstract float[] getFloats(); + + public abstract void setData(ByteBuffer buffer, int offset, int length); + + public void setData(ByteBuffer buffer) { + setData(buffer, 0, buffer.limit()); + } + + public void setData(byte[] bytes, int offset, int length) { + setData(ByteBuffer.wrap(bytes, offset, length)); + } + + public abstract ByteBuffer getData(); + + public abstract void setBitmap(Bitmap bitmap); + + public abstract Bitmap getBitmap(); + + public void setTimestamp(long timestamp) { + mTimestamp = timestamp; + } + + public long getTimestamp() { + return mTimestamp; + } + + public void setDataFromFrame(Frame frame) { + setData(frame.getData()); + } + + protected boolean requestResize(int[] newDimensions) { + return false; + } + + public int getRefCount() { + return mRefCount; + } + + public Frame release() { + if (mFrameManager != null) { + return mFrameManager.releaseFrame(this); + } else { + return this; + } + } + + public Frame retain() { + if (mFrameManager != null) { + return mFrameManager.retainFrame(this); + } else { + return this; + } + } + + public FrameManager getFrameManager() { + return mFrameManager; + } + + protected void assertFrameMutable() { + if (isReadOnly()) { + throw new RuntimeException("Attempting to modify read-only frame!"); + } + } + + protected void setReusable(boolean reusable) { + mReusable = reusable; + } + + protected void setFormat(FrameFormat format) { + mFormat = format.mutableCopy(); + } + + protected void setGenericObjectValue(Object value) { + throw new RuntimeException( + "Cannot set object value of unsupported type: " + value.getClass()); + } + + protected static Bitmap convertBitmapToRGBA(Bitmap bitmap) { + if (bitmap.getConfig() == Bitmap.Config.ARGB_8888) { + return bitmap; + } else { + Bitmap result = bitmap.copy(Bitmap.Config.ARGB_8888, false); + if (result == null) { + throw new RuntimeException("Error converting bitmap to RGBA!"); + } else if (result.getRowBytes() != result.getWidth() * 4) { + throw new RuntimeException("Unsupported row byte count in bitmap!"); + } + return result; + } + } + + protected void reset(FrameFormat newFormat) { + mFormat = newFormat.mutableCopy(); + mReadOnly = false; + mRefCount = 1; + } + + /** + * Called just before a frame is stored, such as when storing to a cache or context. + */ + protected void onFrameStore() { + } + + /** + * Called when a frame is fetched from an internal store such as a cache. + */ + protected void onFrameFetch() { + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + protected abstract boolean hasNativeAllocation(); + + protected abstract void releaseNativeAllocation(); + + final int incRefCount() { + ++mRefCount; + return mRefCount; + } + + final int decRefCount() { + --mRefCount; + return mRefCount; + } + + final boolean isReusable() { + return mReusable; + } + + final void markReadOnly() { + mReadOnly = true; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java new file mode 100644 index 0000000..8f619be --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java @@ -0,0 +1,439 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; + +import java.util.Arrays; +import java.util.Map.Entry; + +/** + * @hide + */ +public class FrameFormat { + + public static final int TYPE_UNSPECIFIED = 0; + public static final int TYPE_BIT = 1; + public static final int TYPE_BYTE = 2; + public static final int TYPE_INT16 = 3; + public static final int TYPE_INT32 = 4; + public static final int TYPE_FLOAT = 5; + public static final int TYPE_DOUBLE = 6; + public static final int TYPE_POINTER = 7; + public static final int TYPE_OBJECT = 8; + + public static final int TARGET_UNSPECIFIED = 0; + public static final int TARGET_SIMPLE = 1; + public static final int TARGET_NATIVE = 2; + public static final int TARGET_GPU = 3; + public static final int TARGET_VERTEXBUFFER = 4; + public static final int TARGET_RS = 5; + + public static final int SIZE_UNSPECIFIED = 0; + + // TODO: When convenience formats are used, consider changing this to 0 and have the convenience + // intializers use a proper BPS. + public static final int BYTES_PER_SAMPLE_UNSPECIFIED = 1; + + protected static final int SIZE_UNKNOWN = -1; + + protected int mBaseType = TYPE_UNSPECIFIED; + protected int mBytesPerSample = 1; + protected int mSize = SIZE_UNKNOWN; + protected int mTarget = TARGET_UNSPECIFIED; + protected int[] mDimensions; + protected KeyValueMap mMetaData; + protected Class mObjectClass; + + protected FrameFormat() { + } + + public FrameFormat(int baseType, int target) { + mBaseType = baseType; + mTarget = target; + initDefaults(); + } + + public static FrameFormat unspecified() { + return new FrameFormat(TYPE_UNSPECIFIED, TARGET_UNSPECIFIED); + } + + public int getBaseType() { + return mBaseType; + } + + public boolean isBinaryDataType() { + return mBaseType >= TYPE_BIT && mBaseType <= TYPE_DOUBLE; + } + + public int getBytesPerSample() { + return mBytesPerSample; + } + + public int getValuesPerSample() { + return mBytesPerSample / bytesPerSampleOf(mBaseType); + } + + public int getTarget() { + return mTarget; + } + + public int[] getDimensions() { + return mDimensions; + } + + public int getDimension(int i) { + return mDimensions[i]; + } + + public int getDimensionCount() { + return mDimensions == null ? 0 : mDimensions.length; + } + + public boolean hasMetaKey(String key) { + return mMetaData != null ? mMetaData.containsKey(key) : false; + } + + public boolean hasMetaKey(String key, Class expectedClass) { + if (mMetaData != null && mMetaData.containsKey(key)) { + if (!expectedClass.isAssignableFrom(mMetaData.get(key).getClass())) { + throw new RuntimeException( + "FrameFormat meta-key '" + key + "' is of type " + + mMetaData.get(key).getClass() + " but expected to be of type " + + expectedClass + "!"); + } + return true; + } + return false; + } + + public Object getMetaValue(String key) { + return mMetaData != null ? mMetaData.get(key) : null; + } + + public int getNumberOfDimensions() { + return mDimensions != null ? mDimensions.length : 0; + } + + public int getLength() { + return (mDimensions != null && mDimensions.length >= 1) ? mDimensions[0] : -1; + } + + public int getWidth() { + return getLength(); + } + + public int getHeight() { + return (mDimensions != null && mDimensions.length >= 2) ? mDimensions[1] : -1; + } + + public int getDepth() { + return (mDimensions != null && mDimensions.length >= 3) ? mDimensions[2] : -1; + } + + public int getSize() { + if (mSize == SIZE_UNKNOWN) mSize = calcSize(mDimensions); + return mSize; + } + + public Class getObjectClass() { + return mObjectClass; + } + + public MutableFrameFormat mutableCopy() { + MutableFrameFormat result = new MutableFrameFormat(); + result.setBaseType(getBaseType()); + result.setTarget(getTarget()); + result.setBytesPerSample(getBytesPerSample()); + result.setDimensions(getDimensions()); + result.setObjectClass(getObjectClass()); + result.mMetaData = mMetaData == null ? null : (KeyValueMap)mMetaData.clone(); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (!(object instanceof FrameFormat)) { + return false; + } + + FrameFormat format = (FrameFormat)object; + return format.mBaseType == mBaseType && + format.mTarget == mTarget && + format.mBytesPerSample == mBytesPerSample && + Arrays.equals(format.mDimensions, mDimensions) && + format.mMetaData.equals(mMetaData); + } + + @Override + public int hashCode() { + return 4211 ^ mBaseType ^ mBytesPerSample ^ getSize(); + } + + public boolean isCompatibleWith(FrameFormat specification) { + // Check base type + if (specification.getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != specification.getBaseType()) { + return false; + } + + // Check target + if (specification.getTarget() != TARGET_UNSPECIFIED + && getTarget() != specification.getTarget()) { + return false; + } + + // Check bytes per sample + if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != specification.getBytesPerSample()) { + return false; + } + + // Check number of dimensions + if (specification.getDimensionCount() > 0 + && getDimensionCount() != specification.getDimensionCount()) { + return false; + } + + // Check dimensions + for (int i = 0; i < specification.getDimensionCount(); ++i) { + int specDim = specification.getDimension(i); + if (specDim != SIZE_UNSPECIFIED && getDimension(i) != specDim) { + return false; + } + } + + // Check class + if (specification.getObjectClass() != null) { + if (getObjectClass() == null + || !specification.getObjectClass().isAssignableFrom(getObjectClass())) { + return false; + } + } + + // Check meta-data + if (specification.mMetaData != null) { + for (String specKey : specification.mMetaData.keySet()) { + if (mMetaData == null + || !mMetaData.containsKey(specKey) + || !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) { + return false; + } + } + } + + // Passed all the tests + return true; + } + + public boolean mayBeCompatibleWith(FrameFormat specification) { + // Check base type + if (specification.getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != specification.getBaseType()) { + return false; + } + + // Check target + if (specification.getTarget() != TARGET_UNSPECIFIED + && getTarget() != TARGET_UNSPECIFIED + && getTarget() != specification.getTarget()) { + return false; + } + + // Check bytes per sample + if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != specification.getBytesPerSample()) { + return false; + } + + // Check number of dimensions + if (specification.getDimensionCount() > 0 + && getDimensionCount() > 0 + && getDimensionCount() != specification.getDimensionCount()) { + return false; + } + + // Check dimensions + for (int i = 0; i < specification.getDimensionCount(); ++i) { + int specDim = specification.getDimension(i); + if (specDim != SIZE_UNSPECIFIED + && getDimension(i) != SIZE_UNSPECIFIED + && getDimension(i) != specDim) { + return false; + } + } + + // Check class + if (specification.getObjectClass() != null && getObjectClass() != null) { + if (!specification.getObjectClass().isAssignableFrom(getObjectClass())) { + return false; + } + } + + // Check meta-data + if (specification.mMetaData != null && mMetaData != null) { + for (String specKey : specification.mMetaData.keySet()) { + if (mMetaData.containsKey(specKey) + && !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) { + return false; + } + } + } + + // Passed all the tests + return true; + } + + public static int bytesPerSampleOf(int baseType) { + // Defaults based on base-type + switch (baseType) { + case TYPE_BIT: + case TYPE_BYTE: + return 1; + case TYPE_INT16: + return 2; + case TYPE_INT32: + case TYPE_FLOAT: + case TYPE_POINTER: + return 4; + case TYPE_DOUBLE: + return 8; + default: + return 1; + } + } + + public static String dimensionsToString(int[] dimensions) { + StringBuffer buffer = new StringBuffer(); + if (dimensions != null) { + int n = dimensions.length; + for (int i = 0; i < n; ++i) { + if (dimensions[i] == SIZE_UNSPECIFIED) { + buffer.append("[]"); + } else { + buffer.append("[" + String.valueOf(dimensions[i]) + "]"); + } + } + } + return buffer.toString(); + } + + public static String baseTypeToString(int baseType) { + switch (baseType) { + case TYPE_UNSPECIFIED: return "unspecified"; + case TYPE_BIT: return "bit"; + case TYPE_BYTE: return "byte"; + case TYPE_INT16: return "int"; + case TYPE_INT32: return "int"; + case TYPE_FLOAT: return "float"; + case TYPE_DOUBLE: return "double"; + case TYPE_POINTER: return "pointer"; + case TYPE_OBJECT: return "object"; + default: return "unknown"; + } + } + + public static String targetToString(int target) { + switch (target) { + case TARGET_UNSPECIFIED: return "unspecified"; + case TARGET_SIMPLE: return "simple"; + case TARGET_NATIVE: return "native"; + case TARGET_GPU: return "gpu"; + case TARGET_VERTEXBUFFER: return "vbo"; + case TARGET_RS: return "renderscript"; + default: return "unknown"; + } + } + + public static String metaDataToString(KeyValueMap metaData) { + if (metaData == null) { + return ""; + } else { + StringBuffer buffer = new StringBuffer(); + buffer.append("{ "); + for (Entry<String, Object> entry : metaData.entrySet()) { + buffer.append(entry.getKey() + ": " + entry.getValue() + " "); + } + buffer.append("}"); + return buffer.toString(); + } + } + + public static int readTargetString(String targetString) { + if (targetString.equalsIgnoreCase("CPU") || targetString.equalsIgnoreCase("NATIVE")) { + return FrameFormat.TARGET_NATIVE; + } else if (targetString.equalsIgnoreCase("GPU")) { + return FrameFormat.TARGET_GPU; + } else if (targetString.equalsIgnoreCase("SIMPLE")) { + return FrameFormat.TARGET_SIMPLE; + } else if (targetString.equalsIgnoreCase("VERTEXBUFFER")) { + return FrameFormat.TARGET_VERTEXBUFFER; + } else if (targetString.equalsIgnoreCase("UNSPECIFIED")) { + return FrameFormat.TARGET_UNSPECIFIED; + } else { + throw new RuntimeException("Unknown target type '" + targetString + "'!"); + } + } + + // TODO: FromString + + public String toString() { + int valuesPerSample = getValuesPerSample(); + String sampleCountString = valuesPerSample == 1 ? "" : String.valueOf(valuesPerSample); + String targetString = mTarget == TARGET_UNSPECIFIED ? "" : (targetToString(mTarget) + " "); + String classString = mObjectClass == null + ? "" + : (" class(" + mObjectClass.getSimpleName() + ") "); + + return targetString + + baseTypeToString(mBaseType) + + sampleCountString + + dimensionsToString(mDimensions) + + classString + + metaDataToString(mMetaData); + } + + private void initDefaults() { + mBytesPerSample = bytesPerSampleOf(mBaseType); + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + int calcSize(int[] dimensions) { + if (dimensions != null && dimensions.length > 0) { + int size = getBytesPerSample(); + for (int dim : dimensions) { + size *= dim; + } + return size; + } + return 0; + } + + boolean isReplaceableBy(FrameFormat format) { + return mTarget == format.mTarget + && getSize() == format.getSize() + && Arrays.equals(format.mDimensions, mDimensions); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java new file mode 100644 index 0000000..8d6c483 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java @@ -0,0 +1,67 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public abstract class FrameManager { + + private FilterContext mContext; + + public abstract Frame newFrame(FrameFormat format); + + public abstract Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId); + + public Frame duplicateFrame(Frame frame) { + Frame result = newFrame(frame.getFormat()); + result.setDataFromFrame(frame); + return result; + } + + public Frame duplicateFrameToTarget(Frame frame, int newTarget) { + MutableFrameFormat newFormat = frame.getFormat().mutableCopy(); + newFormat.setTarget(newTarget); + Frame result = newFrame(newFormat); + result.setDataFromFrame(frame); + return result; + } + + public abstract Frame retainFrame(Frame frame); + + public abstract Frame releaseFrame(Frame frame); + + public FilterContext getContext() { + return mContext; + } + + public GLEnvironment getGLEnvironment() { + return mContext != null ? mContext.getGLEnvironment() : null; + } + + public void tearDown() { + } + + void setContext(FilterContext context) { + mContext = context; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java new file mode 100644 index 0000000..fcf5f5d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java @@ -0,0 +1,180 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.NativeAllocatorTag; +import android.graphics.SurfaceTexture; +import android.os.Looper; +import android.util.Log; +import android.view.Surface; +import android.media.MediaRecorder; + +/** + * @hide + */ +public class GLEnvironment { + + private int glEnvId; + + public GLEnvironment() { + nativeAllocate(); + } + + private GLEnvironment(NativeAllocatorTag tag) { + } + + public synchronized void tearDown() { + if (glEnvId != -1) { + nativeDeallocate(); + glEnvId = -1; + } + } + + @Override + protected void finalize() throws Throwable { + tearDown(); + } + + public void initWithNewContext() { + if (!nativeInitWithNewContext()) { + throw new RuntimeException("Could not initialize GLEnvironment with new context!"); + } + } + + public void initWithCurrentContext() { + if (!nativeInitWithCurrentContext()) { + throw new RuntimeException("Could not initialize GLEnvironment with current context!"); + } + } + + public boolean isActive() { + return nativeIsActive(); + } + + public boolean isContextActive() { + return nativeIsContextActive(); + } + + public static boolean isAnyContextActive() { + return nativeIsAnyContextActive(); + } + + public void activate() { + if (Looper.myLooper() != null && Looper.myLooper().equals(Looper.getMainLooper())) { + Log.e("FilterFramework", "Activating GL context in UI thread!"); + } + if (!nativeActivate()) { + throw new RuntimeException("Could not activate GLEnvironment!"); + } + } + + public void deactivate() { + if (!nativeDeactivate()) { + throw new RuntimeException("Could not deactivate GLEnvironment!"); + } + } + + public void swapBuffers() { + if (!nativeSwapBuffers()) { + throw new RuntimeException("Error swapping EGL buffers!"); + } + } + + public int registerSurface(Surface surface) { + int result = nativeAddSurface(surface); + if (result < 0) { + throw new RuntimeException("Error registering surface " + surface + "!"); + } + return result; + } + + public int registerSurfaceTexture(SurfaceTexture surfaceTexture, int width, int height) { + Surface surface = new Surface(surfaceTexture); + int result = nativeAddSurfaceWidthHeight(surface, width, height); + surface.release(); + if (result < 0) { + throw new RuntimeException("Error registering surfaceTexture " + surfaceTexture + "!"); + } + return result; + } + + public int registerSurfaceFromMediaRecorder(MediaRecorder mediaRecorder) { + int result = nativeAddSurfaceFromMediaRecorder(mediaRecorder); + if (result < 0) { + throw new RuntimeException("Error registering surface from " + + "MediaRecorder" + mediaRecorder + "!"); + } + return result; + } + + public void activateSurfaceWithId(int surfaceId) { + if (!nativeActivateSurfaceId(surfaceId)) { + throw new RuntimeException("Could not activate surface " + surfaceId + "!"); + } + } + + public void unregisterSurfaceId(int surfaceId) { + if (!nativeRemoveSurfaceId(surfaceId)) { + throw new RuntimeException("Could not unregister surface " + surfaceId + "!"); + } + } + + public void setSurfaceTimestamp(long timestamp) { + if (!nativeSetSurfaceTimestamp(timestamp)) { + throw new RuntimeException("Could not set timestamp for current surface!"); + } + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeInitWithNewContext(); + + private native boolean nativeInitWithCurrentContext(); + + private native boolean nativeIsActive(); + + private native boolean nativeIsContextActive(); + + private static native boolean nativeIsAnyContextActive(); + + private native boolean nativeActivate(); + + private native boolean nativeDeactivate(); + + private native boolean nativeSwapBuffers(); + + private native boolean nativeAllocate(); + + private native boolean nativeDeallocate(); + + private native int nativeAddSurface(Surface surface); + + private native int nativeAddSurfaceWidthHeight(Surface surface, int width, int height); + + private native int nativeAddSurfaceFromMediaRecorder(MediaRecorder mediaRecorder); + + private native boolean nativeDisconnectSurfaceMediaSource(MediaRecorder mediaRecorder); + + private native boolean nativeActivateSurfaceId(int surfaceId); + + private native boolean nativeRemoveSurfaceId(int surfaceId); + + private native boolean nativeSetSurfaceTimestamp(long timestamp); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java new file mode 100644 index 0000000..1558cc6 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java @@ -0,0 +1,417 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.StopWatchMap; +import android.graphics.Bitmap; +import android.opengl.GLES20; +import android.graphics.Rect; + +import java.nio.ByteBuffer; + +class GLFrameTimer { + + private static StopWatchMap mTimer = null; + + public static StopWatchMap get() { + if (mTimer == null) { + mTimer = new StopWatchMap(); + } + return mTimer; + } + +} + +/** + * @hide + */ +public class GLFrame extends Frame { + + // GL-related binding types + public final static int EXISTING_TEXTURE_BINDING = 100; + public final static int EXISTING_FBO_BINDING = 101; + public final static int NEW_TEXTURE_BINDING = 102; // TODO: REMOVE THIS + public final static int NEW_FBO_BINDING = 103; // TODO: REMOVE THIS + public final static int EXTERNAL_TEXTURE = 104; + + private int glFrameId = -1; + + /** + * Flag whether we own the texture or not. If we do not, we must be careful when caching or + * storing the frame, as the user may delete, and regenerate it. + */ + private boolean mOwnsTexture = true; + + /** + * Keep a reference to the GL environment, so that it does not get deallocated while there + * are still frames living in it. + */ + private GLEnvironment mGLEnvironment; + + GLFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + } + + GLFrame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) { + super(format, frameManager, bindingType, bindingId); + } + + void init(GLEnvironment glEnv) { + FrameFormat format = getFormat(); + mGLEnvironment = glEnv; + + // Check that we have a valid format + if (format.getBytesPerSample() != 4) { + throw new IllegalArgumentException("GL frames must have 4 bytes per sample!"); + } else if (format.getDimensionCount() != 2) { + throw new IllegalArgumentException("GL frames must be 2-dimensional!"); + } else if (getFormat().getSize() < 0) { + throw new IllegalArgumentException("Initializing GL frame with zero size!"); + } + + // Create correct frame + int bindingType = getBindingType(); + boolean reusable = true; + if (bindingType == Frame.NO_BINDING) { + initNew(false); + } else if (bindingType == EXTERNAL_TEXTURE) { + initNew(true); + reusable = false; + } else if (bindingType == EXISTING_TEXTURE_BINDING) { + initWithTexture((int)getBindingId()); + } else if (bindingType == EXISTING_FBO_BINDING) { + initWithFbo((int)getBindingId()); + } else if (bindingType == NEW_TEXTURE_BINDING) { + initWithTexture((int)getBindingId()); + } else if (bindingType == NEW_FBO_BINDING) { + initWithFbo((int)getBindingId()); + } else { + throw new RuntimeException("Attempting to create GL frame with unknown binding type " + + bindingType + "!"); + } + setReusable(reusable); + } + + private void initNew(boolean isExternal) { + if (isExternal) { + if (!nativeAllocateExternal(mGLEnvironment)) { + throw new RuntimeException("Could not allocate external GL frame!"); + } + } else { + if (!nativeAllocate(mGLEnvironment, getFormat().getWidth(), getFormat().getHeight())) { + throw new RuntimeException("Could not allocate GL frame!"); + } + } + } + + private void initWithTexture(int texId) { + int width = getFormat().getWidth(); + int height = getFormat().getHeight(); + if (!nativeAllocateWithTexture(mGLEnvironment, texId, width, height)) { + throw new RuntimeException("Could not allocate texture backed GL frame!"); + } + mOwnsTexture = false; + markReadOnly(); + } + + private void initWithFbo(int fboId) { + int width = getFormat().getWidth(); + int height = getFormat().getHeight(); + if (!nativeAllocateWithFbo(mGLEnvironment, fboId, width, height)) { + throw new RuntimeException("Could not allocate FBO backed GL frame!"); + } + } + + void flushGPU(String message) { + StopWatchMap timer = GLFrameTimer.get(); + if (timer.LOG_MFF_RUNNING_TIMES) { + timer.start("glFinish " + message); + GLES20.glFinish(); + timer.stop("glFinish " + message); + } + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return glFrameId != -1; + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + glFrameId = -1; + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + @Override + public Object getObjectValue() { + assertGLEnvValid(); + return ByteBuffer.wrap(getNativeData()); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for GL frame!"); + } + } + + @Override + public int[] getInts() { + assertGLEnvValid(); + flushGPU("getInts"); + return getNativeInts(); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for GL frame!"); + } + } + + @Override + public float[] getFloats() { + assertGLEnvValid(); + flushGPU("getFloats"); + return getNativeFloats(); + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + assertGLEnvValid(); + byte[] bytes = buffer.array(); + if (getFormat().getSize() != bytes.length) { + throw new RuntimeException("Data size in setData does not match GL frame size!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set GL frame data!"); + } + } + + @Override + public ByteBuffer getData() { + assertGLEnvValid(); + flushGPU("getData"); + return ByteBuffer.wrap(getNativeData()); + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + assertGLEnvValid(); + if (getFormat().getWidth() != bitmap.getWidth() || + getFormat().getHeight() != bitmap.getHeight()) { + throw new RuntimeException("Bitmap dimensions do not match GL frame dimensions!"); + } else { + Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap); + if (!setNativeBitmap(rgbaBitmap, rgbaBitmap.getByteCount())) { + throw new RuntimeException("Could not set GL frame bitmap data!"); + } + } + } + + @Override + public Bitmap getBitmap() { + assertGLEnvValid(); + flushGPU("getBitmap"); + Bitmap result = Bitmap.createBitmap(getFormat().getWidth(), + getFormat().getHeight(), + Bitmap.Config.ARGB_8888); + if (!getNativeBitmap(result)) { + throw new RuntimeException("Could not get bitmap data from GL frame!"); + } + return result; + } + + @Override + public void setDataFromFrame(Frame frame) { + assertGLEnvValid(); + + // Make sure frame fits + if (getFormat().getSize() < frame.getFormat().getSize()) { + throw new RuntimeException( + "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " + + "smaller GL frame of size " + getFormat().getSize() + "!"); + } + + // Invoke optimized implementations if possible + if (frame instanceof NativeFrame) { + nativeCopyFromNative((NativeFrame)frame); + } else if (frame instanceof GLFrame) { + nativeCopyFromGL((GLFrame)frame); + } else if (frame instanceof SimpleFrame) { + setObjectValue(frame.getObjectValue()); + } else { + super.setDataFromFrame(frame); + } + } + + public void setViewport(int x, int y, int width, int height) { + assertFrameMutable(); + setNativeViewport(x, y, width, height); + } + + public void setViewport(Rect rect) { + assertFrameMutable(); + setNativeViewport(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + + public void generateMipMap() { + assertFrameMutable(); + assertGLEnvValid(); + if (!generateNativeMipMap()) { + throw new RuntimeException("Could not generate mip-map for GL frame!"); + } + } + + public void setTextureParameter(int param, int value) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeTextureParam(param, value)) { + throw new RuntimeException("Could not set texture value " + param + " = " + value + " " + + "for GLFrame!"); + } + } + + public int getTextureId() { + return getNativeTextureId(); + } + + public int getFboId() { + return getNativeFboId(); + } + + public void focus() { + if (!nativeFocus()) { + throw new RuntimeException("Could not focus on GLFrame for drawing!"); + } + } + + @Override + public String toString() { + return "GLFrame id: " + glFrameId + " (" + getFormat() + ") with texture ID " + + getTextureId() + ", FBO ID " + getFboId(); + } + + @Override + protected void reset(FrameFormat newFormat) { + if (!nativeResetParams()) { + throw new RuntimeException("Could not reset GLFrame texture parameters!"); + } + super.reset(newFormat); + } + + @Override + protected void onFrameStore() { + if (!mOwnsTexture) { + // Detach texture from FBO in case user manipulates it. + nativeDetachTexFromFbo(); + } + } + + @Override + protected void onFrameFetch() { + if (!mOwnsTexture) { + // Reattach texture to FBO when using frame again. This may reallocate the texture + // in case it has become invalid. + nativeReattachTexToFbo(); + } + } + + private void assertGLEnvValid() { + if (!mGLEnvironment.isContextActive()) { + if (GLEnvironment.isAnyContextActive()) { + throw new RuntimeException("Attempting to access " + this + " with foreign GL " + + "context active!"); + } else { + throw new RuntimeException("Attempting to access " + this + " with no GL context " + + " active!"); + } + } + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(GLEnvironment env, int width, int height); + + private native boolean nativeAllocateWithTexture(GLEnvironment env, + int textureId, + int width, + int height); + + private native boolean nativeAllocateWithFbo(GLEnvironment env, + int fboId, + int width, + int height); + + private native boolean nativeAllocateExternal(GLEnvironment env); + + private native boolean nativeDeallocate(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native byte[] getNativeData(); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int[] getNativeInts(); + + private native float[] getNativeFloats(); + + private native boolean setNativeBitmap(Bitmap bitmap, int size); + + private native boolean getNativeBitmap(Bitmap bitmap); + + private native boolean setNativeViewport(int x, int y, int width, int height); + + private native int getNativeTextureId(); + + private native int getNativeFboId(); + + private native boolean generateNativeMipMap(); + + private native boolean setNativeTextureParam(int param, int value); + + private native boolean nativeResetParams(); + + private native boolean nativeCopyFromNative(NativeFrame frame); + + private native boolean nativeCopyFromGL(GLFrame frame); + + private native boolean nativeFocus(); + + private native boolean nativeReattachTexToFbo(); + + private native boolean nativeDetachTexFromFbo(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java new file mode 100644 index 0000000..3e37d4f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java @@ -0,0 +1,30 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateFieldPort { + String name() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java new file mode 100644 index 0000000..0dec8cc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java @@ -0,0 +1,30 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateFinalPort { + String name() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java new file mode 100644 index 0000000..fb40416 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java @@ -0,0 +1,32 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateProgramPort { + String name(); + Class type(); + String variableName() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java new file mode 100644 index 0000000..354126d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java @@ -0,0 +1,29 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateProgramPorts { + GenerateProgramPort[] value(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java new file mode 100644 index 0000000..b496c54 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java @@ -0,0 +1,100 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public abstract class GraphRunner { + + protected FilterContext mFilterContext = null; + + /** Interface for listeners waiting for the runner to complete. */ + public interface OnRunnerDoneListener { + /** Callback method to be called when the runner completes a + * {@link #run()} call. + * + * @param result will be RESULT_FINISHED if the graph finished running + * on its own, RESULT_STOPPED if the runner was stopped by a call + * to stop(), RESULT_BLOCKED if no filters could run due to lack + * of inputs or outputs or due to scheduling policies, and + * RESULT_SLEEPING if a filter node requested sleep. + */ + public void onRunnerDone(int result); + } + + public static final int RESULT_UNKNOWN = 0; + public static final int RESULT_RUNNING = 1; + public static final int RESULT_FINISHED = 2; + public static final int RESULT_SLEEPING = 3; + public static final int RESULT_BLOCKED = 4; + public static final int RESULT_STOPPED = 5; + public static final int RESULT_ERROR = 6; + + public GraphRunner(FilterContext context) { + mFilterContext = context; + } + + public abstract FilterGraph getGraph(); + + public FilterContext getContext() { + return mFilterContext; + } + + /** + * Helper function for subclasses to activate the GL environment before running. + * @return true, if the GL environment was activated. Returns false, if the GL environment + * was already active. + */ + protected boolean activateGlContext() { + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null && !glEnv.isActive()) { + glEnv.activate(); + return true; + } + return false; + } + + /** + * Helper function for subclasses to deactivate the GL environment after running. + */ + protected void deactivateGlContext() { + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null) { + glEnv.deactivate(); + } + } + + /** Starts running the graph. Will open the filters in the graph if they are not already open. */ + public abstract void run(); + + public abstract void setDoneCallback(OnRunnerDoneListener listener); + public abstract boolean isRunning(); + + /** Stops graph execution. As part of stopping, also closes the graph nodes. */ + public abstract void stop(); + + /** Closes the filters in a graph. Can only be called if the graph is not running. */ + public abstract void close(); + + /** + * Returns the last exception that happened during an asynchronous run. Returns null if + * there is nothing to report. + */ + public abstract Exception getError(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/InputPort.java b/media/mca/filterfw/java/android/filterfw/core/InputPort.java new file mode 100644 index 0000000..de5cccc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/InputPort.java @@ -0,0 +1,85 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public abstract class InputPort extends FilterPort { + + protected OutputPort mSourcePort; + + public InputPort(Filter filter, String name) { + super(filter, name); + } + + public void setSourcePort(OutputPort source) { + if (mSourcePort != null) { + throw new RuntimeException(this + " already connected to " + mSourcePort + "!"); + } + mSourcePort = source; + } + + public boolean isConnected() { + return mSourcePort != null; + } + + public void open() { + super.open(); + if (mSourcePort != null && !mSourcePort.isOpen()) { + mSourcePort.open(); + } + } + + public void close() { + if (mSourcePort != null && mSourcePort.isOpen()) { + mSourcePort.close(); + } + super.close(); + } + + public OutputPort getSourcePort() { + return mSourcePort; + } + + public Filter getSourceFilter() { + return mSourcePort == null ? null : mSourcePort.getFilter(); + } + + public FrameFormat getSourceFormat() { + return mSourcePort != null ? mSourcePort.getPortFormat() : getPortFormat(); + } + + public Object getTarget() { + return null; + } + + public boolean filterMustClose() { + return !isOpen() && isBlocking() && !hasFrame(); + } + + public boolean isReady() { + return hasFrame() || !isBlocking(); + } + + public boolean acceptsFrame() { + return !hasFrame(); + } + + public abstract void transfer(FilterContext context); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java new file mode 100644 index 0000000..8cf9a13 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java @@ -0,0 +1,82 @@ +/* + * 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 android.filterfw.core; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +public class KeyValueMap extends HashMap<String, Object> { + + public void setKeyValues(Object... keyValues) { + if (keyValues.length % 2 != 0) { + throw new RuntimeException("Key-Value arguments passed into setKeyValues must be " + + "an alternating list of keys and values!"); + } + for (int i = 0; i < keyValues.length; i += 2) { + if (!(keyValues[i] instanceof String)) { + throw new RuntimeException("Key-value argument " + i + " must be a key of type " + + "String, but found an object of type " + keyValues[i].getClass() + "!"); + } + String key = (String)keyValues[i]; + Object value = keyValues[i+1]; + put(key, value); + } + } + + public static KeyValueMap fromKeyValues(Object... keyValues) { + KeyValueMap result = new KeyValueMap(); + result.setKeyValues(keyValues); + return result; + } + + public String getString(String key) { + Object result = get(key); + return result != null ? (String)result : null; + } + + public int getInt(String key) { + Object result = get(key); + return result != null ? (Integer)result : null; + } + + public float getFloat(String key) { + Object result = get(key); + return result != null ? (Float)result : null; + } + + @Override + public String toString() { + StringWriter writer = new StringWriter(); + for (Map.Entry<String, Object> entry : entrySet()) { + String valueString; + Object value = entry.getValue(); + if (value instanceof String) { + valueString = "\"" + value + "\""; + } else { + valueString = value.toString(); + } + writer.write(entry.getKey() + " = " + valueString + ";\n"); + } + return writer.toString(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java new file mode 100644 index 0000000..8c78975 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java @@ -0,0 +1,96 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; + +import java.util.Arrays; + +/** + * @hide + */ +public class MutableFrameFormat extends FrameFormat { + + public MutableFrameFormat() { + super(); + } + + public MutableFrameFormat(int baseType, int target) { + super(baseType, target); + } + + public void setBaseType(int baseType) { + mBaseType = baseType; + mBytesPerSample = bytesPerSampleOf(baseType); + } + + public void setTarget(int target) { + mTarget = target; + } + + public void setBytesPerSample(int bytesPerSample) { + mBytesPerSample = bytesPerSample; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int[] dimensions) { + mDimensions = (dimensions == null) ? null : Arrays.copyOf(dimensions, dimensions.length); + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int size) { + int[] dimensions = new int[1]; + dimensions[0] = size; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int width, int height) { + int[] dimensions = new int[2]; + dimensions[0] = width; + dimensions[1] = height; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int width, int height, int depth) { + int[] dimensions = new int[3]; + dimensions[0] = width; + dimensions[1] = height; + dimensions[2] = depth; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensionCount(int count) { + mDimensions = new int[count]; + } + + public void setObjectClass(Class objectClass) { + mObjectClass = objectClass; + } + + public void setMetaValue(String key, Object value) { + if (mMetaData == null) { + mMetaData = new KeyValueMap(); + } + mMetaData.put(key, value); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java new file mode 100644 index 0000000..4d43d7c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java @@ -0,0 +1,28 @@ +/* + * 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 android.filterfw.core; + +/** + * This class is simply a place-holder type used to identify calls coming + * from the native layer. This way method signatures can be selected + * that are to be accessed from the native layer only. + * + * @hide + **/ +public class NativeAllocatorTag { +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java new file mode 100644 index 0000000..80da5ea --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java @@ -0,0 +1,129 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; + +/** + * @hide + */ +public class NativeBuffer { + + // These are set by the native layer + private long mDataPointer = 0; + private int mSize = 0; + + private Frame mAttachedFrame; + + private boolean mOwnsData = false; + private int mRefCount = 1; + + public NativeBuffer() { + } + + public NativeBuffer(int count) { + allocate(count * getElementSize()); + mOwnsData = true; + } + + public NativeBuffer mutableCopy() { + NativeBuffer result = null; + try { + Class myClass = getClass(); + result = (NativeBuffer)myClass.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Unable to allocate a copy of " + getClass() + "! Make " + + "sure the class has a default constructor!"); + } + if (mSize > 0 && !nativeCopyTo(result)) { + throw new RuntimeException("Failed to copy NativeBuffer to mutable instance!"); + } + return result; + } + + public int size() { + return mSize; + } + + public int count() { + return (mDataPointer != 0) ? mSize / getElementSize() : 0; + } + + public int getElementSize() { + return 1; + } + + public NativeBuffer retain() { + if (mAttachedFrame != null) { + mAttachedFrame.retain(); + } else if (mOwnsData) { + ++mRefCount; + } + return this; + } + + public NativeBuffer release() { + // Decrement refcount + boolean doDealloc = false; + if (mAttachedFrame != null) { + doDealloc = (mAttachedFrame.release() == null); + } else if (mOwnsData) { + --mRefCount; + doDealloc = (mRefCount == 0); + } + + // Deallocate if necessary + if (doDealloc) { + deallocate(mOwnsData); + return null; + } else { + return this; + } + } + + public boolean isReadOnly() { + return (mAttachedFrame != null) ? mAttachedFrame.isReadOnly() : false; + } + + static { + System.loadLibrary("filterfw"); + } + + void attachToFrame(Frame frame) { + // We do not auto-retain. We expect the user to call retain() if they want to hold on to + // the frame. + mAttachedFrame = frame; + } + + protected void assertReadable() { + if (mDataPointer == 0 || mSize == 0 + || (mAttachedFrame != null && !mAttachedFrame.hasNativeAllocation())) { + throw new NullPointerException("Attempting to read from null data frame!"); + } + } + + protected void assertWritable() { + if (isReadOnly()) { + throw new RuntimeException("Attempting to modify read-only native (structured) data!"); + } + } + + private native boolean allocate(int size); + private native boolean deallocate(boolean ownsData); + private native boolean nativeCopyTo(NativeBuffer buffer); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java new file mode 100644 index 0000000..bfd09ba --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java @@ -0,0 +1,265 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeBuffer; +import android.graphics.Bitmap; + +import android.util.Log; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class NativeFrame extends Frame { + + private int nativeFrameId = -1; + + NativeFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + int capacity = format.getSize(); + nativeAllocate(capacity); + setReusable(capacity != 0); + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + nativeFrameId = -1; + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return nativeFrameId != -1; + } + + @Override + public int getCapacity() { + return getNativeCapacity(); + } + + /** + * Returns the native frame's Object value. + * + * If the frame's base-type is not TYPE_OBJECT, this returns a data buffer containing the native + * data (this is equivalent to calling getData(). + * If the frame is based on an object type, this type is expected to be a subclass of + * NativeBuffer. The NativeBuffer returned is only valid for as long as the frame is alive. If + * you need to hold on to the returned value, you must retain it. + */ + @Override + public Object getObjectValue() { + // If this is not a structured frame, return our data + if (getFormat().getBaseType() != FrameFormat.TYPE_OBJECT) { + return getData(); + } + + // Get the structure class + Class structClass = getFormat().getObjectClass(); + if (structClass == null) { + throw new RuntimeException("Attempting to get object data from frame that does " + + "not specify a structure object class!"); + } + + // Make sure it is a NativeBuffer subclass + if (!NativeBuffer.class.isAssignableFrom(structClass)) { + throw new RuntimeException("NativeFrame object class must be a subclass of " + + "NativeBuffer!"); + } + + // Instantiate a new empty instance of this class + NativeBuffer structData = null; + try { + structData = (NativeBuffer)structClass.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Could not instantiate new structure instance of type '" + + structClass + "'!"); + } + + // Wrap it around our data + if (!getNativeBuffer(structData)) { + throw new RuntimeException("Could not get the native structured data for frame!"); + } + + // Attach this frame to it + structData.attachToFrame(this); + + return structData; + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + if (ints.length * nativeIntSize() > getFormat().getSize()) { + throw new RuntimeException( + "NativeFrame cannot hold " + ints.length + " integers. (Can only hold " + + (getFormat().getSize() / nativeIntSize()) + " integers)."); + } else if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for native frame!"); + } + } + + @Override + public int[] getInts() { + return getNativeInts(getFormat().getSize()); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + if (floats.length * nativeFloatSize() > getFormat().getSize()) { + throw new RuntimeException( + "NativeFrame cannot hold " + floats.length + " floats. (Can only hold " + + (getFormat().getSize() / nativeFloatSize()) + " floats)."); + } else if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for native frame!"); + } + } + + @Override + public float[] getFloats() { + return getNativeFloats(getFormat().getSize()); + } + + // TODO: This function may be a bit confusing: Is the offset the target or source offset? Maybe + // we should allow specifying both? (May be difficult for other frame types). + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + byte[] bytes = buffer.array(); + if ((length + offset) > buffer.limit()) { + throw new RuntimeException("Offset and length exceed buffer size in native setData: " + + (length + offset) + " bytes given, but only " + buffer.limit() + + " bytes available!"); + } else if (getFormat().getSize() != length) { + throw new RuntimeException("Data size in setData does not match native frame size: " + + "Frame size is " + getFormat().getSize() + " bytes, but " + + length + " bytes given!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set native frame data!"); + } + } + + @Override + public ByteBuffer getData() { + byte[] data = getNativeData(getFormat().getSize()); + return data == null ? null : ByteBuffer.wrap(data); + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + if (getFormat().getNumberOfDimensions() != 2) { + throw new RuntimeException("Attempting to set Bitmap for non 2-dimensional native frame!"); + } else if (getFormat().getWidth() != bitmap.getWidth() || + getFormat().getHeight() != bitmap.getHeight()) { + throw new RuntimeException("Bitmap dimensions do not match native frame dimensions!"); + } else { + Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap); + int byteCount = rgbaBitmap.getByteCount(); + int bps = getFormat().getBytesPerSample(); + if (!setNativeBitmap(rgbaBitmap, byteCount, bps)) { + throw new RuntimeException("Could not set native frame bitmap data!"); + } + } + } + + @Override + public Bitmap getBitmap() { + if (getFormat().getNumberOfDimensions() != 2) { + throw new RuntimeException("Attempting to get Bitmap for non 2-dimensional native frame!"); + } + Bitmap result = Bitmap.createBitmap(getFormat().getWidth(), + getFormat().getHeight(), + Bitmap.Config.ARGB_8888); + int byteCount = result.getByteCount(); + int bps = getFormat().getBytesPerSample(); + if (!getNativeBitmap(result, byteCount, bps)) { + throw new RuntimeException("Could not get bitmap data from native frame!"); + } + return result; + } + + @Override + public void setDataFromFrame(Frame frame) { + // Make sure frame fits + if (getFormat().getSize() < frame.getFormat().getSize()) { + throw new RuntimeException( + "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " + + "smaller native frame of size " + getFormat().getSize() + "!"); + } + + // Invoke optimized implementations if possible + if (frame instanceof NativeFrame) { + nativeCopyFromNative((NativeFrame)frame); + } else if (frame instanceof GLFrame) { + nativeCopyFromGL((GLFrame)frame); + } else if (frame instanceof SimpleFrame) { + setObjectValue(frame.getObjectValue()); + } else { + super.setDataFromFrame(frame); + } + } + + @Override + public String toString() { + return "NativeFrame id: " + nativeFrameId + " (" + getFormat() + ") of size " + + getCapacity(); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(int capacity); + + private native boolean nativeDeallocate(); + + private native int getNativeCapacity(); + + private static native int nativeIntSize(); + + private static native int nativeFloatSize(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native byte[] getNativeData(int byteCount); + + private native boolean getNativeBuffer(NativeBuffer buffer); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int[] getNativeInts(int byteCount); + + private native float[] getNativeFloats(int byteCount); + + private native boolean setNativeBitmap(Bitmap bitmap, int size, int bytesPerSample); + + private native boolean getNativeBitmap(Bitmap bitmap, int size, int bytesPerSample); + + private native boolean nativeCopyFromNative(NativeFrame frame); + + private native boolean nativeCopyFromGL(GLFrame frame); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java new file mode 100644 index 0000000..791ab3c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java @@ -0,0 +1,176 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.Program; + +/** + * @hide + */ +public class NativeProgram extends Program { + + private int nativeProgramId; + private boolean mHasInitFunction = false; + private boolean mHasTeardownFunction = false; + private boolean mHasSetValueFunction = false; + private boolean mHasGetValueFunction = false; + private boolean mHasResetFunction = false; + private boolean mTornDown = false; + + public NativeProgram(String nativeLibName, String nativeFunctionPrefix) { + // Allocate the native instance + allocate(); + + // Open the native library + String fullLibName = "lib" + nativeLibName + ".so"; + if (!openNativeLibrary(fullLibName)) { + throw new RuntimeException("Could not find native library named '" + fullLibName + "' " + + "required for native program!"); + } + + // Bind the native functions + String processFuncName = nativeFunctionPrefix + "_process"; + if (!bindProcessFunction(processFuncName)) { + throw new RuntimeException("Could not find native program function name " + + processFuncName + " in library " + fullLibName + "! " + + "This function is required!"); + } + + String initFuncName = nativeFunctionPrefix + "_init"; + mHasInitFunction = bindInitFunction(initFuncName); + + String teardownFuncName = nativeFunctionPrefix + "_teardown"; + mHasTeardownFunction = bindTeardownFunction(teardownFuncName); + + String setValueFuncName = nativeFunctionPrefix + "_setvalue"; + mHasSetValueFunction = bindSetValueFunction(setValueFuncName); + + String getValueFuncName = nativeFunctionPrefix + "_getvalue"; + mHasGetValueFunction = bindGetValueFunction(getValueFuncName); + + String resetFuncName = nativeFunctionPrefix + "_reset"; + mHasResetFunction = bindResetFunction(resetFuncName); + + // Initialize the native code + if (mHasInitFunction && !callNativeInit()) { + throw new RuntimeException("Could not initialize NativeProgram!"); + } + } + + public void tearDown() { + if (mTornDown) return; + if (mHasTeardownFunction && !callNativeTeardown()) { + throw new RuntimeException("Could not tear down NativeProgram!"); + } + deallocate(); + mTornDown = true; + } + + @Override + public void reset() { + if (mHasResetFunction && !callNativeReset()) { + throw new RuntimeException("Could not reset NativeProgram!"); + } + } + + @Override + protected void finalize() throws Throwable { + tearDown(); + } + + @Override + public void process(Frame[] inputs, Frame output) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + NativeFrame[] nativeInputs = new NativeFrame[inputs.length]; + for (int i = 0; i < inputs.length; ++i) { + if (inputs[i] == null || inputs[i] instanceof NativeFrame) { + nativeInputs[i] = (NativeFrame)inputs[i]; + } else { + throw new RuntimeException("NativeProgram got non-native frame as input "+ i +"!"); + } + } + + // Get the native output frame + NativeFrame nativeOutput = null; + if (output == null || output instanceof NativeFrame) { + nativeOutput = (NativeFrame)output; + } else { + throw new RuntimeException("NativeProgram got non-native output frame!"); + } + + // Process! + if (!callNativeProcess(nativeInputs, nativeOutput)) { + throw new RuntimeException("Calling native process() caused error!"); + } + } + + @Override + public void setHostValue(String variableName, Object value) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + if (!mHasSetValueFunction) { + throw new RuntimeException("Attempting to set native variable, but native code does not " + + "define native setvalue function!"); + } + if (!callNativeSetValue(variableName, value.toString())) { + throw new RuntimeException("Error setting native value for variable '" + variableName + "'!"); + } + } + + @Override + public Object getHostValue(String variableName) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + if (!mHasGetValueFunction) { + throw new RuntimeException("Attempting to get native variable, but native code does not " + + "define native getvalue function!"); + } + return callNativeGetValue(variableName); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean allocate(); + + private native boolean deallocate(); + + private native boolean nativeInit(); + + private native boolean openNativeLibrary(String libName); + + private native boolean bindInitFunction(String funcName); + private native boolean bindSetValueFunction(String funcName); + private native boolean bindGetValueFunction(String funcName); + private native boolean bindProcessFunction(String funcName); + private native boolean bindResetFunction(String funcName); + private native boolean bindTeardownFunction(String funcName); + + private native boolean callNativeInit(); + private native boolean callNativeSetValue(String key, String value); + private native String callNativeGetValue(String key); + private native boolean callNativeProcess(NativeFrame[] inputs, NativeFrame output); + private native boolean callNativeReset(); + private native boolean callNativeTeardown(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java new file mode 100644 index 0000000..dbc8d16 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java @@ -0,0 +1,78 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; +import android.filterfw.core.RoundRobinScheduler; +import android.util.Log; + +import java.util.HashMap; + +/** + * This OneShotScheduler only schedules source filters at most once. All other + * filters will be scheduled, and possibly repeatedly, until there is no filter + * that can be scheduled. + * + * @hide + */ +public class OneShotScheduler extends RoundRobinScheduler { + private HashMap <String, Integer> scheduled; + + private final boolean mLogVerbose; + private static final String TAG = "OneShotScheduler"; + + public OneShotScheduler(FilterGraph graph) { + super(graph); + scheduled = new HashMap<String, Integer>(); + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void reset() { + super.reset(); + scheduled.clear(); + } + + @Override + public Filter scheduleNextNode() { + Filter first = null; + // return the first filter that is not scheduled before. + while (true) { + Filter filter = super.scheduleNextNode(); + if (filter == null) { + if (mLogVerbose) Log.v(TAG, "No filters available to run."); + return null; + } + if (!scheduled.containsKey(filter.getName())) { + if (filter.getNumberOfConnectedInputs() == 0) + scheduled.put(filter.getName(),1); + if (mLogVerbose) Log.v(TAG, "Scheduling filter \"" + filter.getName() + "\" of type " + filter.getFilterClassName()); + return filter; + } + // if loop back, nothing available + if (first == filter) { + break; + } + // save the first scheduled one + if (first == null) first = filter; + } + if (mLogVerbose) Log.v(TAG, "One pass through graph completed."); + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/OutputPort.java b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java new file mode 100644 index 0000000..872dbdd --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java @@ -0,0 +1,122 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class OutputPort extends FilterPort { + + protected InputPort mTargetPort; + protected InputPort mBasePort; + + public OutputPort(Filter filter, String name) { + super(filter, name); + } + + public void connectTo(InputPort target) { + if (mTargetPort != null) { + throw new RuntimeException(this + " already connected to " + mTargetPort + "!"); + } + mTargetPort = target; + mTargetPort.setSourcePort(this); + } + + public boolean isConnected() { + return mTargetPort != null; + } + + public void open() { + super.open(); + if (mTargetPort != null && !mTargetPort.isOpen()) { + mTargetPort.open(); + } + } + + public void close() { + super.close(); + if (mTargetPort != null && mTargetPort.isOpen()) { + mTargetPort.close(); + } + } + + public InputPort getTargetPort() { + return mTargetPort; + } + + public Filter getTargetFilter() { + return mTargetPort == null ? null : mTargetPort.getFilter(); + } + + public void setBasePort(InputPort basePort) { + mBasePort = basePort; + } + + public InputPort getBasePort() { + return mBasePort; + } + + public boolean filterMustClose() { + return !isOpen() && isBlocking(); + } + + public boolean isReady() { + return (isOpen() && mTargetPort.acceptsFrame()) || !isBlocking(); + } + + @Override + public void clear() { + if (mTargetPort != null) { + mTargetPort.clear(); + } + } + + @Override + public void pushFrame(Frame frame) { + if (mTargetPort == null) { + throw new RuntimeException( + "Attempting to push frame on unconnected port: " + this + "!"); + } + mTargetPort.pushFrame(frame); + } + + @Override + public void setFrame(Frame frame) { + assertPortIsOpen(); + if (mTargetPort == null) { + throw new RuntimeException( + "Attempting to set frame on unconnected port: " + this + "!"); + } + mTargetPort.setFrame(frame); + } + + @Override + public Frame pullFrame() { + throw new RuntimeException("Cannot pull frame on " + this + "!"); + } + + @Override + public boolean hasFrame() { + return mTargetPort == null ? false : mTargetPort.hasFrame(); + } + + @Override + public String toString() { + return "output " + super.toString(); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java new file mode 100644 index 0000000..1930648 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Program.java @@ -0,0 +1,41 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; + +/** + * @hide + */ +public abstract class Program { + + public abstract void process(Frame[] inputs, Frame output); + + public void process(Frame input, Frame output) { + Frame[] inputs = new Frame[1]; + inputs[0] = input; + process(inputs, output); + } + + public abstract void setHostValue(String variableName, Object value); + + public abstract Object getHostValue(String variableName); + + public void reset() { + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java new file mode 100644 index 0000000..3cab26d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java @@ -0,0 +1,62 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class ProgramPort extends FieldPort { + + protected String mVarName; + + public ProgramPort(Filter filter, + String name, + String varName, + Field field, + boolean hasDefault) { + super(filter, name, field, hasDefault); + mVarName = varName; + } + + @Override + public String toString() { + return "Program " + super.toString(); + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mValueWaiting) { + try { + Object fieldValue = mField.get(mFilter); + if (fieldValue != null) { + Program program = (Program)fieldValue; + program.setHostValue(mVarName, mValue); + mValueWaiting = false; + } + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access to program field '" + mField.getName() + "' was denied!"); + } catch (ClassCastException e) { + throw new RuntimeException("Non Program field '" + mField.getName() + + "' annotated with ProgramParameter!"); + } + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java new file mode 100644 index 0000000..5592d37 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java @@ -0,0 +1,57 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class ProgramVariable { + + private Program mProgram; + private String mVarName; + + public ProgramVariable(Program program, String varName) { + mProgram = program; + mVarName = varName; + } + + public Program getProgram() { + return mProgram; + } + + public String getVariableName() { + return mVarName; + } + + public void setValue(Object value) { + if (mProgram == null) { + throw new RuntimeException("Attempting to set program variable '" + mVarName + + "' but the program is null!"); + } + mProgram.setHostValue(mVarName, value); + } + + public Object getValue() { + if (mProgram == null) { + throw new RuntimeException("Attempting to get program variable '" + mVarName + + "' but the program is null!"); + } + return mProgram.getHostValue(mVarName); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java new file mode 100644 index 0000000..2c7a29a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java @@ -0,0 +1,33 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class ProtocolException extends RuntimeException { + + public ProtocolException() { + super(); + } + + public ProtocolException(String message) { + super(message); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java new file mode 100644 index 0000000..087f5db --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java @@ -0,0 +1,54 @@ +/* + * 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 android.filterfw.core; + +import java.util.Random; +import java.util.Vector; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class RandomScheduler extends Scheduler { + + private Random mRand = new Random(); + + public RandomScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + } + + @Override + public Filter scheduleNextNode() { + Vector<Filter> candidates = new Vector<Filter>(); + for (Filter filter : getGraph().getFilters()) { + if (filter.canProcess()) + candidates.add(filter); + } + if (candidates.size() > 0) { + int r = mRand.nextInt(candidates.size()); + return candidates.elementAt(r); + } + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java new file mode 100644 index 0000000..12cbf19 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java @@ -0,0 +1,73 @@ +/* + * 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 android.filterfw.core; + +import java.util.Set; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class RoundRobinScheduler extends Scheduler { + + private int mLastPos = -1; + + public RoundRobinScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + mLastPos = -1; + } + + @Override + public Filter scheduleNextNode() { + Set<Filter> all_filters = getGraph().getFilters(); + if (mLastPos >= all_filters.size()) mLastPos = -1; + int pos = 0; + Filter first = null; + int firstNdx = -1; + for (Filter filter : all_filters) { + if (filter.canProcess()) { + if (pos <= mLastPos) { + if (first == null) { + // store the first available filter + first = filter; + firstNdx = pos; + } + } else { + // return the next available filter since last + mLastPos = pos; + return filter; + } + } + pos ++; + } + // going around from the beginning + if (first != null ) { + mLastPos = firstNdx; + return first; + } + // if there is nothing to be scheduled, still keep the previous + // position. + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Scheduler.java b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java new file mode 100644 index 0000000..6f0864a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java @@ -0,0 +1,47 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterGraph; + +/** + * @hide + */ +public abstract class Scheduler { + // All methods are core internal methods as Scheduler internals are only used by the GraphRunner. + + private FilterGraph mGraph; + + Scheduler(FilterGraph graph) { + mGraph = graph; + } + + FilterGraph getGraph() { + return mGraph; + } + + abstract void reset(); + + abstract Filter scheduleNextNode(); + + boolean finished() { + // TODO: Check that the state of all nodes is FINISHED. + return true; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java new file mode 100644 index 0000000..f493fd2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java @@ -0,0 +1,287 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeBuffer; +import android.filterfw.format.ObjectFormat; +import android.graphics.Bitmap; + +import java.io.InputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OptionalDataException; +import java.io.OutputStream; +import java.io.StreamCorruptedException; +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; + +/** + * A frame that serializes any assigned values. Such a frame is used when passing data objects + * between threads. + * + * @hide + */ +public class SerializedFrame extends Frame { + + /** + * The initial capacity of the serialized data stream. + */ + private final static int INITIAL_CAPACITY = 64; + + /** + * The internal data streams. + */ + private DirectByteOutputStream mByteOutputStream; + private ObjectOutputStream mObjectOut; + + /** + * An unsynchronized output stream that writes data to an accessible byte array. Callers are + * responsible for synchronization. This is more efficient than a ByteArrayOutputStream, as + * there are no array copies or synchronization involved to read back written data. + */ + private class DirectByteOutputStream extends OutputStream { + private byte[] mBuffer = null; + private int mOffset = 0; + private int mDataOffset = 0; + + public DirectByteOutputStream(int size) { + mBuffer = new byte[size]; + } + + private final void ensureFit(int bytesToWrite) { + if (mOffset + bytesToWrite > mBuffer.length) { + byte[] oldBuffer = mBuffer; + mBuffer = new byte[Math.max(mOffset + bytesToWrite, mBuffer.length * 2)]; + System.arraycopy(oldBuffer, 0, mBuffer, 0, mOffset); + oldBuffer = null; + } + } + + public final void markHeaderEnd() { + mDataOffset = mOffset; + } + + public final int getSize() { + return mOffset; + } + + public byte[] getByteArray() { + return mBuffer; + } + + @Override + public final void write(byte b[]) { + write(b, 0, b.length); + } + + @Override + public final void write(byte b[], int off, int len) { + ensureFit(len); + System.arraycopy(b, off, mBuffer, mOffset, len); + mOffset += len; + } + + @Override + public final void write(int b) { + ensureFit(1); + mBuffer[mOffset++] = (byte)b; + } + + public final void reset() { + mOffset = mDataOffset; + } + + public final DirectByteInputStream getInputStream() { + return new DirectByteInputStream(mBuffer, mOffset); + } + } + + /** + * An unsynchronized input stream that reads data directly from a provided byte array. Callers + * are responsible for synchronization and ensuring that the byte buffer is valid. + */ + private class DirectByteInputStream extends InputStream { + + private byte[] mBuffer; + private int mPos = 0; + private int mSize; + + public DirectByteInputStream(byte[] buffer, int size) { + mBuffer = buffer; + mSize = size; + } + + @Override + public final int available() { + return mSize - mPos; + } + + @Override + public final int read() { + return (mPos < mSize) ? (mBuffer[mPos++] & 0xFF) : -1; + } + + @Override + public final int read(byte[] b, int off, int len) { + if (mPos >= mSize) { + return -1; + } + if ((mPos + len) > mSize) { + len = mSize - mPos; + } + System.arraycopy(mBuffer, mPos, b, off, len); + mPos += len; + return len; + } + + @Override + public final long skip(long n) { + if ((mPos + n) > mSize) { + n = mSize - mPos; + } + if (n < 0) { + return 0; + } + mPos += n; + return n; + } + } + + SerializedFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + setReusable(false); + + // Setup streams + try { + mByteOutputStream = new DirectByteOutputStream(INITIAL_CAPACITY); + mObjectOut = new ObjectOutputStream(mByteOutputStream); + mByteOutputStream.markHeaderEnd(); + } catch (IOException e) { + throw new RuntimeException("Could not create serialization streams for " + + "SerializedFrame!", e); + } + } + + static SerializedFrame wrapObject(Object object, FrameManager frameManager) { + FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE); + SerializedFrame result = new SerializedFrame(format, frameManager); + result.setObjectValue(object); + return result; + } + + @Override + protected boolean hasNativeAllocation() { + return false; + } + + @Override + protected void releaseNativeAllocation() { + } + + @Override + public Object getObjectValue() { + return deserializeObjectValue(); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + setGenericObjectValue(ints); + } + + @Override + public int[] getInts() { + Object result = deserializeObjectValue(); + return (result instanceof int[]) ? (int[])result : null; + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + setGenericObjectValue(floats); + } + + @Override + public float[] getFloats() { + Object result = deserializeObjectValue(); + return (result instanceof float[]) ? (float[])result : null; + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length)); + } + + @Override + public ByteBuffer getData() { + Object result = deserializeObjectValue(); + return (result instanceof ByteBuffer) ? (ByteBuffer)result : null; + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + setGenericObjectValue(bitmap); + } + + @Override + public Bitmap getBitmap() { + Object result = deserializeObjectValue(); + return (result instanceof Bitmap) ? (Bitmap)result : null; + } + + @Override + protected void setGenericObjectValue(Object object) { + serializeObjectValue(object); + } + + private final void serializeObjectValue(Object object) { + try { + mByteOutputStream.reset(); + mObjectOut.writeObject(object); + mObjectOut.flush(); + mObjectOut.close(); + } catch (IOException e) { + throw new RuntimeException("Could not serialize object " + object + " in " + + this + "!", e); + } + } + + private final Object deserializeObjectValue() { + try { + InputStream inputStream = mByteOutputStream.getInputStream(); + ObjectInputStream objectStream = new ObjectInputStream(inputStream); + return objectStream.readObject(); + } catch (IOException e) { + throw new RuntimeException("Could not deserialize object in " + this + "!", e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to deserialize object of unknown class in " + + this + "!", e); + } + } + + @Override + public String toString() { + return "SerializedFrame (" + getFormat() + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java new file mode 100644 index 0000000..a971cb6 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java @@ -0,0 +1,301 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.NativeAllocatorTag; +import android.filterfw.core.Program; +import android.filterfw.core.StopWatchMap; +import android.filterfw.core.VertexFrame; +import android.filterfw.geometry.Quad; +import android.opengl.GLES20; + +/** + * @hide + */ +public class ShaderProgram extends Program { + + private int shaderProgramId; + + private int mMaxTileSize = 0; + + // Keep a reference to the GL environment, so that it does not get deallocated while there + // are still programs living in it. + private GLEnvironment mGLEnvironment; + + private StopWatchMap mTimer = null; + + private void setTimer() { + mTimer = new StopWatchMap(); + } + + // Used from native layer for creating empty wrapper only! + private ShaderProgram() { + } + + private ShaderProgram(NativeAllocatorTag tag) { + } + + public ShaderProgram(FilterContext context, String fragmentShader) { + mGLEnvironment = getGLEnvironment(context); + allocate(mGLEnvironment, null, fragmentShader); + if (!compileAndLink()) { + throw new RuntimeException("Could not compile and link shader!"); + } + this.setTimer(); + } + + public ShaderProgram(FilterContext context, String vertexShader, String fragmentShader) { + mGLEnvironment = getGLEnvironment(context); + allocate(mGLEnvironment, vertexShader, fragmentShader); + if (!compileAndLink()) { + throw new RuntimeException("Could not compile and link shader!"); + } + this.setTimer(); + } + + public static ShaderProgram createIdentity(FilterContext context) { + ShaderProgram program = nativeCreateIdentity(getGLEnvironment(context)); + program.setTimer(); + return program; + } + + @Override + protected void finalize() throws Throwable { + deallocate(); + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + @Override + public void process(Frame[] inputs, Frame output) { + if (mTimer.LOG_MFF_RUNNING_TIMES) { + mTimer.start("glFinish"); + GLES20.glFinish(); + mTimer.stop("glFinish"); + } + + // Get the GL input frames + // TODO: We do the same in the NativeProgram... can we find a better way?! + GLFrame[] glInputs = new GLFrame[inputs.length]; + for (int i = 0; i < inputs.length; ++i) { + if (inputs[i] instanceof GLFrame) { + glInputs[i] = (GLFrame)inputs[i]; + } else { + throw new RuntimeException("ShaderProgram got non-GL frame as input " + i + "!"); + } + } + + // Get the GL output frame + GLFrame glOutput = null; + if (output instanceof GLFrame) { + glOutput = (GLFrame)output; + } else { + throw new RuntimeException("ShaderProgram got non-GL output frame!"); + } + + // Adjust tiles to meet maximum tile size requirement + if (mMaxTileSize > 0) { + int xTiles = (output.getFormat().getWidth() + mMaxTileSize - 1) / mMaxTileSize; + int yTiles = (output.getFormat().getHeight() + mMaxTileSize - 1) / mMaxTileSize; + setShaderTileCounts(xTiles, yTiles); + } + + // Process! + if (!shaderProcess(glInputs, glOutput)) { + throw new RuntimeException("Error executing ShaderProgram!"); + } + + if (mTimer.LOG_MFF_RUNNING_TIMES) { + GLES20.glFinish(); + } + } + + @Override + public void setHostValue(String variableName, Object value) { + if (!setUniformValue(variableName, value)) { + throw new RuntimeException("Error setting uniform value for variable '" + + variableName + "'!"); + } + } + + @Override + public Object getHostValue(String variableName) { + return getUniformValue(variableName); + } + + public void setAttributeValues(String attributeName, float[] data, int componentCount) { + if (!setShaderAttributeValues(attributeName, data, componentCount)) { + throw new RuntimeException("Error setting attribute value for attribute '" + + attributeName + "'!"); + } + } + + public void setAttributeValues(String attributeName, + VertexFrame vertexData, + int type, + int componentCount, + int strideInBytes, + int offsetInBytes, + boolean normalize) { + if (!setShaderAttributeVertexFrame(attributeName, + vertexData, + type, + componentCount, + strideInBytes, + offsetInBytes, + normalize)) { + throw new RuntimeException("Error setting attribute value for attribute '" + + attributeName + "'!"); + } + } + + public void setSourceRegion(Quad region) { + setSourceRegion(region.p0.x, region.p0.y, + region.p1.x, region.p1.y, + region.p2.x, region.p2.y, + region.p3.x, region.p3.y); + } + + public void setTargetRegion(Quad region) { + setTargetRegion(region.p0.x, region.p0.y, + region.p1.x, region.p1.y, + region.p2.x, region.p2.y, + region.p3.x, region.p3.y); + } + + public void setSourceRect(float x, float y, float width, float height) { + setSourceRegion(x, y, x + width, y, x, y + height, x + width, y + height); + } + + public void setTargetRect(float x, float y, float width, float height) { + setTargetRegion(x, y, x + width, y, x, y + height, x + width, y + height); + } + + public void setClearsOutput(boolean clears) { + if (!setShaderClearsOutput(clears)) { + throw new RuntimeException("Could not set clears-output flag to " + clears + "!"); + } + } + + public void setClearColor(float r, float g, float b) { + if (!setShaderClearColor(r, g, b)) { + throw new RuntimeException("Could not set clear color to " + r + "," + g + "," + b + "!"); + } + } + + public void setBlendEnabled(boolean enable) { + if (!setShaderBlendEnabled(enable)) { + throw new RuntimeException("Could not set Blending " + enable + "!"); + } + } + + public void setBlendFunc(int sfactor, int dfactor) { + if (!setShaderBlendFunc(sfactor, dfactor)) { + throw new RuntimeException("Could not set BlendFunc " + sfactor +","+ dfactor + "!"); + } + } + + public void setDrawMode(int drawMode) { + if (!setShaderDrawMode(drawMode)) { + throw new RuntimeException("Could not set GL draw-mode to " + drawMode + "!"); + } + } + + public void setVertexCount(int count) { + if (!setShaderVertexCount(count)) { + throw new RuntimeException("Could not set GL vertex count to " + count + "!"); + } + } + + public void setMaximumTileSize(int size) { + mMaxTileSize = size; + } + + public void beginDrawing() { + if (!beginShaderDrawing()) { + throw new RuntimeException("Could not prepare shader-program for drawing!"); + } + } + + private static GLEnvironment getGLEnvironment(FilterContext context) { + GLEnvironment result = context != null ? context.getGLEnvironment() : null; + if (result == null) { + throw new NullPointerException("Attempting to create ShaderProgram with no GL " + + "environment in place!"); + } + return result; + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean allocate(GLEnvironment glEnv, + String vertexShader, + String fragmentShader); + + private native boolean deallocate(); + + private native boolean compileAndLink(); + + private native boolean shaderProcess(GLFrame[] inputs, GLFrame output); + + private native boolean setUniformValue(String name, Object value); + + private native Object getUniformValue(String name); + + public native boolean setSourceRegion(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + + private native boolean setTargetRegion(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + + private static native ShaderProgram nativeCreateIdentity(GLEnvironment glEnv); + + private native boolean setShaderClearsOutput(boolean clears); + + private native boolean setShaderBlendEnabled(boolean enable); + + private native boolean setShaderBlendFunc(int sfactor, int dfactor); + + private native boolean setShaderClearColor(float r, float g, float b); + + private native boolean setShaderDrawMode(int drawMode); + + private native boolean setShaderTileCounts(int xCount, int yCount); + + private native boolean setShaderVertexCount(int vertexCount); + + private native boolean beginShaderDrawing(); + + private native boolean setShaderAttributeValues(String attributeName, + float[] data, + int componentCount); + + private native boolean setShaderAttributeVertexFrame(String attributeName, + VertexFrame vertexData, + int type, + int componentCount, + int strideInBytes, + int offsetInBytes, + boolean normalize); + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java new file mode 100644 index 0000000..534a30d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java @@ -0,0 +1,161 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeBuffer; +import android.filterfw.format.ObjectFormat; +import android.graphics.Bitmap; + +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class SimpleFrame extends Frame { + + private Object mObject; + + SimpleFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + initWithFormat(format); + setReusable(false); + } + + static SimpleFrame wrapObject(Object object, FrameManager frameManager) { + FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE); + SimpleFrame result = new SimpleFrame(format, frameManager); + result.setObjectValue(object); + return result; + } + + private void initWithFormat(FrameFormat format) { + final int count = format.getLength(); + final int baseType = format.getBaseType(); + switch (baseType) { + case FrameFormat.TYPE_BYTE: + mObject = new byte[count]; + break; + case FrameFormat.TYPE_INT16: + mObject = new short[count]; + break; + case FrameFormat.TYPE_INT32: + mObject = new int[count]; + break; + case FrameFormat.TYPE_FLOAT: + mObject = new float[count]; + break; + case FrameFormat.TYPE_DOUBLE: + mObject = new double[count]; + break; + default: + mObject = null; + break; + } + } + + @Override + protected boolean hasNativeAllocation() { + return false; + } + + @Override + protected void releaseNativeAllocation() { + } + + @Override + public Object getObjectValue() { + return mObject; + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + setGenericObjectValue(ints); + } + + @Override + public int[] getInts() { + return (mObject instanceof int[]) ? (int[])mObject : null; + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + setGenericObjectValue(floats); + } + + @Override + public float[] getFloats() { + return (mObject instanceof float[]) ? (float[])mObject : null; + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length)); + } + + @Override + public ByteBuffer getData() { + return (mObject instanceof ByteBuffer) ? (ByteBuffer)mObject : null; + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + setGenericObjectValue(bitmap); + } + + @Override + public Bitmap getBitmap() { + return (mObject instanceof Bitmap) ? (Bitmap)mObject : null; + } + + private void setFormatObjectClass(Class objectClass) { + MutableFrameFormat format = getFormat().mutableCopy(); + format.setObjectClass(objectClass); + setFormat(format); + } + + @Override + protected void setGenericObjectValue(Object object) { + // Update the FrameFormat class + // TODO: Take this out! FrameFormats should not be modified and convenience formats used + // instead! + FrameFormat format = getFormat(); + if (format.getObjectClass() == null) { + setFormatObjectClass(object.getClass()); + } else if (!format.getObjectClass().isAssignableFrom(object.getClass())) { + throw new RuntimeException( + "Attempting to set object value of type '" + object.getClass() + "' on " + + "SimpleFrame of type '" + format.getObjectClass() + "'!"); + } + + // Set the object value + mObject = object; + } + + @Override + public String toString() { + return "SimpleFrame (" + getFormat() + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java new file mode 100644 index 0000000..e2b9047 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java @@ -0,0 +1,107 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.SimpleFrame; +import android.filterfw.core.VertexFrame; + +/** + * @hide + */ +public class SimpleFrameManager extends FrameManager { + + public SimpleFrameManager() { + } + + @Override + public Frame newFrame(FrameFormat format) { + return createNewFrame(format); + } + + @Override + public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) { + Frame result = null; + switch(format.getTarget()) { + case FrameFormat.TARGET_GPU: { + GLFrame glFrame = new GLFrame(format, this, bindingType, bindingId); + glFrame.init(getGLEnvironment()); + result = glFrame; + break; + } + + default: + throw new RuntimeException("Attached frames are not supported for target type: " + + FrameFormat.targetToString(format.getTarget()) + "!"); + } + return result; + } + + private Frame createNewFrame(FrameFormat format) { + Frame result = null; + switch(format.getTarget()) { + case FrameFormat.TARGET_SIMPLE: + result = new SimpleFrame(format, this); + break; + + case FrameFormat.TARGET_NATIVE: + result = new NativeFrame(format, this); + break; + + case FrameFormat.TARGET_GPU: { + GLFrame glFrame = new GLFrame(format, this); + glFrame.init(getGLEnvironment()); + result = glFrame; + break; + } + + case FrameFormat.TARGET_VERTEXBUFFER: { + result = new VertexFrame(format, this); + break; + } + + default: + throw new RuntimeException("Unsupported frame target type: " + + FrameFormat.targetToString(format.getTarget()) + "!"); + } + return result; + } + + @Override + public Frame retainFrame(Frame frame) { + frame.incRefCount(); + return frame; + } + + @Override + public Frame releaseFrame(Frame frame) { + int refCount = frame.decRefCount(); + if (refCount == 0 && frame.hasNativeAllocation()) { + frame.releaseNativeAllocation(); + return null; + } else if (refCount < 0) { + throw new RuntimeException("Frame reference count dropped below 0!"); + } + return frame; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java new file mode 100644 index 0000000..bb4e5ba --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java @@ -0,0 +1,45 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class SimpleScheduler extends Scheduler { + + public SimpleScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + } + + @Override + public Filter scheduleNextNode() { + for (Filter filter : getGraph().getFilters()) { + if (filter.canProcess()) + return filter; + } + return null; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java new file mode 100644 index 0000000..444a1fc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java @@ -0,0 +1,101 @@ +/* + * 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 android.filterfw.core; + +import android.os.SystemClock; +import android.util.Log; +import java.util.HashMap; + +/** + * @hide + */ +class StopWatch { + + private int STOP_WATCH_LOGGING_PERIOD = 200; + private String TAG = "MFF"; + + private String mName; + private long mStartTime; + private long mTotalTime; + private int mNumCalls; + + public StopWatch(String name) { + mName = name; + mStartTime = -1; + mTotalTime = 0; + mNumCalls = 0; + } + + public void start() { + if (mStartTime != -1) { + throw new RuntimeException( + "Calling start with StopWatch already running"); + } + mStartTime = SystemClock.elapsedRealtime(); + } + + public void stop() { + if (mStartTime == -1) { + throw new RuntimeException( + "Calling stop with StopWatch already stopped"); + } + long stopTime = SystemClock.elapsedRealtime(); + mTotalTime += stopTime - mStartTime; + ++mNumCalls; + mStartTime = -1; + if (mNumCalls % STOP_WATCH_LOGGING_PERIOD == 0) { + Log.i(TAG, "AVG ms/call " + mName + ": " + + String.format("%.1f", mTotalTime * 1.0f / mNumCalls)); + mTotalTime = 0; + mNumCalls = 0; + } + } + +} + +public class StopWatchMap { + + public boolean LOG_MFF_RUNNING_TIMES = false; + + private HashMap<String, StopWatch> mStopWatches = null; + + public StopWatchMap() { + mStopWatches = new HashMap<String, StopWatch>(); + } + + public void start(String stopWatchName) { + if (!LOG_MFF_RUNNING_TIMES) { + return; + } + if (!mStopWatches.containsKey(stopWatchName)) { + mStopWatches.put(stopWatchName, new StopWatch(stopWatchName)); + } + mStopWatches.get(stopWatchName).start(); + } + + public void stop(String stopWatchName) { + if (!LOG_MFF_RUNNING_TIMES) { + return; + } + if (!mStopWatches.containsKey(stopWatchName)) { + throw new RuntimeException( + "Calling stop with unknown stopWatchName: " + stopWatchName); + } + mStopWatches.get(stopWatchName).stop(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/StreamPort.java b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java new file mode 100644 index 0000000..8520a0b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java @@ -0,0 +1,100 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class StreamPort extends InputPort { + + private Frame mFrame; + private boolean mPersistent; + + public StreamPort(Filter filter, String name) { + super(filter, name); + } + + @Override + public void clear() { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void setFrame(Frame frame) { + assignFrame(frame, true); + } + + @Override + public void pushFrame(Frame frame) { + assignFrame(frame, false); + } + + protected synchronized void assignFrame(Frame frame, boolean persistent) { + assertPortIsOpen(); + checkFrameType(frame, persistent); + + if (persistent) { + if (mFrame != null) { + mFrame.release(); + } + } else if (mFrame != null) { + throw new RuntimeException( + "Attempting to push more than one frame on port: " + this + "!"); + } + mFrame = frame.retain(); + mFrame.markReadOnly(); + mPersistent = persistent; + } + + @Override + public synchronized Frame pullFrame() { + // Make sure we have a frame + if (mFrame == null) { + throw new RuntimeException("No frame available to pull on port: " + this + "!"); + } + + // Return a retained result + Frame result = mFrame; + if (mPersistent) { + mFrame.retain(); + } else { + mFrame = null; + } + return result; + } + + @Override + public synchronized boolean hasFrame() { + return mFrame != null; + } + + @Override + public String toString() { + return "input " + super.toString(); + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mFrame != null) { + checkFrameManager(mFrame, context); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java new file mode 100644 index 0000000..abbd359 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java @@ -0,0 +1,227 @@ +/* + * 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 android.filterfw.core; + +import android.os.ConditionVariable; +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @hide + */ +public class SyncRunner extends GraphRunner { + + private Scheduler mScheduler = null; + + private OnRunnerDoneListener mDoneListener = null; + private ScheduledThreadPoolExecutor mWakeExecutor = new ScheduledThreadPoolExecutor(1); + private ConditionVariable mWakeCondition = new ConditionVariable(); + + private StopWatchMap mTimer = null; + + private final boolean mLogVerbose; + private final static String TAG = "SyncRunner"; + + // TODO: Provide factory based constructor? + public SyncRunner(FilterContext context, FilterGraph graph, Class schedulerClass) { + super(context); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + if (mLogVerbose) Log.v(TAG, "Initializing SyncRunner"); + + // Create the scheduler + if (Scheduler.class.isAssignableFrom(schedulerClass)) { + try { + Constructor schedulerConstructor = schedulerClass.getConstructor(FilterGraph.class); + mScheduler = (Scheduler)schedulerConstructor.newInstance(graph); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Scheduler does not have constructor <init>(FilterGraph)!", e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate the Scheduler instance!", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access Scheduler constructor!", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Scheduler constructor threw an exception", e); + } catch (Exception e) { + throw new RuntimeException("Could not instantiate Scheduler", e); + } + } else { + throw new IllegalArgumentException("Class provided is not a Scheduler subclass!"); + } + + // Associate this runner and the graph with the context + mFilterContext = context; + mFilterContext.addGraph(graph); + + mTimer = new StopWatchMap(); + + if (mLogVerbose) Log.v(TAG, "Setting up filters"); + + // Setup graph filters + graph.setupFilters(); + } + + @Override + public FilterGraph getGraph() { + return mScheduler != null ? mScheduler.getGraph() : null; + } + + public int step() { + assertReadyToStep(); + if (!getGraph().isReady() ) { + throw new RuntimeException("Trying to process graph that is not open!"); + } + return performStep() ? RESULT_RUNNING : determinePostRunState(); + } + + public void beginProcessing() { + mScheduler.reset(); + getGraph().beginProcessing(); + } + + public void close() { + // Close filters + if (mLogVerbose) Log.v(TAG, "Closing graph."); + getGraph().closeFilters(mFilterContext); + mScheduler.reset(); + } + + @Override + public void run() { + if (mLogVerbose) Log.v(TAG, "Beginning run."); + + assertReadyToStep(); + + // Preparation + beginProcessing(); + boolean glActivated = activateGlContext(); + + // Run + boolean keepRunning = true; + while (keepRunning) { + keepRunning = performStep(); + } + + // Cleanup + if (glActivated) { + deactivateGlContext(); + } + + // Call completion callback if set + if (mDoneListener != null) { + if (mLogVerbose) Log.v(TAG, "Calling completion listener."); + mDoneListener.onRunnerDone(determinePostRunState()); + } + if (mLogVerbose) Log.v(TAG, "Run complete"); + } + + @Override + public boolean isRunning() { + return false; + } + + @Override + public void setDoneCallback(OnRunnerDoneListener listener) { + mDoneListener = listener; + } + + @Override + public void stop() { + throw new RuntimeException("SyncRunner does not support stopping a graph!"); + } + + @Override + synchronized public Exception getError() { + return null; + } + + protected void waitUntilWake() { + mWakeCondition.block(); + } + + protected void processFilterNode(Filter filter) { + if (mLogVerbose) Log.v(TAG, "Processing filter node"); + filter.performProcess(mFilterContext); + if (filter.getStatus() == Filter.STATUS_ERROR) { + throw new RuntimeException("There was an error executing " + filter + "!"); + } else if (filter.getStatus() == Filter.STATUS_SLEEPING) { + if (mLogVerbose) Log.v(TAG, "Scheduling filter wakeup"); + scheduleFilterWake(filter, filter.getSleepDelay()); + } + } + + protected void scheduleFilterWake(Filter filter, int delay) { + // Close the wake condition + mWakeCondition.close(); + + // Schedule the wake-up + final Filter filterToSchedule = filter; + final ConditionVariable conditionToWake = mWakeCondition; + + mWakeExecutor.schedule(new Runnable() { + @Override + public void run() { + filterToSchedule.unsetStatus(Filter.STATUS_SLEEPING); + conditionToWake.open(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + protected int determinePostRunState() { + boolean isBlocked = false; + for (Filter filter : mScheduler.getGraph().getFilters()) { + if (filter.isOpen()) { + if (filter.getStatus() == Filter.STATUS_SLEEPING) { + // If ANY node is sleeping, we return our state as sleeping + return RESULT_SLEEPING; + } else { + // If a node is still open, it is blocked (by input or output) + return RESULT_BLOCKED; + } + } + } + return RESULT_FINISHED; + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + boolean performStep() { + if (mLogVerbose) Log.v(TAG, "Performing one step."); + Filter filter = mScheduler.scheduleNextNode(); + if (filter != null) { + mTimer.start(filter.getName()); + processFilterNode(filter); + mTimer.stop(filter.getName()); + return true; + } else { + return false; + } + } + + void assertReadyToStep() { + if (mScheduler == null) { + throw new RuntimeException("Attempting to run schedule with no scheduler in place!"); + } else if (getGraph() == null) { + throw new RuntimeException("Calling step on scheduler with no graph in place!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java new file mode 100644 index 0000000..6982ce3 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java @@ -0,0 +1,143 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.graphics.Bitmap; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class VertexFrame extends Frame { + + private int vertexFrameId = -1; + + VertexFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + if (getFormat().getSize() <= 0) { + throw new IllegalArgumentException("Initializing vertex frame with zero size!"); + } else { + if (!nativeAllocate(getFormat().getSize())) { + throw new RuntimeException("Could not allocate vertex frame!"); + } + } + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return vertexFrameId != -1; + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + vertexFrameId = -1; + } + + @Override + public Object getObjectValue() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for vertex frame!"); + } + } + + @Override + public int[] getInts() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for vertex frame!"); + } + } + + @Override + public float[] getFloats() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + byte[] bytes = buffer.array(); + if (getFormat().getSize() != bytes.length) { + throw new RuntimeException("Data size in setData does not match vertex frame size!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set vertex frame data!"); + } + } + + @Override + public ByteBuffer getData() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setBitmap(Bitmap bitmap) { + throw new RuntimeException("Unsupported: Cannot set vertex frame bitmap value!"); + } + + @Override + public Bitmap getBitmap() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setDataFromFrame(Frame frame) { + // TODO: Optimize + super.setDataFromFrame(frame); + } + + public int getVboId() { + return getNativeVboId(); + } + + @Override + public String toString() { + return "VertexFrame (" + getFormat() + ") with VBO ID " + getVboId(); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(int size); + + private native boolean nativeDeallocate(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int getNativeVboId(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/package-info.java b/media/mca/filterfw/java/android/filterfw/core/package-info.java new file mode 100644 index 0000000..4afda1b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.core; diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java new file mode 100644 index 0000000..d57f47c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java @@ -0,0 +1,92 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.graphics.Bitmap; + +/** + * @hide + */ +public class ImageFormat { + + public static final String COLORSPACE_KEY = "colorspace"; + + public static final int COLORSPACE_GRAY = 1; + public static final int COLORSPACE_RGB = 2; + public static final int COLORSPACE_RGBA = 3; + public static final int COLORSPACE_YUV = 4; + + public static MutableFrameFormat create(int width, + int height, + int colorspace, + int bytesPerSample, + int target) { + MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_BYTE, target); + result.setDimensions(width, height); + result.setBytesPerSample(bytesPerSample); + result.setMetaValue(COLORSPACE_KEY, colorspace); + if (target == FrameFormat.TARGET_SIMPLE) { + result.setObjectClass(Bitmap.class); + } + return result; + } + + public static MutableFrameFormat create(int width, + int height, + int colorspace, + int target) { + return create(width, + height, + colorspace, + bytesPerSampleForColorspace(colorspace), + target); + } + + public static MutableFrameFormat create(int colorspace, int target) { + return create(FrameFormat.SIZE_UNSPECIFIED, + FrameFormat.SIZE_UNSPECIFIED, + colorspace, + bytesPerSampleForColorspace(colorspace), + target); + } + + public static MutableFrameFormat create(int colorspace) { + return create(FrameFormat.SIZE_UNSPECIFIED, + FrameFormat.SIZE_UNSPECIFIED, + colorspace, + bytesPerSampleForColorspace(colorspace), + FrameFormat.TARGET_UNSPECIFIED); + } + + public static int bytesPerSampleForColorspace(int colorspace) { + switch (colorspace) { + case COLORSPACE_GRAY: + return 1; + case COLORSPACE_RGB: + return 3; + case COLORSPACE_RGBA: + return 4; + case COLORSPACE_YUV: + return 3; + default: + throw new RuntimeException("Unknown colorspace id " + colorspace + "!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java new file mode 100644 index 0000000..ae39628 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java @@ -0,0 +1,105 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeBuffer; + +/** + * @hide + */ +public class ObjectFormat { + + public static MutableFrameFormat fromClass(Class clazz, int count, int target) { + // Create frame format + MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target); + result.setObjectClass(getBoxedClass(clazz)); + if (count != FrameFormat.SIZE_UNSPECIFIED) { + result.setDimensions(count); + } + result.setBytesPerSample(bytesPerSampleForClass(clazz, target)); + return result; + } + + public static MutableFrameFormat fromClass(Class clazz, int target) { + return fromClass(clazz, FrameFormat.SIZE_UNSPECIFIED, target); + } + + public static MutableFrameFormat fromObject(Object object, int target) { + return object == null + ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target) + : fromClass(object.getClass(), FrameFormat.SIZE_UNSPECIFIED, target); + } + + public static MutableFrameFormat fromObject(Object object, int count, int target) { + return object == null + ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target) + : fromClass(object.getClass(), count, target); + } + + private static int bytesPerSampleForClass(Class clazz, int target) { + // Native targets have objects manifested in a byte buffer. Thus it is important to + // correctly determine the size of single element here. + if (target == FrameFormat.TARGET_NATIVE) { + if (!NativeBuffer.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Native object-based formats must be of a " + + "NativeBuffer subclass! (Received class: " + clazz + ")."); + } + try { + return ((NativeBuffer)clazz.newInstance()).getElementSize(); + } catch (Exception e) { + throw new RuntimeException("Could not determine the size of an element in a " + + "native object-based frame of type " + clazz + "! Perhaps it is missing a " + + "default constructor?"); + } + } else { + return FrameFormat.BYTES_PER_SAMPLE_UNSPECIFIED; + } + } + + private static Class getBoxedClass(Class type) { + // Check if type is primitive + if (type.isPrimitive()) { + // Yes -> box it + if (type == boolean.class) { + return java.lang.Boolean.class; + } else if (type == byte.class) { + return java.lang.Byte.class; + } else if (type == char.class) { + return java.lang.Character.class; + } else if (type == short.class) { + return java.lang.Short.class; + } else if (type == int.class) { + return java.lang.Integer.class; + } else if (type == long.class) { + return java.lang.Long.class; + } else if (type == float.class) { + return java.lang.Float.class; + } else if (type == double.class) { + return java.lang.Double.class; + } else { + throw new IllegalArgumentException( + "Unknown primitive type: " + type.getSimpleName() + "!"); + } + } else { + // No -> return it + return type; + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java new file mode 100644 index 0000000..40f07aa --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java @@ -0,0 +1,79 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public class PrimitiveFormat { + + public static MutableFrameFormat createByteFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_BYTE, count, target); + } + + public static MutableFrameFormat createInt16Format(int count, int target) { + return createFormat(FrameFormat.TYPE_INT16, count, target); + } + + public static MutableFrameFormat createInt32Format(int count, int target) { + return createFormat(FrameFormat.TYPE_INT32, count, target); + } + + public static MutableFrameFormat createFloatFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_FLOAT, count, target); + } + + public static MutableFrameFormat createDoubleFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_DOUBLE, count, target); + } + + public static MutableFrameFormat createByteFormat(int target) { + return createFormat(FrameFormat.TYPE_BYTE, target); + } + + public static MutableFrameFormat createInt16Format(int target) { + return createFormat(FrameFormat.TYPE_INT16, target); + } + + public static MutableFrameFormat createInt32Format(int target) { + return createFormat(FrameFormat.TYPE_INT32, target); + } + + public static MutableFrameFormat createFloatFormat(int target) { + return createFormat(FrameFormat.TYPE_FLOAT, target); + } + + public static MutableFrameFormat createDoubleFormat(int target) { + return createFormat(FrameFormat.TYPE_DOUBLE, target); + } + + private static MutableFrameFormat createFormat(int baseType, int count, int target) { + MutableFrameFormat result = new MutableFrameFormat(baseType, target); + result.setDimensions(count); + return result; + } + + private static MutableFrameFormat createFormat(int baseType, int target) { + MutableFrameFormat result = new MutableFrameFormat(baseType, target); + result.setDimensionCount(1); + return result; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/package-info.java b/media/mca/filterfw/java/android/filterfw/format/package-info.java new file mode 100644 index 0000000..dfd9a3f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.format; diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java new file mode 100644 index 0000000..8207c72c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java @@ -0,0 +1,113 @@ +/* + * 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 android.filterfw.geometry; + +import java.lang.Math; + +/** + * @hide + */ +public class Point { + + public float x; + public float y; + + public Point() { + } + + public Point(float x, float y) { + this.x = x; + this.y = y; + } + + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + public boolean IsInUnitRange() { + return x >= 0.0f && x <= 1.0f && + y >= 0.0f && y <= 1.0f; + } + + public Point plus(float x, float y) { + return new Point(this.x + x, this.y + y); + } + + public Point plus(Point point) { + return this.plus(point.x, point.y); + } + + public Point minus(float x, float y) { + return new Point(this.x - x, this.y - y); + } + + public Point minus(Point point) { + return this.minus(point.x, point.y); + } + + public Point times(float s) { + return new Point(this.x * s, this.y * s); + } + + public Point mult(float x, float y) { + return new Point(this.x * x, this.y * y); + } + + public float length() { + return (float)Math.sqrt(x*x + y*y); + } + + public float distanceTo(Point p) { + return p.minus(this).length(); + } + + public Point scaledTo(float length) { + return this.times(length / this.length()); + } + + public Point normalize() { + return this.scaledTo(1.0f); + } + + public Point rotated90(int count) { + float nx = this.x; + float ny = this.y; + for (int i = 0; i < count; ++i) { + float ox = nx; + nx = ny; + ny = -ox; + } + return new Point(nx, ny); + } + + public Point rotated(float radians) { + // TODO(renn): Optimize: Keep cache of cos/sin values + return new Point((float)(Math.cos(radians) * x - Math.sin(radians) * y), + (float)(Math.sin(radians) * x + Math.cos(radians) * y)); + } + + public Point rotatedAround(Point center, float radians) { + return this.minus(center).rotated(radians).plus(center); + } + + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java new file mode 100644 index 0000000..ee092fd --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java @@ -0,0 +1,94 @@ +/* + * 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 android.filterfw.geometry; + +import android.filterfw.geometry.Point; + +import java.lang.Float; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @hide + */ +public class Quad { + + public Point p0; + public Point p1; + public Point p2; + public Point p3; + + public Quad() { + } + + public Quad(Point p0, Point p1, Point p2, Point p3) { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + public boolean IsInUnitRange() { + return p0.IsInUnitRange() && + p1.IsInUnitRange() && + p2.IsInUnitRange() && + p3.IsInUnitRange(); + } + + public Quad translated(Point t) { + return new Quad(p0.plus(t), p1.plus(t), p2.plus(t), p3.plus(t)); + } + + public Quad translated(float x, float y) { + return new Quad(p0.plus(x, y), p1.plus(x, y), p2.plus(x, y), p3.plus(x, y)); + } + + public Quad scaled(float s) { + return new Quad(p0.times(s), p1.times(s), p2.times(s), p3.times(s)); + } + + public Quad scaled(float x, float y) { + return new Quad(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y)); + } + + public Rectangle boundingBox() { + List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x); + List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y); + float x0 = Collections.min(xs); + float y0 = Collections.min(ys); + float x1 = Collections.max(xs); + float y1 = Collections.max(ys); + return new Rectangle(x0, y0, x1 - x0, y1 - y0); + } + + public float getBoundingWidth() { + List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x); + return Collections.max(xs) - Collections.min(xs); + } + + public float getBoundingHeight() { + List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y); + return Collections.max(ys) - Collections.min(ys); + } + + @Override + public String toString() { + return "{" + p0 + ", " + p1 + ", " + p2 + ", " + p3 + "}"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java new file mode 100644 index 0000000..e4bd622 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java @@ -0,0 +1,95 @@ +/* + * 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 android.filterfw.geometry; + +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; + +/** + * @hide + */ +public class Rectangle extends Quad { + + public Rectangle() { + } + + public Rectangle(float x, float y, float width, float height) { + super(new Point(x, y), + new Point(x + width, y), + new Point(x, y + height), + new Point(x + width, y + height)); + } + + public Rectangle(Point origin, Point size) { + super(origin, + origin.plus(size.x, 0.0f), + origin.plus(0.0f, size.y), + origin.plus(size.x, size.y)); + } + + public static Rectangle fromRotatedRect(Point center, Point size, float rotation) { + Point p0 = new Point(center.x - size.x/2f, center.y - size.y/2f); + Point p1 = new Point(center.x + size.x/2f, center.y - size.y/2f); + Point p2 = new Point(center.x - size.x/2f, center.y + size.y/2f); + Point p3 = new Point(center.x + size.x/2f, center.y + size.y/2f); + return new Rectangle(p0.rotatedAround(center, rotation), + p1.rotatedAround(center, rotation), + p2.rotatedAround(center, rotation), + p3.rotatedAround(center, rotation)); + } + + private Rectangle(Point p0, Point p1, Point p2, Point p3) { + super(p0, p1, p2, p3); + } + + public static Rectangle fromCenterVerticalAxis(Point center, Point vAxis, Point size) { + Point dy = vAxis.scaledTo(size.y / 2.0f); + Point dx = vAxis.rotated90(1).scaledTo(size.x / 2.0f); + return new Rectangle(center.minus(dx).minus(dy), + center.plus(dx).minus(dy), + center.minus(dx).plus(dy), + center.plus(dx).plus(dy)); + } + + public float getWidth() { + return p1.minus(p0).length(); + } + + public float getHeight() { + return p2.minus(p0).length(); + } + + public Point center() { + return p0.plus(p1).plus(p2).plus(p3).times(0.25f); + } + + @Override + public Rectangle scaled(float s) { + return new Rectangle(p0.times(s), p1.times(s), p2.times(s), p3.times(s)); + } + + @Override + public Rectangle scaled(float x, float y) { + return new Rectangle(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y)); + } + + //public Rectangle rotated(float radians) { + // TODO: Implement this. + //} + +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/package-info.java b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java new file mode 100644 index 0000000..5547622 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.geometry; diff --git a/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java new file mode 100644 index 0000000..940b393 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java @@ -0,0 +1,33 @@ +/* + * 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 android.filterfw.io; + +/** + * @hide + */ +public class GraphIOException extends Exception { + + public GraphIOException() { + super(); + } + + public GraphIOException(String message) { + super(message); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/io/GraphReader.java b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java new file mode 100644 index 0000000..deb06e2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java @@ -0,0 +1,69 @@ +/* + * 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 android.filterfw.io; + +import android.content.Context; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.KeyValueMap; +import android.filterfw.io.GraphIOException; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.StringWriter; + +/** + * @hide + */ +public abstract class GraphReader { + + protected KeyValueMap mReferences = new KeyValueMap(); + + public abstract FilterGraph readGraphString(String graphString) throws GraphIOException; + + public abstract KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException; + + public FilterGraph readGraphResource(Context context, int resourceId) throws GraphIOException { + InputStream inputStream = context.getResources().openRawResource(resourceId); + InputStreamReader reader = new InputStreamReader(inputStream); + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + try { + int bytesRead; + while ((bytesRead = reader.read(buffer, 0, 1024)) > 0) { + writer.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException("Could not read specified resource file!"); + } + return readGraphString(writer.toString()); + } + + public void addReference(String name, Object object) { + mReferences.put(name, object); + } + + public void addReferencesByMap(KeyValueMap refs) { + mReferences.putAll(refs); + } + + public void addReferencesByKeysAndValues(Object... references) { + mReferences.setKeyValues(references); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java new file mode 100644 index 0000000..4f1df02 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java @@ -0,0 +1,123 @@ +/* + * 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 android.filterfw.io; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @hide + */ +public class PatternScanner { + + private String mInput; + private Pattern mIgnorePattern; + private int mOffset = 0; + private int mLineNo = 0; + private int mStartOfLine = 0; + + public PatternScanner(String input) { + mInput = input; + } + + public PatternScanner(String input, Pattern ignorePattern) { + mInput = input; + mIgnorePattern = ignorePattern; + skip(mIgnorePattern); + } + + public String tryEat(Pattern pattern) { + // Skip ignore pattern + if (mIgnorePattern != null) { + skip(mIgnorePattern); + } + + // Create the matcher + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + + // Attempt to match + String result = null; + if (matcher.lookingAt()) { + updateLineCount(mOffset, matcher.end()); + mOffset = matcher.end(); + result = mInput.substring(matcher.start(), matcher.end()); + } + + // Skip ignore pattern + if (result != null && mIgnorePattern != null) { + skip(mIgnorePattern); + } + + return result; + } + + public String eat(Pattern pattern, String tokenName) { + String result = tryEat(pattern); + if (result == null) { + throw new RuntimeException(unexpectedTokenMessage(tokenName)); + } + return result; + } + + public boolean peek(Pattern pattern) { + // Skip ignore pattern + if (mIgnorePattern != null) { + skip(mIgnorePattern); + } + + // Create the matcher + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + + // Attempt to match + return matcher.lookingAt(); + } + + public void skip(Pattern pattern) { + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + if (matcher.lookingAt()) { + updateLineCount(mOffset, matcher.end()); + mOffset = matcher.end(); + } + } + + public boolean atEnd() { + return mOffset >= mInput.length(); + } + + public int lineNo() { + return mLineNo; + } + + public String unexpectedTokenMessage(String tokenName) { + String line = mInput.substring(mStartOfLine, mOffset); + return "Unexpected token on line " + (mLineNo + 1) + " after '" + line + "' <- Expected " + + tokenName + "!"; + } + + public void updateLineCount(int start, int end) { + for (int i = start; i < end; ++i) { + if (mInput.charAt(i) == '\n') { + ++mLineNo; + mStartOfLine = i + 1; + } + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java new file mode 100644 index 0000000..366ef82 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java @@ -0,0 +1,489 @@ +/* + * 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 android.filterfw.io; + +import java.lang.Float; +import java.lang.Integer; +import java.lang.String; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.ProtocolException; +import android.filterfw.io.GraphReader; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.PatternScanner; + +/** + * @hide + */ +public class TextGraphReader extends GraphReader { + + private ArrayList<Command> mCommands = new ArrayList<Command>(); + private Filter mCurrentFilter; + private FilterGraph mCurrentGraph; + private KeyValueMap mBoundReferences; + private KeyValueMap mSettings; + private FilterFactory mFactory; + + private interface Command { + public void execute(TextGraphReader reader) throws GraphIOException; + } + + private class ImportPackageCommand implements Command { + private String mPackageName; + + public ImportPackageCommand(String packageName) { + mPackageName = packageName; + } + + @Override + public void execute(TextGraphReader reader) throws GraphIOException { + try { + reader.mFactory.addPackage(mPackageName); + } catch (IllegalArgumentException e) { + throw new GraphIOException(e.getMessage()); + } + } + } + + private class AddLibraryCommand implements Command { + private String mLibraryName; + + public AddLibraryCommand(String libraryName) { + mLibraryName = libraryName; + } + + @Override + public void execute(TextGraphReader reader) { + reader.mFactory.addFilterLibrary(mLibraryName); + } + } + + private class AllocateFilterCommand implements Command { + private String mClassName; + private String mFilterName; + + public AllocateFilterCommand(String className, String filterName) { + mClassName = className; + mFilterName = filterName; + } + + public void execute(TextGraphReader reader) throws GraphIOException { + // Create the filter + Filter filter = null; + try { + filter = reader.mFactory.createFilterByClassName(mClassName, mFilterName); + } catch (IllegalArgumentException e) { + throw new GraphIOException(e.getMessage()); + } + + // Set it as the current filter + reader.mCurrentFilter = filter; + } + } + + private class InitFilterCommand implements Command { + private KeyValueMap mParams; + + public InitFilterCommand(KeyValueMap params) { + mParams = params; + } + + @Override + public void execute(TextGraphReader reader) throws GraphIOException { + Filter filter = reader.mCurrentFilter; + try { + filter.initWithValueMap(mParams); + } catch (ProtocolException e) { + throw new GraphIOException(e.getMessage()); + } + reader.mCurrentGraph.addFilter(mCurrentFilter); + } + } + + private class ConnectCommand implements Command { + private String mSourceFilter; + private String mSourcePort; + private String mTargetFilter; + private String mTargetName; + + public ConnectCommand(String sourceFilter, + String sourcePort, + String targetFilter, + String targetName) { + mSourceFilter = sourceFilter; + mSourcePort = sourcePort; + mTargetFilter = targetFilter; + mTargetName = targetName; + } + + @Override + public void execute(TextGraphReader reader) { + reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName); + } + } + + @Override + public FilterGraph readGraphString(String graphString) throws GraphIOException { + FilterGraph result = new FilterGraph(); + + reset(); + mCurrentGraph = result; + parseString(graphString); + applySettings(); + executeCommands(); + reset(); + + return result; + } + + private void reset() { + mCurrentGraph = null; + mCurrentFilter = null; + mCommands.clear(); + mBoundReferences = new KeyValueMap(); + mSettings = new KeyValueMap(); + mFactory = new FilterFactory(); + } + + private void parseString(String graphString) throws GraphIOException { + final Pattern commandPattern = Pattern.compile("@[a-zA-Z]+"); + final Pattern curlyClosePattern = Pattern.compile("\\}"); + final Pattern curlyOpenPattern = Pattern.compile("\\{"); + final Pattern ignorePattern = Pattern.compile("(\\s+|//[^\\n]*\\n)+"); + final Pattern packageNamePattern = Pattern.compile("[a-zA-Z\\.]+"); + final Pattern libraryNamePattern = Pattern.compile("[a-zA-Z\\./:]+"); + final Pattern portPattern = Pattern.compile("\\[[a-zA-Z0-9\\-_]+\\]"); + final Pattern rightArrowPattern = Pattern.compile("=>"); + final Pattern semicolonPattern = Pattern.compile(";"); + final Pattern wordPattern = Pattern.compile("[a-zA-Z0-9\\-_]+"); + + final int STATE_COMMAND = 0; + final int STATE_IMPORT_PKG = 1; + final int STATE_ADD_LIBRARY = 2; + final int STATE_FILTER_CLASS = 3; + final int STATE_FILTER_NAME = 4; + final int STATE_CURLY_OPEN = 5; + final int STATE_PARAMETERS = 6; + final int STATE_CURLY_CLOSE = 7; + final int STATE_SOURCE_FILTERNAME = 8; + final int STATE_SOURCE_PORT = 9; + final int STATE_RIGHT_ARROW = 10; + final int STATE_TARGET_FILTERNAME = 11; + final int STATE_TARGET_PORT = 12; + final int STATE_ASSIGNMENT = 13; + final int STATE_EXTERNAL = 14; + final int STATE_SETTING = 15; + final int STATE_SEMICOLON = 16; + + int state = STATE_COMMAND; + PatternScanner scanner = new PatternScanner(graphString, ignorePattern); + + String curClassName = null; + String curSourceFilterName = null; + String curSourcePortName = null; + String curTargetFilterName = null; + String curTargetPortName = null; + + // State machine main loop + while (!scanner.atEnd()) { + switch (state) { + case STATE_COMMAND: { + String curCommand = scanner.eat(commandPattern, "<command>"); + if (curCommand.equals("@import")) { + state = STATE_IMPORT_PKG; + } else if (curCommand.equals("@library")) { + state = STATE_ADD_LIBRARY; + } else if (curCommand.equals("@filter")) { + state = STATE_FILTER_CLASS; + } else if (curCommand.equals("@connect")) { + state = STATE_SOURCE_FILTERNAME; + } else if (curCommand.equals("@set")) { + state = STATE_ASSIGNMENT; + } else if (curCommand.equals("@external")) { + state = STATE_EXTERNAL; + } else if (curCommand.equals("@setting")) { + state = STATE_SETTING; + } else { + throw new GraphIOException("Unknown command '" + curCommand + "'!"); + } + break; + } + + case STATE_IMPORT_PKG: { + String packageName = scanner.eat(packageNamePattern, "<package-name>"); + mCommands.add(new ImportPackageCommand(packageName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_ADD_LIBRARY: { + String libraryName = scanner.eat(libraryNamePattern, "<library-name>"); + mCommands.add(new AddLibraryCommand(libraryName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_FILTER_CLASS: + curClassName = scanner.eat(wordPattern, "<class-name>"); + state = STATE_FILTER_NAME; + break; + + case STATE_FILTER_NAME: { + String curFilterName = scanner.eat(wordPattern, "<filter-name>"); + mCommands.add(new AllocateFilterCommand(curClassName, curFilterName)); + state = STATE_CURLY_OPEN; + break; + } + + case STATE_CURLY_OPEN: + scanner.eat(curlyOpenPattern, "{"); + state = STATE_PARAMETERS; + break; + + case STATE_PARAMETERS: { + KeyValueMap params = readKeyValueAssignments(scanner, curlyClosePattern); + mCommands.add(new InitFilterCommand(params)); + state = STATE_CURLY_CLOSE; + break; + } + + case STATE_CURLY_CLOSE: + scanner.eat(curlyClosePattern, "}"); + state = STATE_COMMAND; + break; + + case STATE_SOURCE_FILTERNAME: + curSourceFilterName = scanner.eat(wordPattern, "<source-filter-name>"); + state = STATE_SOURCE_PORT; + break; + + case STATE_SOURCE_PORT: { + String portString = scanner.eat(portPattern, "[<source-port-name>]"); + curSourcePortName = portString.substring(1, portString.length() - 1); + state = STATE_RIGHT_ARROW; + break; + } + + case STATE_RIGHT_ARROW: + scanner.eat(rightArrowPattern, "=>"); + state = STATE_TARGET_FILTERNAME; + break; + + case STATE_TARGET_FILTERNAME: + curTargetFilterName = scanner.eat(wordPattern, "<target-filter-name>"); + state = STATE_TARGET_PORT; + break; + + case STATE_TARGET_PORT: { + String portString = scanner.eat(portPattern, "[<target-port-name>]"); + curTargetPortName = portString.substring(1, portString.length() - 1); + mCommands.add(new ConnectCommand(curSourceFilterName, + curSourcePortName, + curTargetFilterName, + curTargetPortName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_ASSIGNMENT: { + KeyValueMap assignment = readKeyValueAssignments(scanner, semicolonPattern); + mBoundReferences.putAll(assignment); + state = STATE_SEMICOLON; + break; + } + + case STATE_EXTERNAL: { + String externalName = scanner.eat(wordPattern, "<external-identifier>"); + bindExternal(externalName); + state = STATE_SEMICOLON; + break; + } + + case STATE_SETTING: { + KeyValueMap setting = readKeyValueAssignments(scanner, semicolonPattern); + mSettings.putAll(setting); + state = STATE_SEMICOLON; + break; + } + + case STATE_SEMICOLON: + scanner.eat(semicolonPattern, ";"); + state = STATE_COMMAND; + break; + } + } + + // Make sure end of input was expected + if (state != STATE_SEMICOLON && state != STATE_COMMAND) { + throw new GraphIOException("Unexpected end of input!"); + } + } + + @Override + public KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException { + final Pattern ignorePattern = Pattern.compile("\\s+"); + PatternScanner scanner = new PatternScanner(assignments, ignorePattern); + return readKeyValueAssignments(scanner, null); + } + + private KeyValueMap readKeyValueAssignments(PatternScanner scanner, + Pattern endPattern) throws GraphIOException { + // Our parser is a state-machine, and these are our states + final int STATE_IDENTIFIER = 0; + final int STATE_EQUALS = 1; + final int STATE_VALUE = 2; + final int STATE_POST_VALUE = 3; + + final Pattern equalsPattern = Pattern.compile("="); + final Pattern semicolonPattern = Pattern.compile(";"); + final Pattern wordPattern = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9]*"); + final Pattern stringPattern = Pattern.compile("'[^']*'|\\\"[^\\\"]*\\\""); + final Pattern intPattern = Pattern.compile("[0-9]+"); + final Pattern floatPattern = Pattern.compile("[0-9]*\\.[0-9]+f?"); + final Pattern referencePattern = Pattern.compile("\\$[a-zA-Z]+[a-zA-Z0-9]"); + final Pattern booleanPattern = Pattern.compile("true|false"); + + int state = STATE_IDENTIFIER; + KeyValueMap newVals = new KeyValueMap(); + String curKey = null; + String curValue = null; + + while (!scanner.atEnd() && !(endPattern != null && scanner.peek(endPattern))) { + switch (state) { + case STATE_IDENTIFIER: + curKey = scanner.eat(wordPattern, "<identifier>"); + state = STATE_EQUALS; + break; + + case STATE_EQUALS: + scanner.eat(equalsPattern, "="); + state = STATE_VALUE; + break; + + case STATE_VALUE: + if ((curValue = scanner.tryEat(stringPattern)) != null) { + newVals.put(curKey, curValue.substring(1, curValue.length() - 1)); + } else if ((curValue = scanner.tryEat(referencePattern)) != null) { + String refName = curValue.substring(1, curValue.length()); + Object referencedObject = mBoundReferences != null + ? mBoundReferences.get(refName) + : null; + if (referencedObject == null) { + throw new GraphIOException( + "Unknown object reference to '" + refName + "'!"); + } + newVals.put(curKey, referencedObject); + } else if ((curValue = scanner.tryEat(booleanPattern)) != null) { + newVals.put(curKey, Boolean.parseBoolean(curValue)); + } else if ((curValue = scanner.tryEat(floatPattern)) != null) { + newVals.put(curKey, Float.parseFloat(curValue)); + } else if ((curValue = scanner.tryEat(intPattern)) != null) { + newVals.put(curKey, Integer.parseInt(curValue)); + } else { + throw new GraphIOException(scanner.unexpectedTokenMessage("<value>")); + } + state = STATE_POST_VALUE; + break; + + case STATE_POST_VALUE: + scanner.eat(semicolonPattern, ";"); + state = STATE_IDENTIFIER; + break; + } + } + + // Make sure end is expected + if (state != STATE_IDENTIFIER && state != STATE_POST_VALUE) { + throw new GraphIOException( + "Unexpected end of assignments on line " + scanner.lineNo() + "!"); + } + + return newVals; + } + + private void bindExternal(String name) throws GraphIOException { + if (mReferences.containsKey(name)) { + Object value = mReferences.get(name); + mBoundReferences.put(name, value); + } else { + throw new GraphIOException("Unknown external variable '" + name + "'! " + + "You must add a reference to this external in the host program using " + + "addReference(...)!"); + } + } + + /** + * Unused for now: Often you may want to declare references that are NOT in a certain graph, + * e.g. when reading multiple graphs with the same reader. We could print a warning, but even + * that may be too much. + **/ + private void checkReferences() throws GraphIOException { + for (String reference : mReferences.keySet()) { + if (!mBoundReferences.containsKey(reference)) { + throw new GraphIOException( + "Host program specifies reference to '" + reference + "', which is not " + + "declared @external in graph file!"); + } + } + } + + private void applySettings() throws GraphIOException { + for (String setting : mSettings.keySet()) { + Object value = mSettings.get(setting); + if (setting.equals("autoBranch")) { + expectSettingClass(setting, value, String.class); + if (value.equals("synced")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_SYNCED); + } else if (value.equals("unsynced")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_UNSYNCED); + } else if (value.equals("off")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_OFF); + } else { + throw new GraphIOException("Unknown autobranch setting: " + value + "!"); + } + } else if (setting.equals("discardUnconnectedOutputs")) { + expectSettingClass(setting, value, Boolean.class); + mCurrentGraph.setDiscardUnconnectedOutputs((Boolean)value); + } else { + throw new GraphIOException("Unknown @setting '" + setting + "'!"); + } + } + } + + private void expectSettingClass(String setting, + Object value, + Class expectedClass) throws GraphIOException { + if (value.getClass() != expectedClass) { + throw new GraphIOException("Setting '" + setting + "' must have a value of type " + + expectedClass.getSimpleName() + ", but found a value of type " + + value.getClass().getSimpleName() + "!"); + } + } + + private void executeCommands() throws GraphIOException { + for (Command command : mCommands) { + command.execute(this); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/io/package-info.java b/media/mca/filterfw/java/android/filterfw/io/package-info.java new file mode 100644 index 0000000..ea3e70f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.io; diff --git a/media/mca/filterfw/jni/Android.mk b/media/mca/filterfw/jni/Android.mk new file mode 100644 index 0000000..5aa5af1 --- /dev/null +++ b/media/mca/filterfw/jni/Android.mk @@ -0,0 +1,51 @@ +# 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. +# + +LOCAL_PATH := $(call my-dir) + +##################### +# Build module libfilterfw_jni +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE = libfilterfw_jni + +LOCAL_SRC_FILES = jni_init.cpp \ + jni_gl_environment.cpp \ + jni_gl_frame.cpp \ + jni_native_buffer.cpp \ + jni_native_frame.cpp \ + jni_native_program.cpp \ + jni_shader_program.cpp \ + jni_util.cpp \ + jni_vertex_frame.cpp + +# Need FilterFW lib +include $(LOCAL_PATH)/../native/libfilterfw.mk + +# Also need the JNI headers. +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + $(LOCAL_PATH)/.. + +# Don't prelink this library. For more efficient code, you may want +# to add this library to the prelink map and set this to true. However, +# it's difficult to do this for applications that are not supplied as +# part of a system image. +LOCAL_PRELINK_MODULE := false + +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp new file mode 100644 index 0000000..3c596a4 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_environment.cpp @@ -0,0 +1,377 @@ +/* + * 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. + */ +// #define LOG_NDEBUG 0 + +#include <stdint.h> +#include <android/native_window_jni.h> + +#include "jni/jni_gl_environment.h" +#include "jni/jni_util.h" +#include <media/mediarecorder.h> +#include "native/core/gl_env.h" + +#include <gui/ISurfaceTexture.h> +#include <gui/SurfaceTextureClient.h> +#include <utils/Errors.h> +#include <system/window.h> + + +using android::filterfw::GLEnv; +using android::filterfw::WindowHandle; +using android::MediaRecorder; +using android::sp; +using android::ISurfaceTexture; +using android::SurfaceTextureClient; + + +class NativeWindowHandle : public WindowHandle { + public: + NativeWindowHandle(ANativeWindow* window) : window_(window) { + } + + virtual ~NativeWindowHandle() { + } + + virtual void Destroy() { + ALOGI("Releasing ANativeWindow!"); + ANativeWindow_release(window_); + } + + virtual const void* InternalHandle() const { + return window_; + } + + virtual void* InternalHandle() { + return window_; + } + + private: + ANativeWindow* window_; +}; + +jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) { + return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<GLEnv>(env, thiz)); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env, + jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->InitWithNewContext()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env, + jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->InitWithCurrentContext()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->IsActive()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->IsContextActive()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env, + jclass clazz) { + return ToJBool(GLEnv::IsAnyContextActive()); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->Activate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->Deactivate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->SwapBuffers()) : JNI_FALSE; +} + +// Get the native mediarecorder object corresponding to the java object +static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject jmediarecorder) { + jclass clazz = env->FindClass("android/media/MediaRecorder"); + if (clazz == NULL) { + return NULL; + } + + jfieldID context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (context == NULL) { + return NULL; + } + + MediaRecorder* const p = (MediaRecorder*)env->GetIntField(jmediarecorder, context); + env->DeleteLocalRef(clazz); + return sp<MediaRecorder>(p); +} + + +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env, + jobject thiz, + jobject surface) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!surface) { + ALOGE("GLEnvironment: Null Surface passed!"); + return -1; + } else if (gl_env) { + // Get the ANativeWindow + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (!window) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + + NativeWindowHandle* winHandle = new NativeWindowHandle(window); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + if (result == -1) { + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface!"); + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window, + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + } else { + delete winHandle; + } + return result; + } + return -1; +} + +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env, + jobject thiz, + jobject surface, + jint width, + jint height) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!surface) { + ALOGE("GLEnvironment: Null SurfaceTexture passed!"); + return -1; + } else if (gl_env) { + // Get the ANativeWindow + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (!window) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + + // Don't care about format (will get overridden by SurfaceTexture + // anyway), but do care about width and height + // TODO: Probably, this should be just be + // ANativeWindow_setBuffersDimensions. The pixel format is + // set during the eglCreateWindowSurface + ANativeWindow_setBuffersGeometry(window, width, height, 0); + + NativeWindowHandle* winHandle = new NativeWindowHandle(window); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + if (result == -1) { + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!"); + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window, + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + } else { + delete winHandle; + } + return result; + } + return -1; +} + +// nativeAddSurfaceFromMediaRecorder gets an EGLSurface +// using a MediaRecorder object. +// When Mediarecorder is used for recording GL Frames, it +// will have a reference to a Native Handle (a SurfaceTexureClient) +// which talks to the StageFrightRecorder in mediaserver via +// a binder interface. +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder( + JNIEnv* env, + jobject thiz, + jobject jmediarecorder) { + ALOGV("GLEnv Jni: nativeAddSurfaceFromMediaRecorder"); + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!gl_env) { + return -1; + } + // get a native mediarecorder object from the java object + sp<MediaRecorder> mr = getMediaRecorder(env, jmediarecorder); + if (mr == NULL) { + ALOGE("GLEnvironment: Error- MediaRecorder could not be initialized!"); + return -1; + } + + // Ask the mediarecorder to return a handle to a surfacemediasource + // This will talk to the StageFrightRecorder via MediaRecorderClient + // over binder calls + sp<ISurfaceTexture> surfaceMS = mr->querySurfaceMediaSourceFromMediaServer(); + if (surfaceMS == NULL) { + ALOGE("GLEnvironment: Error- MediaRecorder returned a null \ + <ISurfaceTexture> handle."); + return -1; + } + sp<SurfaceTextureClient> surfaceTC = new SurfaceTextureClient(surfaceMS); + // Get the ANativeWindow + sp<ANativeWindow> window = surfaceTC; + + + if (window == NULL) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + window->incStrong((void*)ANativeWindow_acquire); + + // In case of encoding, no need to set the dimensions + // on the buffers. The dimensions for the final encoding are set by + // the consumer side. + // The pixel format is dictated by the GL, and set during the + // eglCreateWindowSurface + + NativeWindowHandle* winHandle = new NativeWindowHandle(window.get()); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + // If we find a surface with that window handle, just return that id + if (result != -1) { + delete winHandle; + return result; + } + // If we do not find a surface with that window handle, create + // one and assign to it the handle + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!"); + delete winHandle; + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window.get(), + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + delete winHandle; + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + return result; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->SwitchToSurfaceId(surfaceId) && gl_env->Activate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->ReleaseSurfaceId(surfaceId)) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env, + jobject thiz, + jlong timestamp) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + int64_t timestamp_native = timestamp; + return gl_env ? ToJBool(gl_env->SetSurfaceTimestamp(timestamp_native)) : JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_gl_environment.h b/media/mca/filterfw/jni/jni_gl_environment.h new file mode 100644 index 0000000..af9c744 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_environment.h @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H +#define ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env, jclass clazz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env, + jobject thiz, + jobject surface); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env, + jobject thiz, + jobject surface, + jint width, + jint height); + +// The call to hook up the SurfaceMediaSource (in MediaServer) to the GL. +// We get a sp<ISurfaceTexure> from the MediaServer and talks to MediaServer +// over a binder interface. GL hooked up to the MediaServer by using the native +// window created using the <ISurfaceTexture> handle +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder( + JNIEnv* env, + jobject thiz, + jobject mediarecorder); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env, + jobject thiz, + jlong timestamp); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp new file mode 100644 index 0000000..61340f9 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_frame.cpp @@ -0,0 +1,322 @@ +/* + * 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. + */ + +#include "android/bitmap.h" + +#include "jni/jni_gl_frame.h" +#include "jni/jni_util.h" + +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/native_frame.h" + +using android::filterfw::GLEnv; +using android::filterfw::GLFrame; +using android::filterfw::NativeFrame; + +// Helper functions //////////////////////////////////////////////////////////////////////////////// +void ConvertFloatsToRGBA(const float* floats, int length, uint8_t* result) { + for (int i = 0; i < length; ++i) { + result[i] = static_cast<uint8_t>(floats[i] * 255.0); + } +} + +void ConvertRGBAToFloats(const uint8_t* rgba, int length, float* result) { + for (int i = 0; i < length; ++i) { + result[i] = rgba[i] / 255.0; + } +} + +// GLFrame JNI implementation ////////////////////////////////////////////////////////////////////// +jboolean Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->Init(width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint tex_id, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithTexture(tex_id, width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint fbo_id, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithFbo(fbo_id, width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env, + jobject thiz, + jobject gl_env) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithExternalTexture()) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<GLFrame>(env, thiz)); +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jbyteArray Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0) { + jbyteArray result = env->NewByteArray(frame->Size()); + jbyte* data = env->GetByteArrayElements(result, NULL); + frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size()); + env->ReleaseByteArrayElements(result, data, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jintArray Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0 && (frame->Size() % sizeof(jint) == 0)) { + jintArray result = env->NewIntArray(frame->Size() / sizeof(jint)); + jint* data = env->GetIntArrayElements(result, NULL); + frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size()); + env->ReleaseIntArrayElements(result, data, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + // Convert floats to RGBA buffer + uint8_t* rgba_buffer = new uint8_t[length]; + ConvertFloatsToRGBA(float_ptr, length, rgba_buffer); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + + // Write RGBA buffer to frame + const bool success = frame->WriteData(rgba_buffer, length); + + // Clean-up + delete[] rgba_buffer; + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jfloatArray Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0) { + // Create the result array + jfloatArray result = env->NewFloatArray(frame->Size()); + jfloat* float_array = env->GetFloatArrayElements(result, NULL); + + // Read the frame pixels + uint8_t* pixels = new uint8_t[frame->Size()]; + frame->CopyDataTo(pixels, frame->Size()); + + // Convert them to floats + ConvertRGBAToFloats(pixels, frame->Size(), float_array); + + // Clean-up + delete[] pixels; + env->ReleaseFloatArrayElements(result, float_array, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && bitmap) { + uint8_t* pixels; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + const bool success = frame->WriteData(pixels, size); + return ToJBool(success && + AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && bitmap) { + uint8_t* pixels; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + frame->CopyDataTo(pixels, frame->Size()); + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env, + jobject thiz, + jint x, + jint y, + jint width, + jint height) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->SetViewport(x, y, width, height)) : JNI_FALSE; +} + +jint Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? frame->GetTextureId() : -1; +} + +jint Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? frame->GetFboId() : -1; +} + +jboolean Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->GenerateMipMap()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env, + jobject thiz, + jint param, + jint value) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->SetTextureParameter(param, value)) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->ResetTexParameters()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame) { + GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz); + NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->WriteData(other_frame->Data(), other_frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame) { + GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz); + GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->CopyPixelsFrom(other_frame)); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->FocusFrameBuffer()); +} + +jboolean Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->ReattachTextureToFbo()); +} + +jboolean Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->DetachTextureFromFbo()); +} + diff --git a/media/mca/filterfw/jni/jni_gl_frame.h b/media/mca/filterfw/jni/jni_gl_frame.h new file mode 100644 index 0000000..8a25aea --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_frame.h @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_GL_FRAME_H +#define ANDROID_FILTERFW_JNI_GL_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint tex_id, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint fbo_id, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env, + jobject thiz, + jobject gl_env); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jintArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints); + +JNIEXPORT jfloatArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jbyteArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env, jobject thiz, jobject bitmap); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env, + jobject thiz, + jint x, + jint y, + jint width, + jint height); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env, + jobject thiz, + jint param, + jint value); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif /* ANDROID_FILTERFW_JNI_GL_FRAME_H */ diff --git a/media/mca/filterfw/jni/jni_init.cpp b/media/mca/filterfw/jni/jni_init.cpp new file mode 100644 index 0000000..3b131f1 --- /dev/null +++ b/media/mca/filterfw/jni/jni_init.cpp @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "jni/jni_util.h" + +#include "native/core/native_frame.h" +#include "native/core/native_program.h" +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/shader_program.h" +#include "native/core/vertex_frame.h" + +using namespace android::filterfw; + +JavaVM* g_current_java_vm_ = NULL; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // Set the current vm pointer + g_current_java_vm_ = vm; + + // Initialize object pools + ObjectPool<NativeFrame>::Setup("android/filterfw/core/NativeFrame", "nativeFrameId"); + ObjectPool<NativeProgram>::Setup("android/filterfw/core/NativeProgram", "nativeProgramId"); + ObjectPool<GLFrame>::Setup("android/filterfw/core/GLFrame", "glFrameId"); + ObjectPool<ShaderProgram>::Setup("android/filterfw/core/ShaderProgram", "shaderProgramId"); + ObjectPool<GLEnv>::Setup("android/filterfw/core/GLEnvironment", "glEnvId"); + ObjectPool<VertexFrame>::Setup("android/filterfw/core/VertexFrame", "vertexFrameId"); + + return JNI_VERSION_1_4; +} diff --git a/media/mca/filterfw/jni/jni_native_buffer.cpp b/media/mca/filterfw/jni/jni_native_buffer.cpp new file mode 100644 index 0000000..097c145 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_buffer.cpp @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#include "jni/jni_native_buffer.h" +#include "jni/jni_util.h" + +char* GetJBufferData(JNIEnv* env, jobject buffer, int* size) { + jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer"); + + // Get fields + jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J"); + jfieldID size_field = env->GetFieldID(base_class, "mSize", "I"); + + // Get their values + char* data = reinterpret_cast<char*>(env->GetLongField(buffer, ptr_field)); + if (size) { + *size = env->GetIntField(buffer, size_field); + } + + // Clean-up + env->DeleteLocalRef(base_class); + + return data; +} + +bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size) { + jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer"); + + // Get fields + jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J"); + jfieldID size_field = env->GetFieldID(base_class, "mSize", "I"); + + // Set their values + env->SetLongField(buffer, ptr_field, reinterpret_cast<jlong>(data)); + env->SetIntField(buffer, size_field, size); + + return true; +} + +jboolean Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size) { + char* data = new char[size]; + return ToJBool(AttachDataToJBuffer(env, thiz, data, size)); +} + +jboolean Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env, + jobject thiz, + jboolean owns_data) { + if (ToCppBool(owns_data)) { + char* data = GetJBufferData(env, thiz, NULL); + delete[] data; + } + return JNI_TRUE; +} + +jboolean Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env, + jobject thiz, + jobject new_buffer) { + // Get source buffer + int size; + char* source_data = GetJBufferData(env, thiz, &size); + + // Make copy + char* target_data = new char[size]; + memcpy(target_data, source_data, size); + + // Attach it to new buffer + AttachDataToJBuffer(env, new_buffer, target_data, size); + + return JNI_TRUE; +} + diff --git a/media/mca/filterfw/jni/jni_native_buffer.h b/media/mca/filterfw/jni/jni_native_buffer.h new file mode 100644 index 0000000..73c12be --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_buffer.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H +#define ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H + +#include <jni.h> + +// Internal Buffer Unwrapping functions //////////////////////////////////////////////////////////// +/** + * Given a Java NativeBuffer instance, get access to the underlying C pointer and its size. The + * size argument may be NULL, in which case the object is not queried for its size. + **/ +char* GetJBufferData(JNIEnv* env, jobject buffer, int* size); + +/** + * Attach a given C data buffer and its size to a given allocated Java NativeBuffer instance. After + * this call, the java instance will have the given C buffer as its backing. Note, that the Java + * instance contains the flag on whether or not it owns the buffer or not, so make sure it is what + * you expect. + **/ +bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size); + +#ifdef __cplusplus +extern "C" { +#endif + +// JNI Wrappers //////////////////////////////////////////////////////////////////////////////////// +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env, jobject thiz, jboolean owns_data); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env, jobject thiz, jobject new_buffer); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp new file mode 100644 index 0000000..1dfa3e6 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_frame.cpp @@ -0,0 +1,293 @@ +/* + * 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. + */ + +#include "android/bitmap.h" + +#include "jni/jni_native_frame.h" +#include "jni/jni_native_buffer.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/gl_frame.h" +#include "native/core/native_frame.h" + +using android::filterfw::NativeFrame; +using android::filterfw::GLFrame; + +typedef union { + uint32_t value; + uint8_t rgba[4]; +} Pixel; + +jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jint size) { + return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<NativeFrame>(env, thiz)); +} + +jint Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv*, jclass) { + return sizeof(jint); +} + +jint Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv*, jclass) { + return sizeof(jfloat); +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), + 0, + length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jbyteArray Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size()) + return NULL; + jbyteArray result = env->NewByteArray(size); + env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env, + jobject thiz, + jobject buffer) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + char* data = reinterpret_cast<char*>(frame->MutableData()); + return ToJBool(AttachDataToJBuffer(env, buffer, data, frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + 0, + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jintArray Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size() || (size % sizeof(jint)) != 0) + return NULL; + const int count = size / sizeof(jint); + jintArray result = env->NewIntArray(count); + env->SetIntArrayRegion(result, 0, count, reinterpret_cast<const jint*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr), + 0, + length * sizeof(jfloat)); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jfloatArray Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size() || (size % sizeof(jfloat)) != 0) + return NULL; + const int count = size / sizeof(jfloat); + jfloatArray result = env->NewFloatArray(count); + env->SetFloatArrayRegion(result, 0, count, reinterpret_cast<const jfloat*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && bitmap) { + // Make sure frame size matches bitmap size + if ((size / 4) != (frame->Size() / bytes_per_sample)) { + ALOGE("Size mismatch in native setBitmap()!"); + return JNI_FALSE; + } + + Pixel* src_ptr; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&src_ptr)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + // Create destination pointers + uint8_t* dst_ptr = reinterpret_cast<uint8_t*>(frame->MutableData()); + const uint8_t* end_ptr = dst_ptr + frame->Size(); + switch (bytes_per_sample) { + case 1: { // RGBA -> GRAY + while (dst_ptr < end_ptr) { + const Pixel pixel = *(src_ptr++); + *(dst_ptr++) = (pixel.rgba[0] + pixel.rgba[1] + pixel.rgba[2]) / 3; + } + break; + } + case 3: { // RGBA -> RGB + while (dst_ptr < end_ptr) { + const Pixel pixel = *(src_ptr++); + *(dst_ptr++) = pixel.rgba[0]; + *(dst_ptr++) = pixel.rgba[1]; + *(dst_ptr++) = pixel.rgba[2]; + } + break; + } + case 4: { // RGBA -> RGBA + memcpy(dst_ptr, src_ptr, frame->Size()); + break; + } + default: + ALOGE("Unsupported bytes-per-pixel %d in setBitmap!", bytes_per_sample); + break; + } + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && bitmap) { + Pixel* dst_ptr; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&dst_ptr)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + // Make sure frame size matches bitmap size + if ((size / 4) != (frame->Size() / bytes_per_sample)) { + ALOGE("Size mismatch in native getBitmap()!"); + return JNI_FALSE; + } + + const uint8_t* src_ptr = frame->Data(); + const uint8_t* end_ptr = src_ptr + frame->Size(); + switch (bytes_per_sample) { + case 1: { // GRAY -> RGBA + while (src_ptr < end_ptr) { + const uint8_t value = *(src_ptr++); + dst_ptr->rgba[0] = dst_ptr->rgba[1] = dst_ptr->rgba[2] = value; + dst_ptr->rgba[3] = 255; + ++dst_ptr; + } + break; + } + case 3: { // RGB -> RGBA + while (src_ptr < end_ptr) { + dst_ptr->rgba[0] = *(src_ptr++); + dst_ptr->rgba[1] = *(src_ptr++); + dst_ptr->rgba[2] = *(src_ptr++); + dst_ptr->rgba[3] = 255; + ++dst_ptr; + } + break; + } + case 4: { // RGBA -> RGBA + memcpy(dst_ptr, src_ptr, frame->Size()); + break; + } + default: + ALOGE("Unsupported bytes-per-pixel %d in getBitmap!", bytes_per_sample); + break; + } + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jint Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + return frame ? frame->Capacity() : -1; +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame) { + NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz); + NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->WriteData(other_frame->Data(), 0, other_frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame) { + NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz); + GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(other_frame->CopyDataTo(this_frame->MutableData(), this_frame->Size())); + } + return JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_native_frame.h b/media/mca/filterfw/jni/jni_native_frame.h new file mode 100644 index 0000000..ecd9f82 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_frame.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_NATIVE_FRAME_H +#define ANDROID_FILTERFW_JNI_NATIVE_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv* env, jclass clazz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv* env, jclass clazz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jintArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints); + +JNIEXPORT jfloatArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jbyteArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env, jobject thiz, jobject buffer); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_NATIVE_FRAME_H diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp new file mode 100644 index 0000000..b30b769 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_program.cpp @@ -0,0 +1,187 @@ +/* + * 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. + */ + +#include <string> +#include <vector> + +#include "jni/jni_native_program.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/native_frame.h" +#include "native/core/native_program.h" + +using android::filterfw::NativeFrame; +using android::filterfw::NativeProgram; + +jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) { + return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<NativeProgram>(env, thiz)); +} + +jboolean Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallInit()); +} + +jboolean Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env, + jobject thiz, + jstring lib_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && lib_name && program->OpenLibrary(ToCppString(env, lib_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && func_name && program->BindInitFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindSetValueFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindGetValueFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && func_name && program->BindProcessFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindResetFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindTeardownFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallInit()); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env, + jobject thiz, + jstring key, + jstring value) { + if (!value) { + ALOGE("Native Program: Attempting to set null value for key %s!", + ToCppString(env, key).c_str()); + } + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + const std::string c_value = ToCppString(env, value); + const std::string c_key = ToCppString(env, key); + return ToJBool(program && program->CallSetValue(c_key, c_value)); +} + +jstring Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env, + jobject thiz, + jstring key) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + const std::string c_key = ToCppString(env, key); + if (program) { + return ToJString(env, program->CallGetValue(c_key)); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + + // Sanity checks + if (!program || !inputs) { + return JNI_FALSE; + } + + // Get the input buffers + const int input_count = env->GetArrayLength(inputs); + std::vector<const char*> input_buffers(input_count, NULL); + std::vector<int> input_sizes(input_count, 0); + for (int i = 0 ; i < input_count; ++i) { + const char* input_data = NULL; + int input_size = 0; + jobject input = env->GetObjectArrayElement(inputs, i); + if (input) { + NativeFrame* native_frame = ConvertFromJava<NativeFrame>(env, input); + if (!native_frame) { + ALOGE("NativeProgram: Could not grab NativeFrame input %d!", i); + return JNI_FALSE; + } + input_data = reinterpret_cast<const char*>(native_frame->Data()); + input_size = native_frame->Size(); + } + input_buffers[i] = input_data; + input_sizes[i] = input_size; + } + + // Get the output buffer + char* output_data = NULL; + int output_size = 0; + if (output) { + NativeFrame* output_frame = ConvertFromJava<NativeFrame>(env, output); + if (!output_frame) { + ALOGE("NativeProgram: Could not grab NativeFrame output!"); + return JNI_FALSE; + } + output_data = reinterpret_cast<char*>(output_frame->MutableData()); + output_size = output_frame->Size(); + } + + // Process the frames! + return ToJBool(program->CallProcess(input_buffers, input_sizes, output_data, output_size)); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallReset()); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallTeardown()); +} diff --git a/media/mca/filterfw/jni/jni_native_program.h b/media/mca/filterfw/jni/jni_native_program.h new file mode 100644 index 0000000..fa97c39 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_program.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H +#define ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env, + jobject thiz, + jstring lib_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env, + jobject thiz, + jstring key, + jstring value); + +JNIEXPORT jstring JNICALL +Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env, + jobject thiz, + jstring key); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp new file mode 100644 index 0000000..19f43cd --- /dev/null +++ b/media/mca/filterfw/jni/jni_shader_program.cpp @@ -0,0 +1,326 @@ +/* + * 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. + */ + +#include <string> +#include <vector> + +#include "jni/jni_shader_program.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/geometry.h" +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/shader_program.h" +#include "native/core/vertex_frame.h" + +using android::filterfw::GLEnv; +using android::filterfw::GLFrame; +using android::filterfw::Point; +using android::filterfw::ProgramVar; +using android::filterfw::Quad; +using android::filterfw::ShaderProgram; +using android::filterfw::VertexFrame; + +jboolean Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jstring vertex_shader, + jstring fragment_shader) { + // Get the GLEnv pointer + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + + // Create the shader + if (!fragment_shader || !gl_env_ptr) + return false; + else if (!vertex_shader) + return ToJBool(WrapObjectInJava(new ShaderProgram( + gl_env_ptr, + ToCppString(env, fragment_shader)), + env, + thiz, + true)); + else + return ToJBool(WrapObjectInJava(new ShaderProgram( + gl_env_ptr, + ToCppString(env, vertex_shader), + ToCppString(env, fragment_shader)), + env, + thiz, + true)); +} + +jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<ShaderProgram>(env, thiz)); +} + +jboolean Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + return program ? ToJBool(program->CompileAndLink()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env, + jobject thiz, + jstring key, + jobject value) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + const Value c_value = ToCValue(env, value); + const std::string c_key = ToCppString(env, key); + if (c_value.value) { + return ToJBool(program && program->SetUniformValue(c_key, c_value)); + } else { + ALOGE("ShaderProgram: Could not convert java object value passed for key '%s'!", c_key.c_str()); + return JNI_FALSE; + } +} + +jobject Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env, + jobject thiz, + jstring key) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + const std::string c_key = ToCppString(env, key); + return program ? ToJObject(env, program->GetUniformValue(c_key)) : JNI_NULL; +} + +jboolean Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + std::vector<const GLFrame*> input_frames; + if (program && inputs && output) { + // Get the input frames + const int input_count = env->GetArrayLength(inputs); + for (int i = 0; i < input_count; ++i) { + jobject input = env->GetObjectArrayElement(inputs, i); + const GLFrame* input_frame = ConvertFromJava<GLFrame>(env, input); + if (!input || !input_frame) { + ALOGE("ShaderProgram: invalid input frame %d!", i); + return JNI_FALSE; + } + input_frames.push_back(input_frame); + } + + // Get the output frame + GLFrame* output_frame = ConvertFromJava<GLFrame>(env, output); + if (!output_frame) { + ALOGE("ShaderProgram: no output frame found!"); + return JNI_FALSE; + } + + // Process the frames! + if (!program->Process(input_frames, output_frame)) { + ALOGE("ShaderProgram: error processing shader!"); + return JNI_FALSE; + } + + return JNI_TRUE; + } + return JNI_FALSE; +} + +jobject Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env, + jclass, + jobject gl_env) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + ShaderProgram* program = gl_env_ptr ? ShaderProgram::CreateIdentity(gl_env_ptr) : NULL; + return program ? WrapNewObjectInJava(program, env, false) : NULL; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetSourceRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3))); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetTargetRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3))); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env, + jobject thiz, + jboolean clears) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetClearsOutput(ToCppBool(clears)); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env, + jobject thiz, + jboolean enable) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetBlendEnabled(ToCppBool(enable)); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env, + jobject thiz, + jint sfactor, + jint dfactor) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetBlendFunc(sfactor, dfactor); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env, + jobject thiz, + jfloat r, + jfloat g, + jfloat b) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetClearColor(r, g, b, 1.0f); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env, + jobject thiz, + jint draw_mode) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetDrawMode(draw_mode); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env, + jobject thiz, + jint x_count, + jint y_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetTileCounts(x_count, y_count); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env, + jobject thiz, + jint vertex_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetVertexCount(vertex_count); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + return ToJBool(program && program->BeginDraw()); +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues( + JNIEnv* env, + jobject thiz, + jstring attr_name, + jfloatArray values, + jint component_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + // Get the floats to set + jfloat* float_ptr = env->GetFloatArrayElements(values, NULL); + const int length = env->GetArrayLength(values); + + // Get the program variable to set + const std::string attr_string = ToCppString(env, attr_name); + ProgramVar program_var = program->GetAttribute(attr_string); + + // Set the variable + if (float_ptr && ShaderProgram::IsVarValid(program_var)) { + const bool success = program->SetAttributeValues(program_var, + reinterpret_cast<float*>(float_ptr), + length, + component_count); + env->ReleaseFloatArrayElements(values, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame( + JNIEnv* env, + jobject thiz, + jstring attr_name, + jobject vertex_frame, + jint type, + jint component_count, + jint stride, + jint offset, + jboolean normalize) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + // Get the vertex frame + VertexFrame* v_frame = ConvertFromJava<VertexFrame>(env, vertex_frame); + + // Get the program variable to set + const std::string attr_string = ToCppString(env, attr_name); + ProgramVar program_var = program->GetAttribute(attr_string); + + // Set the variable + if (v_frame && ShaderProgram::IsVarValid(program_var)) { + const bool success = program->SetAttributeValues(program_var, + v_frame, + type, + component_count, + stride, + offset, + ToCppBool(normalize)); + return ToJBool(success); + } + } + return JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_shader_program.h b/media/mca/filterfw/jni/jni_shader_program.h new file mode 100644 index 0000000..94a1dd4 --- /dev/null +++ b/media/mca/filterfw/jni/jni_shader_program.h @@ -0,0 +1,150 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H +#define ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H + +#include <jni.h> + +#include "native/core/value.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jstring vertex_shader, + jstring fragment_shader); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env, + jobject thiz, + jstring key, + jobject value); + +JNIEXPORT jobject JNICALL +Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env, + jobject thiz, + jstring key); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output); + +JNIEXPORT jobject JNICALL +Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env, + jclass clazz, + jobject gl_env); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env, + jobject thiz, + jboolean clears); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env, + jobject thiz, + jfloat r, + jfloat g, + jfloat b); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env, + jobject thiz, + jboolean enable); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env, + jobject thiz, + jint sfactor, + jint dfactor); +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env, + jobject thiz, + jint draw_mode); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env, + jobject thiz, + jint x_count, + jint y_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env, + jobject thiz, + jint vertex_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues(JNIEnv* env, + jobject thiz, + jstring attr_name, + jfloatArray values, + jint component_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame(JNIEnv* env, + jobject thiz, + jstring attr_name, + jobject vertex_frame, + jint type, + jint component_count, + jint stride, + jint offset, + jboolean normalize); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H diff --git a/media/mca/filterfw/jni/jni_util.cpp b/media/mca/filterfw/jni/jni_util.cpp new file mode 100644 index 0000000..30c0898 --- /dev/null +++ b/media/mca/filterfw/jni/jni_util.cpp @@ -0,0 +1,188 @@ +/* + * 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. + */ + +#include <string> + +#include "jni/jni_util.h" + +#include "base/logging.h" + +#if 0 +JavaObject::JavaObject() + : object_(JNI_NULL), + ref_count_(new int(0)) { +} + +JavaObject::JavaObject(const JavaObject& java_obj) + : object_(java_obj.object_), + ref_count_(java_obj.ref_count_) { + Retain(); +} + +JavaObject::JavaObject(jobject object, JNIEnv* env) + : object_(NULL), + ref_count_(new int(0)) { + Retain(); + object_ = env->NewGlobalRef(object_); +} + +JavaObject::~JavaObject() { + Release(); +} + +JavaObject& JavaObject::operator=(const JavaObject& java_obj) { + Release(); + object_ = java_obj.object_; + ref_count_ = java_obj.ref_count_; + Retain(); + return *this; +} + +void JavaObject::Retain() { + if (ref_count_) + ++(*ref_count_); + else + ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted."); +} + +void JavaObject::Release() { + if (ref_count_) { + if (*ref_count_ > 0) + --(*ref_count_); + if (*ref_count_ == 0) { + JNIEnv* env = GetCurrentJNIEnv(); + if (!env) + ALOGE("JavaObject: Releasing outside of Java thread. Will just leak!"); + else if (object_) + env->DeleteGlobalRef(object_); + delete ref_count_; + ref_count_ = NULL; + } + } else { + ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted."); + } +} + +void JavaObject::Reset() { + Release(); + object_ = NULL; + ref_count_ = new int(0); +} + +JavaVM* GetCurrentJavaVM() { + return g_current_java_vm_; +} + +JNIEnv* GetCurrentJNIEnv() { + JavaVM* vm = GetCurrentJavaVM(); + JNIEnv* env = NULL; + const jint result = vm->GetEnv(reinterpret_cast<void**>(&env), + JNI_VERSION_1_4); + return result == JNI_OK ? env : NULL; +} +#endif + +jstring ToJString(JNIEnv* env, const std::string& value) { + return env->NewStringUTF(value.c_str()); +} + +std::string ToCppString(JNIEnv* env, jstring value) { + jboolean isCopy; + const char* c_value = env->GetStringUTFChars(value, &isCopy); + std::string result(c_value); + if (isCopy == JNI_TRUE) + env->ReleaseStringUTFChars(value, c_value); + return result; +} + +jboolean ToJBool(bool value) { + return value ? JNI_TRUE : JNI_FALSE; +} + +bool ToCppBool(jboolean value) { + return value == JNI_TRUE; +} + +// TODO: We actually shouldn't use such a function as it requires a class name lookup at every +// invocation. Instead, store the class objects and use those. +bool IsJavaInstanceOf(JNIEnv* env, jobject object, const std::string& class_name) { + jclass clazz = env->FindClass(class_name.c_str()); + return clazz ? env->IsInstanceOf(object, clazz) == JNI_TRUE : false; +} + +template<typename T> +jobject CreateJObject(JNIEnv* env, const std::string& class_name, const std::string& signature, T value) { + jobject result = JNI_NULL; + + return result; +} + +Value ToCValue(JNIEnv* env, jobject object) { + Value result = MakeNullValue(); + if (object != NULL) { + if (IsJavaInstanceOf(env, object, "java/lang/Boolean")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "booleanValue", "()Z"); + result = MakeIntValue(env->CallBooleanMethod(object, method) == JNI_TRUE ? 1 : 0); + } else if (IsJavaInstanceOf(env, object, "java/lang/Integer")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "intValue", "()I"); + result = MakeIntValue(env->CallIntMethod(object, method)); + } else if (IsJavaInstanceOf(env, object, "java/lang/Float")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "floatValue", "()F"); + result = MakeFloatValue(env->CallFloatMethod(object, method)); + } else if (IsJavaInstanceOf(env, object, "java/lang/String")) { + result = MakeStringValue(ToCppString(env, static_cast<jstring>(object)).c_str()); + } else if (IsJavaInstanceOf(env, object, "[I")) { + jint* elems = env->GetIntArrayElements(static_cast<jintArray>(object), NULL); + const jint count = env->GetArrayLength(static_cast<jintArray>(object)); + result = MakeIntArrayValue(elems, count); + env->ReleaseIntArrayElements(static_cast<jintArray>(object), elems, JNI_ABORT); + } else if (IsJavaInstanceOf(env, object, "[F")) { + jfloat* elems = env->GetFloatArrayElements(static_cast<jfloatArray>(object), NULL); + const jint count = env->GetArrayLength(static_cast<jfloatArray>(object)); + result = MakeFloatArrayValue(elems, count); + env->ReleaseFloatArrayElements(static_cast<jfloatArray>(object), elems, JNI_ABORT); + } + } + return result; +} + +jobject ToJObject(JNIEnv* env, const Value& value) { + jobject result = JNI_NULL; + if (ValueIsInt(value)) { + jclass clazz = env->FindClass("java/lang/Integer"); + jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(I)V"); + result = env->NewObject(clazz, constructorID, GetIntValue(value)); + } else if (ValueIsFloat(value)) { + jclass clazz = env->FindClass("java/lang/Float"); + jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(F)V"); + result = env->NewObject(clazz, constructorID, GetFloatValue(value)); + } else if (ValueIsString(value)) { + result = ToJString(env, GetStringValue(value)); + } else if (ValueIsIntArray(value)) { + result = env->NewIntArray(GetValueCount(value)); + env->SetIntArrayRegion(static_cast<jintArray>(result), + 0, + GetValueCount(value), + reinterpret_cast<const jint*>(GetIntArrayValue(value))); + } else if (ValueIsFloatArray(value)) { + result = env->NewFloatArray(GetValueCount(value)); + env->SetFloatArrayRegion(static_cast<jfloatArray>(result), + 0, + GetValueCount(value), + reinterpret_cast<const jfloat*>(GetFloatArrayValue(value))); + } + return result; +} diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h new file mode 100644 index 0000000..68ff653 --- /dev/null +++ b/media/mca/filterfw/jni/jni_util.h @@ -0,0 +1,283 @@ +/* + * 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. + */ + +#include <jni.h> + +#include <hash_map> +#include <string> + +#include "base/utilities.h" +#include "core/value.h" + +#ifndef ANDROID_FILTERFW_JNI_JNI_UTIL_H +#define ANDROID_FILTERFW_JNI_JNI_UTIL_H + +// We add this JNI_NULL macro to allow consistent code separation of Java and +// C++ types. +#define JNI_NULL NULL + +#if 0 +// Pointer to current JavaVM. Do not use this directly. Instead use the funciton +// GetCurrentJavaVM(). +extern JavaVM* g_current_java_vm_; + +// Wrapper around a java object pointer, which includes the environment +// pointer in which the object "lives". This is used for passing down Java +// objects from the Java layer to C++. +// While an instance of this class does not own the underlying java object, it +// does hold a global reference to it, so that the Java garbage collector does +// not destroy it. It uses reference counting to determine when it can destroy +// the reference. +// TODO: Add multi-thread support! +class JavaObject { + public: + // Creates a NULL JavaObject. + JavaObject(); + + // Creates a wrapper around the given object in the given JNI environment. + JavaObject(jobject object, JNIEnv* env); + + // Copy constructor. + JavaObject(const JavaObject& java_obj); + + // Destructor. + ~JavaObject(); + + // Assignment operator. + JavaObject& operator=(const JavaObject& java_obj); + + // Access to the object (non-const as JNI functions are non-const). + jobject object() const { + return object_; + } + + // Resets this object to the NULL JavaObject. + void Reset(); + + private: + // Retain the instance, i.e. increase reference count. + void Retain(); + + // Release the instance, i.e. decrease reference count. + void Release(); + + // The object pointer (not owned). + jobject object_; + + // The reference count of this object + int* ref_count_; +}; +#endif + +// ObjectPool template class. This class keeps track of C++ instances that are +// coupled to Java objects. This is done by using an "id" field in the Java +// object, which is then mapped to the correct instance here. It should not be +// necessary to use this class directly. Instead, the convenience functions +// below can be used. +template<class T> +class ObjectPool { + public: + // Create a new ObjectPool for a specific object type. Pass the path to the + // Java equivalent class of the C++ class, and the name of the java member + // field that will store the object's ID. + static void Setup(const std::string& jclass_name, + const std::string& id_fld_name) { + instance_ = new ObjectPool<T>(jclass_name, id_fld_name); + } + + // Return the shared instance to this type's pool. + static ObjectPool* Instance() { + return instance_; + } + + // Delete this type's pool. + static void TearDown() { + delete instance_; + } + + // Register a new C++ object with the pool. This does not affect the Java + // layer. Use WrapObject() instead to perform the necessary Java-side + // assignments. Pass true to owns if the object pool owns the object. + int RegisterObject(T* object, bool owns) { + const int id = next_id_; + objects_[id] = object; + owns_[id] = owns; + ++next_id_; + return id; + } + + // Return the object in the pool with the specified ID. + T* ObjectWithID(int obj_id) const { + typename CObjMap::const_iterator iter = objects_.find(obj_id); + return iter == objects_.end() ? NULL : iter->second; + } + + // Get the ID of a Java object. This ID can be used to look-up the C++ + // object. + int GetObjectID(JNIEnv* env, jobject j_object) { + jclass cls = env->GetObjectClass(j_object); + jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I"); + const int result = env->GetIntField(j_object, id_field); + env->DeleteLocalRef(cls); + return result; + } + + // Take a C++ object and wrap it with a given Java object. This will + // essentially set the ID member of the Java object to the ID of the C++ + // object. Pass true to owns if the object pool owns the object. + bool WrapObject(T* c_object, JNIEnv* env, jobject j_object, bool owns) { + const int id = RegisterObject(c_object, owns); + jclass cls = env->GetObjectClass(j_object); + jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I"); + env->SetIntField(j_object, id_field, id); + env->DeleteLocalRef(cls); + return true; + } + + // Remove the object with the given ID from this pool, and delete it. This + // does not affect the Java layer. + bool DeleteObjectWithID(int obj_id) { + typename CObjMap::iterator iter = objects_.find(obj_id); + const bool found = iter != objects_.end(); + if (found) { + if (owns_[obj_id]) + delete iter->second; + objects_.erase(iter); + } + return found; + } + + // Instantiates a new java object for this class. The Java class must have + // a default constructor for this to succeed. + jobject CreateJavaObject(JNIEnv* env) { + jclass cls = env->FindClass(jclass_name_.c_str()); + jmethodID constructor = env->GetMethodID( + cls, + "<init>", + "(Landroid/filterfw/core/NativeAllocatorTag;)V"); + jobject result = env->NewObject(cls, constructor, JNI_NULL); + env->DeleteLocalRef(cls); + return result; + } + + int GetObjectCount() const { + return objects_.size(); + } + + const std::string& GetJavaClassName() const { + return jclass_name_; + } + + private: + explicit ObjectPool(const std::string& jclass_name, + const std::string& id_fld_name) + : jclass_name_(jclass_name), + id_field_name_(id_fld_name), + next_id_(0) { } + + typedef std::hash_map<int, T*> CObjMap; + typedef std::hash_map<int, bool> FlagMap; + static ObjectPool* instance_; + std::string jclass_name_; + std::string id_field_name_; + int next_id_; + CObjMap objects_; + FlagMap owns_; + + DISALLOW_COPY_AND_ASSIGN(ObjectPool); +}; + +template<typename T> ObjectPool<T>* ObjectPool<T>::instance_ = NULL; + +// Convenience Functions /////////////////////////////////////////////////////// + +// This function "links" the C++ instance and the Java instance, so that they +// can be mapped to one another. This must be called for every C++ instance +// which is wrapped by a Java front-end interface. Pass true to owns, if the +// Java layer should own the object. +template<typename T> +bool WrapObjectInJava(T* c_object, JNIEnv* env, jobject j_object, bool owns) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool ? pool->WrapObject(c_object, env, j_object, owns) : false; +} + +// Creates a new Java instance, which wraps the passed C++ instance. Returns +// the wrapped object or JNI_NULL if there was an error. Pass true to owns, if +// the Java layer should own the object. +template<typename T> +jobject WrapNewObjectInJava(T* c_object, JNIEnv* env, bool owns) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + if (pool) { + jobject result = pool->CreateJavaObject(env); + if (WrapObjectInJava(c_object, env, result, owns)) + return result; + } + return JNI_NULL; +} + +// Use ConvertFromJava to obtain a C++ instance given a Java object. This +// instance must have been wrapped in Java using the WrapObjectInJava() +// function. +template<typename T> +T* ConvertFromJava(JNIEnv* env, jobject j_object) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool && j_object + ? pool->ObjectWithID(pool->GetObjectID(env, j_object)) + : NULL; +} + +// Delete the native object given a Java instance. This should be called from +// the Java object's finalizer. +template<typename T> +bool DeleteNativeObject(JNIEnv* env, jobject j_object) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool && j_object + ? pool->DeleteObjectWithID(pool->GetObjectID(env, j_object)) + : false; +} + +#if 0 +// Get the current JNI VM, or NULL if there is no current VM +JavaVM* GetCurrentJavaVM(); + +// Get the current JNI environment, or NULL if this is not a JNI thread +JNIEnv* GetCurrentJNIEnv(); +#endif + +// Convert C++ boolean to Java boolean. +jboolean ToJBool(bool value); + +// Convert Java boolean to C++ boolean. +bool ToCppBool(jboolean value); + +// Convert Java String to C++ string. +jstring ToJString(JNIEnv* env, const std::string& value); + +// Convert C++ string to Java String. +std::string ToCppString(JNIEnv* env, jstring value); + +// Convert Java object to a (C) Value object. +Value ToCValue(JNIEnv* env, jobject object); + +// Convert a (C) Value object to a Java object. +jobject ToJObject(JNIEnv* env, const Value& value); + +// Returns true, iff the passed object is an instance of the class specified +// by its fully qualified class name. +bool IsJavaInstanceOf(JNIEnv* env, jobject object, + const std::string& class_name); + +#endif // ANDROID_FILTERFW_JNI_JNI_UTIL_H diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp new file mode 100644 index 0000000..caae938 --- /dev/null +++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#include "jni/jni_vertex_frame.h" +#include "jni/jni_util.h" + +#include "native/core/vertex_frame.h" + +using android::filterfw::VertexFrame; + +jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jint size) { + return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<VertexFrame>(env, thiz)); +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr), + length * sizeof(jfloat)); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), + length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jint Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + return frame ? frame->GetVboId() : -1; +} diff --git a/media/mca/filterfw/jni/jni_vertex_frame.h b/media/mca/filterfw/jni/jni_vertex_frame.h new file mode 100644 index 0000000..fcd7ea1 --- /dev/null +++ b/media/mca/filterfw/jni/jni_vertex_frame.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_VERTEX_FRAME_H +#define ANDROID_FILTERFW_JNI_VERTEX_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_VERTEX_FRAME_H diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk new file mode 100644 index 0000000..46ee283 --- /dev/null +++ b/media/mca/filterfw/native/Android.mk @@ -0,0 +1,44 @@ +# 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. +# + +LOCAL_PATH := $(call my-dir) + +##################### +# Build module libfilterfw_static + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterfw_native + +LOCAL_SRC_FILES += core/geometry.cpp \ + core/gl_env.cpp \ + core/gl_frame.cpp \ + core/native_frame.cpp \ + core/native_program.cpp \ + core/shader_program.cpp \ + core/vertex_frame.cpp \ + core/value.cpp + +# add local includes +include $(LOCAL_PATH)/libfilterfw.mk + +# gcc should always be placed at the end. +LOCAL_EXPORT_LDLIBS := -llog -lgcc + +# TODO: Build a shared library as well? +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/mca/filterfw/native/base/logging.h b/media/mca/filterfw/native/base/logging.h new file mode 100644 index 0000000..1236d0b --- /dev/null +++ b/media/mca/filterfw/native/base/logging.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_BASE_LOGGING_H +#define ANDROID_FILTERFW_BASE_LOGGING_H + +#define LOG_EVERY_FRAME false + +#define LOG_FRAME(...) if (LOG_EVERY_FRAME) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) + +#define LOG_TAG "MCA" +#include <utils/Log.h> + +#endif // ANDROID_FILTERFW_BASE_LOGGING_H diff --git a/media/mca/filterfw/native/base/utilities.h b/media/mca/filterfw/native/base/utilities.h new file mode 100644 index 0000000..6bb3b7f --- /dev/null +++ b/media/mca/filterfw/native/base/utilities.h @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_BASE_UTILITIES_H +#define ANDROID_FILTERFW_BASE_UTILITIES_H + +#include <set> +#include <utility> + +namespace android { +namespace filterfw { + +// Convenience Macro to make copy constructor and assignment operator private +// (thereby disallowing copying and assigning). +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +// Perform a lookup in a map or hash_map. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +template <class Collection> +const typename Collection::value_type::second_type* +FindOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return &it->second; +} + +// A simple class that gives checklist functionality: There are essemtially two +// operations defined on a CheckList: +// - Adding a new (unchecked) item. +// - Checking off an item. +// When checking off the last remaining item CheckItem() returns true. +template<typename T> +class CheckList { + public: + // Add a new unchecked item. Does nothing if item is already in checklist. + void AddItem(const T& item); + + // Check off an item in the checklist. Returns true if all items have been + // checked. + bool CheckItem(const T& item); + + // Clear the checklist. + void Clear() { + items_.clear(); + } + + private: + std::set<T> items_; +}; + +template<typename T> +void CheckList<T>::AddItem(const T& item) { + if (!ContainsKey(items_, item)) + items_.insert(item); +} + +template<typename T> +bool CheckList<T>::CheckItem(const T& item) { + typename std::set<T>::iterator iter = items_.find(item); + if (iter != items_.end()) + items_.erase(iter); + return items_.empty(); +} + +// Perform a lookup in a map or hash_map whose values are pointers. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +// This function does not distinguish between a missing key and a key mapped +// to a NULL value. +template <class Collection> +const typename Collection::value_type::second_type +FindPtrOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return it->second; +} + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template <typename Collection, typename Key> +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Insert a new key and value into a map or hash_map. +// If the key is not present in the map the key and value are +// inserted, otherwise nothing happens. True indicates that an insert +// took place, false indicates the key was already present. +template <class Collection, class Key, class Value> +bool InsertIfNotPresent(Collection * const collection, + const Key& key, const Value& value) { + std::pair<typename Collection::iterator, bool> ret = + collection->insert(typename Collection::value_type(key, value)); + return ret.second; +} + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_BASE_UTILITIES_H diff --git a/media/mca/filterfw/native/core/geometry.cpp b/media/mca/filterfw/native/core/geometry.cpp new file mode 100644 index 0000000..677b91d --- /dev/null +++ b/media/mca/filterfw/native/core/geometry.cpp @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#include "core/geometry.h" + +#include <cmath> + +#include "base/logging.h" + +namespace android { +namespace filterfw { + +float Point::Length() const { + return std::sqrt(x_ * x_ + y_ * y_); +} + +bool Point::ScaleTo(float new_length) { + float length = Length(); + if (length == 0.0f) { + return false; + } + x_ *= new_length / length; + y_ *= new_length / length; + return true; +} + +float Point::Distance(const Point& p0, const Point& p1) { + Point diff = p1 - p0; + return diff.Length(); +} + +Point Point::operator+(const Point& other) const { + Point out; + out.x_ = x_ + other.x_; + out.y_ = y_ + other.y_; + return out; +} + +Point Point::operator-(const Point& other) const { + Point out; + out.x_ = x_ - other.x_; + out.y_ = y_ - other.y_; + return out; +} + +Point Point::operator*(float factor) const { + Point out; + out.x_ = factor * x_; + out.y_ = factor * y_; + return out; +} + +void Point::Rotate90Clockwise() { + const float x = x_; + x_ = y_; + y_ = -x; +} + +bool Rect::ExpandToAspectRatio(float ratio) { + if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) { + return false; + } + + const float current_ratio = width / height; + if (current_ratio < ratio) { + const float dx = width * (ratio / current_ratio - 1.0f); + x -= dx / 2.0f; + width += dx; + } else { + const float dy = height * (current_ratio / ratio - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ExpandToMinLength(float length) { + if (width <= 0.0f || height <= 0.0f || length <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (length > current_length) { + const float dx = width * (length / current_length - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (length / current_length - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ScaleWithLengthLimit(float factor, float max_length) { + if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (current_length >= max_length) { + return true; + } + + float f = factor; + if (current_length * f > max_length) { + f *= max_length / (current_length * f); + } + + const float dx = width * (f - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (f - 1.0f); + y -= dy / 2.0f; + height += dy; + return true; +} + +const Point& Quad::point(int ix) const { + ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds"); + return points_[ix]; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/geometry.h b/media/mca/filterfw/native/core/geometry.h new file mode 100644 index 0000000..7f89eef --- /dev/null +++ b/media/mca/filterfw/native/core/geometry.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GEOMETRY_H +#define ANDROID_FILTERFW_CORE_GEOMETRY_H + +#include <vector> + +namespace android { +namespace filterfw { + +// This is an initial implementation of some geometrical structures. This is +// likely to grow and become more sophisticated in the future. + +class Point { + public: + Point() : x_(0.0f), y_(0.0f) {} + Point(float x, float y) : x_(x), y_(y) {} + + float x() const { return x_; } + float y() const { return y_; } + + float Length() const; + bool ScaleTo(float new_length); + static float Distance(const Point& p0, const Point& p1); + + // Add more of these as needed: + Point operator+(const Point& other) const; + Point operator-(const Point& other) const; + Point operator*(float factor) const; + + void Rotate90Clockwise(); + + private: + float x_, y_; +}; + +class Quad { + public: + Quad() : points_(4) {} + virtual ~Quad() {} + + Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) + : points_(4) { + points_[0] = p0; + points_[1] = p1; + points_[2] = p2; + points_[3] = p3; + } + + const std::vector<Point>& points() const { return points_; } + const Point& point(int ix) const; + + protected: + std::vector<Point> points_; +}; + +struct Rect { + float x, y, width, height; + + Rect() { + x = y = 0.0f; + width = height = 1.0f; + } + + Rect(float x, float y, float width, float height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + } + + bool ExpandToAspectRatio(float ratio); + bool ExpandToMinLength(float length); + bool ScaleWithLengthLimit(float factor, float max_length); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GEOMETRY_H diff --git a/media/mca/filterfw/native/core/gl_buffer_interface.h b/media/mca/filterfw/native/core/gl_buffer_interface.h new file mode 100644 index 0000000..24b1db9 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_buffer_interface.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H +#define ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H + +#include <GLES2/gl2.h> + +namespace android { +namespace filterfw { + +class GLTextureHandle { + public: + virtual ~GLTextureHandle() { } + + // Returns the held texture id. + virtual GLuint GetTextureId() const = 0; + + // Binds the held texture. This may result in creating the texture if it + // is not yet available. + virtual bool FocusTexture() = 0; + + // Generates the mipmap chain of the held texture. Returns true, iff + // generating was successful. + virtual bool GenerateMipMap() = 0; + + // Set a texture parameter (see glTextureParameter documentation). Returns + // true iff the parameter was set successfully. + virtual bool SetTextureParameter(GLenum pname, GLint value) = 0; + + // Returns the texture target used. + // Texture Target should be: GL_TEXTURE_2D, GL_TEXTURE_EXTERNAL_OES. + virtual GLuint GetTextureTarget() const = 0; +}; + +class GLFrameBufferHandle { + public: + virtual ~GLFrameBufferHandle() { } + + // Returns the held FBO id. + virtual GLuint GetFboId() const = 0; + + // Binds the held FBO. This may result in creating the FBO if it + // is not yet available. + virtual bool FocusFrameBuffer() = 0; +}; + +// Interface to instances that hold GL textures and frame-buffer-objects. +// The GLFrame class implements this interface. +class GLBufferHandle : public GLTextureHandle, public GLFrameBufferHandle { + public: + virtual ~GLBufferHandle() { } +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp new file mode 100644 index 0000000..738b8e0 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_env.cpp @@ -0,0 +1,408 @@ +/* + * 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. + */ +// #define LOG_NDEBUG 0 + +#include "base/logging.h" +#include "base/utilities.h" +#include "core/gl_env.h" +#include "core/shader_program.h" +#include "core/vertex_frame.h" +#include "system/window.h" + +#include <map> +#include <string> +#include <EGL/eglext.h> + +namespace android { +namespace filterfw { + +GLEnv::GLEnv() + : display_(EGL_NO_DISPLAY), + context_id_(0), + surface_id_(0), + max_surface_id_(0), + created_context_(false), + created_surface_(false), + initialized_(false) { +} + +GLEnv::~GLEnv() { + // Destroy surfaces + for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin(); + it != surfaces_.end(); + ++it) { + if (it->first != 0 || created_surface_) { + eglDestroySurface(display(), it->second.first); + if (it->second.second) { + it->second.second->Destroy(); + delete it->second.second; + } + } + } + + // Destroy contexts + for (std::map<int, EGLContext>::iterator it = contexts_.begin(); + it != contexts_.end(); + ++it) { + if (it->first != 0 || created_context_) + eglDestroyContext(display(), it->second); + } + + // Destroy attached shaders and frames + STLDeleteValues(&attached_shaders_); + STLDeleteValues(&attached_vframes_); + + // Destroy display + if (initialized_) + eglTerminate(display()); + + // Log error if this did not work + if (CheckEGLError("TearDown!")) + ALOGE("GLEnv: Error tearing down GL Environment!"); +} + +bool GLEnv::IsInitialized() const { + return (contexts_.size() > 0 && + surfaces_.size() > 0 && + display_ != EGL_NO_DISPLAY); +} + +bool GLEnv::Deactivate() { + eglMakeCurrent(display(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + return !CheckEGLError("eglMakeCurrent"); +} + +bool GLEnv::Activate() { + ALOGV("Activate()"); + if (display() != eglGetCurrentDisplay() || + context() != eglGetCurrentContext() || + surface() != eglGetCurrentSurface(EGL_DRAW)) { + // Make sure we are initialized + if (context() == EGL_NO_CONTEXT || surface() == EGL_NO_SURFACE) + return false; + + // Make our context current + ALOGV("eglMakeCurrent"); + eglMakeCurrent(display(), surface(), surface(), context()); + + return !CheckEGLMakeCurrentError(); + } + return true; +} + +bool GLEnv::SwapBuffers() { + const bool result = eglSwapBuffers(display(), surface()) == EGL_TRUE; + return !CheckEGLError("eglSwapBuffers") && result; +} + +bool GLEnv::InitWithCurrentContext() { + if (IsInitialized()) + return true; + + display_ = eglGetCurrentDisplay(); + contexts_[0] = eglGetCurrentContext(); + surfaces_[0] = SurfaceWindowPair(eglGetCurrentSurface(EGL_DRAW), NULL); + + return (context() != EGL_NO_CONTEXT) && + (display() != EGL_NO_DISPLAY) && + (surface() != EGL_NO_SURFACE); +} + +bool GLEnv::InitWithNewContext() { + if (IsInitialized()) { + ALOGE("GLEnv: Attempting to reinitialize environment!"); + return false; + } + + display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (CheckEGLError("eglGetDisplay")) return false; + + EGLint majorVersion; + EGLint minorVersion; + eglInitialize(display(), &majorVersion, &minorVersion); + if (CheckEGLError("eglInitialize")) return false; + initialized_ = true; + + // Configure context/surface + EGLConfig config; + EGLint numConfigs = -1; + + // TODO(renn): Do we need the window bit here? + // TODO: Currently choosing the config that includes all + // This is not needed if the encoding is not being used + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + eglChooseConfig(display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnv::Init: No suitable EGL configuration found!"); + return false; + } + + // Create dummy surface using a SurfaceTexture + surfaceTexture_ = new SurfaceTexture(0); + window_ = new SurfaceTextureClient(static_cast<sp<ISurfaceTexture> >( + surfaceTexture_->getBufferQueue())); + + surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL); + if (CheckEGLError("eglCreateWindowSurface")) return false; + + // Create context + EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + contexts_[0] = eglCreateContext(display(), + config, + EGL_NO_CONTEXT, + context_attribs); + if (CheckEGLError("eglCreateContext")) return false; + + created_context_ = created_surface_ = true; + + return true; +} + +bool GLEnv::IsActive() const { + ALOGV("IsActive()"); + return context() == eglGetCurrentContext() + && display() == eglGetCurrentDisplay() + && surface() == eglGetCurrentSurface(EGL_DRAW); +} + +bool GLEnv::IsContextActive() const { + return context() == eglGetCurrentContext(); +} + +bool GLEnv::IsAnyContextActive() { + return eglGetCurrentContext() != EGL_NO_CONTEXT; +} + +int GLEnv::AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle) { + const int id = ++max_surface_id_; + surfaces_[id] = SurfaceWindowPair(surface, window_handle); + return id; +} + +int GLEnv::AddSurface(const EGLSurface& surface) { + return AddWindowSurface(surface, NULL); +} + +bool GLEnv::SwitchToSurfaceId(int surface_id) { + ALOGV("SwitchToSurfaceId"); + if (surface_id_ != surface_id) { + const SurfaceWindowPair* surface = FindOrNull(surfaces_, surface_id); + if (surface) { + bool wasActive = IsActive(); + surface_id_ = surface_id; + return wasActive ? Activate() : true; + } + return false; + } + return true; +} + +bool GLEnv::ReleaseSurfaceId(int surface_id) { + if (surface_id > 0) { + const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, surface_id); + if (surface_window_pair) { + if (surface_id_ == surface_id) + SwitchToSurfaceId(0); + eglDestroySurface(display(), surface_window_pair->first); + if (surface_window_pair->second) { + surface_window_pair->second->Destroy(); + delete surface_window_pair->second; + } + surfaces_.erase(surface_id); + return true; + } + } + return false; +} + +bool GLEnv::SetSurfaceTimestamp(int64_t timestamp) { + if (surface_id_ > 0) { + const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, + surface_id_); + if (surface_window_pair) { + ANativeWindow *window = static_cast<ANativeWindow*>( + surface_window_pair->second->InternalHandle()); + native_window_set_buffers_timestamp(window, timestamp); + return true; + } + } + return false; +} + +int GLEnv::FindSurfaceIdForWindow(const WindowHandle* window_handle) { + for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin(); + it != surfaces_.end(); + ++it) { + const WindowHandle* my_handle = it->second.second; + if (my_handle && my_handle->Equals(window_handle)) { + return it->first; + } + } + return -1; +} + + +int GLEnv::AddContext(const EGLContext& context) { + const int id = contexts_.size(); + contexts_[id] = context; + return id; +} + +bool GLEnv::SwitchToContextId(int context_id) { + const EGLContext* context = FindOrNull(contexts_, context_id); + if (context) { + if (context_id_ != context_id) { + context_id_ = context_id; + return Activate(); + } + return true; + } + return false; +} + +void GLEnv::ReleaseContextId(int context_id) { + if (context_id > 0) { + const EGLContext* context = FindOrNull(contexts_, context_id); + if (context) { + contexts_.erase(context_id); + if (context_id_ == context_id && IsActive()) + SwitchToContextId(0); + eglDestroyContext(display(), *context); + } + } +} + +bool GLEnv::CheckGLError(const std::string& op) { + bool err = false; + for (GLint error = glGetError(); error; error = glGetError()) { + ALOGE("GL Error: Operation '%s' caused GL error (0x%x)\n", + op.c_str(), + error); + err = true; + } + return err; +} + +bool GLEnv::CheckEGLError(const std::string& op) { + bool err = false; + for (EGLint error = eglGetError(); + error != EGL_SUCCESS; + error = eglGetError()) { + ALOGE("EGL Error: Operation '%s' caused EGL error (0x%x)\n", + op.c_str(), + error); + err = true; + } + return err; +} + +bool GLEnv::CheckEGLMakeCurrentError() { + bool err = false; + for (EGLint error = eglGetError(); + error != EGL_SUCCESS; + error = eglGetError()) { + switch (error) { + case EGL_BAD_DISPLAY: + ALOGE("EGL Error: Attempting to activate context with bad display!"); + break; + case EGL_BAD_SURFACE: + ALOGE("EGL Error: Attempting to activate context with bad surface!"); + break; + case EGL_BAD_ACCESS: + ALOGE("EGL Error: Attempting to activate context, which is " + "already active in another thread!"); + break; + default: + ALOGE("EGL Error: Making EGL rendering context current caused " + "error: 0x%x\n", error); + } + err = true; + } + return err; +} + +GLuint GLEnv::GetCurrentProgram() { + GLint result; + glGetIntegerv(GL_CURRENT_PROGRAM, &result); + ALOG_ASSERT(result >= 0); + return static_cast<GLuint>(result); +} + +EGLDisplay GLEnv::GetCurrentDisplay() { + return eglGetCurrentDisplay(); +} + +int GLEnv::NumberOfComponents(GLenum type) { + switch (type) { + case GL_BOOL: + case GL_FLOAT: + case GL_INT: + return 1; + case GL_BOOL_VEC2: + case GL_FLOAT_VEC2: + case GL_INT_VEC2: + return 2; + case GL_INT_VEC3: + case GL_FLOAT_VEC3: + case GL_BOOL_VEC3: + return 3; + case GL_BOOL_VEC4: + case GL_FLOAT_VEC4: + case GL_INT_VEC4: + case GL_FLOAT_MAT2: + return 4; + case GL_FLOAT_MAT3: + return 9; + case GL_FLOAT_MAT4: + return 16; + default: + return 0; + } +} + +void GLEnv::AttachShader(int key, ShaderProgram* shader) { + ShaderProgram* existingShader = ShaderWithKey(key); + if (existingShader) + delete existingShader; + attached_shaders_[key] = shader; +} + +void GLEnv::AttachVertexFrame(int key, VertexFrame* frame) { + VertexFrame* existingFrame = VertexFrameWithKey(key); + if (existingFrame) + delete existingFrame; + attached_vframes_[key] = frame; +} + +ShaderProgram* GLEnv::ShaderWithKey(int key) { + return FindPtrOrNull(attached_shaders_, key); +} + +VertexFrame* GLEnv::VertexFrameWithKey(int key) { + return FindPtrOrNull(attached_vframes_, key); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h new file mode 100644 index 0000000..b61785f --- /dev/null +++ b/media/mca/filterfw/native/core/gl_env.h @@ -0,0 +1,261 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_ENV_H +#define ANDROID_FILTERFW_CORE_GL_ENV_H + +#include <string> +#include <utility> +#include <map> + +#include "base/logging.h" +#include "base/utilities.h" + +#include <GLES2/gl2.h> +#include <EGL/egl.h> + +#include <gui/ISurfaceTexture.h> +#include <gui/SurfaceTextureClient.h> + +namespace android { +namespace filterfw { + +class ShaderProgram; +class VertexFrame; + +class WindowHandle { + public: + virtual ~WindowHandle() { + } + + virtual void Destroy() = 0; + + virtual bool Equals(const WindowHandle* window) const { + return InternalHandle() == window->InternalHandle(); + } + + virtual const void* InternalHandle() const = 0; + + virtual void* InternalHandle() = 0; +}; + +// The GLEnv class provides functionality related to the EGL environment, which +// includes the display, context, and surface. It is possible to either create +// a new environment or base it off the currently active EGL environment. In +// order to do the latter, an EGL environment must be setup already (though not +// necessarily through this class), and have an active display, context, and +// surface. +class GLEnv { + public: + // Constructing and Activating ///////////////////////////////////////////// + // Constructs a new GLEnv object. This does not create a GL context. + GLEnv(); + + // Destructor. Tears down and deallocates any GL objects that were created + // by this instance. + ~GLEnv(); + + // Inits a new GL environment, including a new surface and context. You + // must call Activate() before performing any GL operations. + bool InitWithNewContext(); + + // Inits the GL environment from the current GL environment. Use this when + // there is already a display, surface and context available (possibly + // created by the host application). You do not need to call Activate() as + // this context is active already. + bool InitWithCurrentContext(); + + // Activates the environment, and makes the associated GL context the + // current context. Creates the environment, if it has not been created + // already. Returns true if the activation was successful. + bool Activate(); + + // Deactivates the environment. Returns true if the deactivation was + // successful. You may want to call this when moving a context to another + // thread. In this case, deactivate the GLEnv in the old thread, and + // reactivate it in the new thread. + bool Deactivate(); + + // When rendering to a visible surface, call this to swap between the + // offscreen and onscreen buffers. Returns true if the buffer swap was + // successful. + bool SwapBuffers(); + + // Working with Surfaces /////////////////////////////////////////////////// + + // Add a surface to the environment. This surface will now be managed (and + // owned) by the GLEnv instance. Returns the id of the surface. + int AddSurface(const EGLSurface& surface); + + // Add a window surface to the environment. The window is passed in as + // an opaque window handle. + // This surface will now be managed (and owned) by the GLEnv instance. + // Returns the id of the surface. + int AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle); + + // Switch to the surface with the specified id. This will make the surface + // active, if it is not active already. Specify an ID of 0 if you would like + // to switch to the default surface. Returns true if successful. + bool SwitchToSurfaceId(int surface_id); + + // Release the surface with the specified id. This will deallocate the + // surface. If this is the active surface, the environment will switch to + // the default surface (0) first. You cannot release the default surface. + bool ReleaseSurfaceId(int surface_id); + + // Set the timestamp for the current surface. Must be called + // before swapBuffers to associate the timestamp with the frame + // resulting from swapBuffers. + bool SetSurfaceTimestamp(int64_t timestamp); + + // Looks for a surface with the associated window handle. Returns -1 if no + // surface with such a window was found. + int FindSurfaceIdForWindow(const WindowHandle* window_handle); + + // Obtain the environment's EGL surface. + const EGLSurface& surface() const { + return surfaces_.find(surface_id_)->second.first; + } + + // Working with Contexts /////////////////////////////////////////////////// + + // Add a context to the environment. This context will now be managed (and + // owned) by the GLEnv instance. Returns the id of the context. + int AddContext(const EGLContext& context); + + // Switch to the context with the specified id. This will make the context + // active, if it is not active already. Specify an ID of 0 if you would like + // to switch to the default context. Returns true if successful. + bool SwitchToContextId(int context_id); + + // Release the context with the specified id. This will deallocate the + // context. If this is the active context, the environment will switch to + // the default context (0) first. You cannot release the default context. + void ReleaseContextId(int context_id); + + // Obtain the environment's EGL context. + const EGLContext& context() const { + return contexts_.find(context_id_)->second; + } + + // Working with the Display //////////////////////////////////////////////// + + // Obtain the environment's EGL display. + const EGLDisplay& display() const { + return display_; + } + + // Inspecting the environment ////////////////////////////////////////////// + // Returns true if the environment is active in the current thread. + bool IsActive() const; + + // Returns true if the environment's context is active in the curent thread. + bool IsContextActive() const; + + // Returns true if there is any EGL context active in the current thread. + // This need not be a context created by a GLEnv instance. + static bool IsAnyContextActive(); + + // Attaching GL objects //////////////////////////////////////////////////// + + // Attach a shader to the environment. The environment takes ownership of + // the shader. + void AttachShader(int key, ShaderProgram* shader); + + // Attach a vertex frame to the environment. The environment takes ownership + // of the frame. + void AttachVertexFrame(int key, VertexFrame* frame); + + // Return the shader with the specified key, or NULL if there is no such + // shader attached to this environment. + ShaderProgram* ShaderWithKey(int key); + + // Return the vertex frame with the specified key, or NULL if there is no + // such frame attached to this environment. + VertexFrame* VertexFrameWithKey(int key); + + // Static methods ////////////////////////////////////////////////////////// + // These operate on the currently active environment! + + // Checks if the current environment is in a GL error state. If so, it will + // output an error message referencing the given operation string. Returns + // true if there was at least one error. + static bool CheckGLError(const std::string& operation); + + // Checks if the current environment is in an EGL error state. If so, it + // will output an error message referencing the given operation string. + // Returns true if there was at least one error. + static bool CheckEGLError(const std::string& operation); + + // Get the currently used (shader) program. + static GLuint GetCurrentProgram(); + + // Get the currently active display. + static EGLDisplay GetCurrentDisplay(); + + // Returns the number of components for a given GL type. For instance, + // returns 4 for vec4, and 16 for mat4. + static int NumberOfComponents(GLenum type); + + private: + typedef std::pair<EGLSurface, WindowHandle*> SurfaceWindowPair; + + // Initializes a new GL environment. + bool Init(); + + // Returns true if one of the Inits has been called successfully on this + // instance. + bool IsInitialized() const; + + // Outputs error messages specific to the operation eglMakeCurrent(). + // Returns true if there was at least one error. + static bool CheckEGLMakeCurrentError(); + + // The EGL display, contexts, and surfaces. + EGLDisplay display_; + std::map<int, EGLContext> contexts_; + std::map<int, SurfaceWindowPair> surfaces_; + + // The currently active context and surface ids. + int context_id_; + int surface_id_; + + // Dummy surface for context + sp<ANativeWindow> window_; + + // Dummy SurfaceTexture for context + sp<SurfaceTexture> surfaceTexture_; + + // The maximum surface id used. + int max_surface_id_; + + // These bools keep track of which objects this GLEnv has created (and + // owns). + bool created_context_; + bool created_surface_; + bool initialized_; + + // Attachments that GL objects can add to the environment. + std::map<int, ShaderProgram*> attached_shaders_; + std::map<int, VertexFrame*> attached_vframes_; + + DISALLOW_COPY_AND_ASSIGN(GLEnv); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_ENV_H diff --git a/media/mca/filterfw/native/core/gl_frame.cpp b/media/mca/filterfw/native/core/gl_frame.cpp new file mode 100644 index 0000000..0f8b4a1 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_frame.cpp @@ -0,0 +1,467 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/gl_env.h" +#include "core/gl_frame.h" +#include "core/shader_program.h" + +#include <vector> + +namespace android { +namespace filterfw { + +static const int kIdentityShaderKey = 1; + +// +// A GLFrame stores pixel data on the GPU. It uses two kinds of GL data +// containers for this: Textures and Frame Buffer Objects (FBOs). Textures are +// used whenever pixel data needs to be read into a shader or the host program, +// and when pixel data is uploaded to a GLFrame. The FBO is used as a rendering +// target for shaders. +// + +GLFrame::GLFrame(GLEnv* gl_env) + : gl_env_(gl_env), + width_(0), + height_(0), + vp_x_(0), + vp_y_(0), + vp_width_(0), + vp_height_(0), + texture_id_(0), + fbo_id_(0), + texture_target_(GL_TEXTURE_2D), + texture_state_(kStateUninitialized), + fbo_state_(kStateUninitialized), + owns_texture_(false), + owns_fbo_(false) { + SetDefaultTexParameters(); +} + +bool GLFrame::Init(int width, int height) { + // Make sure we haven't been initialized already + if (width_ == 0 && height_ == 0) { + InitDimensions(width, height); + return true; + } + return false; +} + +bool GLFrame::InitWithTexture(GLint texture_id, int width, int height) { + texture_id_ = texture_id; + texture_state_ = glIsTexture(texture_id) ? kStateComplete : kStateGenerated; + InitDimensions(width, height); + return true; +} + +bool GLFrame::InitWithFbo(GLint fbo_id, int width, int height) { + fbo_id_ = fbo_id; + fbo_state_ = glIsFramebuffer(fbo_id) ? kStateComplete : kStateGenerated; + texture_state_ = kStateUnmanaged; + InitDimensions(width, height); + return true; +} + +bool GLFrame::InitWithExternalTexture() { + texture_target_ = GL_TEXTURE_EXTERNAL_OES; + width_ = 0; + height_ = 0; + return GenerateTextureName(); +} + +void GLFrame::InitDimensions(int width, int height) { + width_ = width; + height_ = height; + vp_width_ = width; + vp_height_ = height; +} + +GLFrame::~GLFrame() { + // Delete texture + if (owns_texture_) { + // Bind FBO so that texture is unbound from it during deletion + if (fbo_state_ == kStateComplete) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + } + glDeleteTextures(1, &texture_id_); + } + + // Delete FBO + if (owns_fbo_) { + glDeleteFramebuffers(1, &fbo_id_); + } +} + +bool GLFrame::GenerateMipMap() { + if (FocusTexture()) { + glGenerateMipmap(GL_TEXTURE_2D); + return !GLEnv::CheckGLError("Generating MipMap!"); + } + return false; +} + +bool GLFrame::SetTextureParameter(GLenum pname, GLint value) { + if (value != tex_params_[pname]) { + if (FocusTexture()) { + glTexParameteri(GL_TEXTURE_2D, pname, value); + if (!GLEnv::CheckGLError("Setting texture parameter!")) { + tex_params_[pname] = value; + return true; + } + } + } else { + return true; + } + return false; +} + +bool GLFrame::UpdateTexParameters() { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]); + return !GLEnv::CheckGLError("Resetting texture parameters!"); +} + +bool GLFrame::TexParametersModifed() { + return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR + || tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR + || tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE + || tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE; +} + +void GLFrame::SetDefaultTexParameters() { + tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR; + tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR; + tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE; + tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE; +} + +bool GLFrame::ResetTexParameters() { + if (TexParametersModifed()) { + if (BindTexture()) { + SetDefaultTexParameters(); + return UpdateTexParameters(); + } + return false; + } + return true; +} + +bool GLFrame::CopyDataTo(uint8_t* buffer, int size) { + return (size >= Size()) + ? CopyPixelsTo(buffer) + : false; +} + +bool GLFrame::CopyPixelsTo(uint8_t* buffer) { + // Use one of the pixel reading methods below, ordered from most + // efficient to least efficient. + if (fbo_state_ == kStateComplete) + return ReadFboPixels(buffer); + else if (texture_state_ == kStateComplete) + return ReadTexturePixels(buffer); + else + return false; +} + +bool GLFrame::WriteData(const uint8_t* data, int data_size) { + return (data_size == Size()) ? UploadTexturePixels(data) : false; +} + +bool GLFrame::SetViewport(int x, int y, int width, int height) { + vp_x_ = x; + vp_y_ = y; + vp_width_ = width; + vp_height_ = height; + return true; +} + +GLFrame* GLFrame::Clone() const { + GLFrame* target = new GLFrame(gl_env_); + target->Init(width_, height_); + target->CopyPixelsFrom(this); + return target; +} + +bool GLFrame::CopyPixelsFrom(const GLFrame* frame) { + if (frame == this) { + return true; + } else if (frame && frame->width_ == width_ && frame->height_ == height_) { + std::vector<const GLFrame*> sources; + sources.push_back(frame); + GetIdentity()->Process(sources, this); + return true; + } + return false; +} + +int GLFrame::Size() const { + return width_ * height_ * 4; +} + +ShaderProgram* GLFrame::GetIdentity() const { + ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey); + if (!stored_shader) { + stored_shader = ShaderProgram::CreateIdentity(gl_env_); + gl_env_->AttachShader(kIdentityShaderKey, stored_shader); + } + return stored_shader; +} + +bool GLFrame::BindFrameBuffer() const { + // Bind the FBO + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + if (GLEnv::CheckGLError("FBO Binding")) return false; + + // Set viewport + glViewport(vp_x_, vp_y_, vp_width_, vp_height_); + if (GLEnv::CheckGLError("ViewPort Setup")) return false; + + return true; +} + +bool GLFrame::FocusFrameBuffer() { + // Create texture backing if necessary + if (texture_state_ == kStateUninitialized) { + if (!GenerateTextureName()) + return false; + } + + // Create and bind FBO to texture if necessary + if (fbo_state_ != kStateComplete) { + if (!GenerateFboName() || !AttachTextureToFbo()) + return false; + } + + // And bind it. + return BindFrameBuffer(); +} + +bool GLFrame::BindTexture() const { + glBindTexture(GL_TEXTURE_2D, texture_id_); + return !GLEnv::CheckGLError("Texture Binding"); +} + +GLuint GLFrame::GetTextureId() const { + return texture_id_; +} + +// Returns the held FBO id. Only call this if the GLFrame holds an FBO. You +// can check this by calling HoldsFbo(). +GLuint GLFrame::GetFboId() const { + return fbo_id_; +} + +bool GLFrame::FocusTexture() { + // Make sure we have a texture + if (!GenerateTextureName()) + return false; + + // Bind the texture + if (!BindTexture()) + return false; + + return !GLEnv::CheckGLError("Texture Binding"); +} + +bool GLFrame::GenerateTextureName() { + if (texture_state_ == kStateUninitialized) { + // Make sure texture not in use already + if (glIsTexture(texture_id_)) { + ALOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_); + return false; + } + + // Generate the texture + glGenTextures (1, &texture_id_); + if (GLEnv::CheckGLError("Texture Generation")) + return false; + texture_state_ = kStateGenerated; + owns_texture_ = true; + } + + return true; +} + +bool GLFrame::AllocateTexture() { + // Allocate or re-allocate (if texture was deleted externally). + if (texture_state_ == kStateGenerated || TextureWasDeleted()) { + LOG_FRAME("GLFrame: Allocating texture: %d", texture_id_); + glBindTexture(GL_TEXTURE_2D, texture_id_); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + width_, + height_, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + NULL); + if (!GLEnv::CheckGLError("Texture Allocation")) { + UpdateTexParameters(); + texture_state_ = kStateComplete; + } + } + return texture_state_ == kStateComplete; +} + +bool GLFrame::TextureWasDeleted() const { + return texture_state_ == kStateComplete && !glIsTexture(texture_id_); +} + +bool GLFrame::GenerateFboName() { + if (fbo_state_ == kStateUninitialized) { + // Make sure FBO not in use already + if (glIsFramebuffer(fbo_id_)) { + ALOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_); + return false; + } + + // Create FBO + glGenFramebuffers(1, &fbo_id_); + if (GLEnv::CheckGLError("FBO Generation")) + return false; + fbo_state_ = kStateGenerated; + owns_fbo_ = true; + } + + return true; +} + +bool GLFrame::ReadFboPixels(uint8_t* pixels) const { + if (fbo_state_ == kStateComplete) { + BindFrameBuffer(); + glReadPixels(0, + 0, + width_, + height_, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixels); + return !GLEnv::CheckGLError("FBO Pixel Readout"); + } + return false; +} + +bool GLFrame::ReadTexturePixels(uint8_t* pixels) const { + // Read pixels from texture if we do not have an FBO + // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture + // data. The only way for us to get texture data is to create a new FBO and + // render the current texture frame into it. As this is quite inefficient, + // and unnecessary (this can only happen if the user is reading out data + // that was just set, and not run through a filter), we warn the user about + // this here. + ALOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly " + "inefficient. Please consider using your original pixel buffer " + "instead!"); + + // Create source frame set (unfortunately this requires an ugly const-cast, + // as we need to wrap ourselves in a frame-set. Still, as this set is used + // as input only, we are certain we will not be modified). + std::vector<const GLFrame*> sources; + sources.push_back(this); + + // Create target frame + GLFrame target(gl_env_); + target.Init(width_, height_); + + // Render the texture to the target + GetIdentity()->Process(sources, &target); + + // Get the pixel data + return target.ReadFboPixels(pixels); +} + +bool GLFrame::AttachTextureToFbo() { + // Check FBO and texture state. We do not do anything if we are not managing the texture. + if (fbo_state_ == kStateComplete || texture_state_ == kStateUnmanaged) { + return true; + } else if (fbo_state_ != kStateGenerated) { + ALOGE("Attempting to attach texture to FBO with no FBO in place!"); + return false; + } + + // If texture has been generated, make sure it is allocated. + if (!AllocateTexture()) + return false; + + // Bind the frame buffer, and check if we it is already bound + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + + // Bind the texture to the frame buffer + LOG_FRAME("Attaching tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_); + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + texture_id_, + 0); + + // Cleanup + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (GLEnv::CheckGLError("Texture Binding to FBO")) + return false; + else + fbo_state_ = kStateComplete; + + return true; +} + +bool GLFrame::ReattachTextureToFbo() { + return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true; +} + +bool GLFrame::DetachTextureFromFbo() { + if (fbo_state_ == kStateComplete && texture_state_ == kStateComplete) { + LOG_FRAME("Detaching tex %d w %d h %d from fbo %d", texture_id_, width_, height_, fbo_id_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + 0, + 0); + if (GLEnv::CheckGLError("Detaching texture to FBO")) + return false; + else + fbo_state_ = kStateGenerated; + } + return true; +} + +bool GLFrame::UploadTexturePixels(const uint8_t* pixels) { + // Bind the texture object + FocusTexture(); + + // Load mipmap level 0 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, + 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Set the user specified texture parameters + UpdateTexParameters(); + + if (GLEnv::CheckGLError("Texture Pixel Upload")) + return false; + + texture_state_ = kStateComplete; + return true; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/gl_frame.h b/media/mca/filterfw/native/core/gl_frame.h new file mode 100644 index 0000000..f2a1ad5 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_frame.h @@ -0,0 +1,217 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_FRAME_H +#define ANDROID_FILTERFW_CORE_GL_FRAME_H + +#include <map> + +#include <GLES2/gl2.h> + +#include "core/gl_buffer_interface.h" + +namespace android { +namespace filterfw { + +class GLEnv; +class ShaderProgram; + +// A GLFrame stores pixel data on the GPU. While pixel data may be uploaded to +// a GLFrame and also read out of a GLFrame (access in place is not supported), +// it is strongly recommended to use ShaderProgram objects for any kind of +// processing from one GLFrame to another. +class GLFrame : public GLBufferHandle { + public: + // Create an empty GL frame in the specified GL environment. Note, that the GLFrame does NOT + // take ownership. The caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + GLFrame(GLEnv* gl_env); + + // Deallocate a GL frame. + ~GLFrame(); + + // Initialize a GL frame to the given width, height, format. Also specify + // whether this is a read-only GL frame or not. + bool Init(int width, int height); + + // Initialize as using an external texture. + bool InitWithExternalTexture(); + + // Initialize using an existing texture. + bool InitWithTexture(GLint texture_id, int width, int height); + + // Initialize using an existing FBO. + bool InitWithFbo(GLint fbo_id, int width, int height); + + // Write the data with the given size in bytes to the frame. The frame size must match the + // size of the data. + bool WriteData(const uint8_t* data, int size); + + // Copies the frame data to the given buffer. + bool CopyDataTo(uint8_t* buffer, int size); + + // Copies the pixels from another GL frame to this frame. + bool CopyPixelsFrom(const GLFrame* frame); + + // Returns the size of the buffer in bytes. + int Size() const; + + // Clone the current frame by creating a new GL frame and copying all data to it. + GLFrame* Clone() const; + + // Returns the held texture id. Only call this if the GLFrame holds a + // texture. You can check this by calling HoldsTexture(). + // Note, that a texture is created only when needed. If you are creating a + // new GLFrame, and you need it to be bound to a texture, upload (zeroed) + // data to it first, before calling this method. + GLuint GetTextureId() const; + + // Returns the held FBO id. Only call this if the GLFrame holds an FBO. You + // can check this by calling HoldsFbo(). + GLuint GetFboId() const; + + // Returns the texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES. + GLuint GetTextureTarget() const { + return texture_target_; + } + + // Set the viewport that will be used when focusing this frame for rendering. Defaults to + // the dimensions of the frame. + bool SetViewport(int x, int y, int width, int height); + + // Binds the held texture. This may result in creating the texture if it + // is not yet available. + bool FocusTexture(); + + // Binds the held FBO. This may result in creating the FBO if it + // is not yet available. + bool FocusFrameBuffer(); + + // Generates the mipmap chain of the held texture. Returns true, iff + // generating was successful. + bool GenerateMipMap(); + + // Set a texture parameter (see glTextureParameter documentation). Returns + // true iff the parameter was set successfully. + bool SetTextureParameter(GLenum pname, GLint value); + + // Reset any modifed texture parameters. + bool ResetTexParameters(); + + // Detaches the internal texture from the FBO. + bool DetachTextureFromFbo(); + + // Reattaches the internal texture to the FBO after detachment. + bool ReattachTextureToFbo(); + + private: + // Type to keep track of texture and FBO states + enum GLObjectState { + kStateUnmanaged, // We do not manage this object (externally managed) + kStateUninitialized, // Not yet initialized + kStateGenerated, // Tex/FBO id is generated + kStateComplete // FBO has valid attachment / Tex has valid pixel data + }; + + // Sets the frame and viewport dimensions. + void InitDimensions(int width, int height); + + // Generates the internal texture name. + bool GenerateTextureName(); + + // Allocates the internal texture. + bool AllocateTexture(); + + // Creates the internal FBO. + bool GenerateFboName(); + + // Copies pixels from texture or FBO to the specified buffer. + bool CopyPixelsTo(uint8_t* buffer); + + // Reads the pixels from the internal texture to the given buffer. + bool ReadTexturePixels(uint8_t* pixels) const; + + // Reads the pixels from the internal FBO to the given buffer. + bool ReadFboPixels(uint8_t* pixels) const; + + // Writes the specified pixels to the internal texture. + bool UploadTexturePixels(const uint8_t* pixels); + + // Binds the internal texture. + bool BindTexture() const; + + // Binds the internal FBO. + bool BindFrameBuffer() const; + + // Attaches the internal texture to the internal FBO. + bool AttachTextureToFbo(); + + // Update the texture parameters to the user specified parameters + bool UpdateTexParameters(); + + // Returns true if the current texture parameters are not the GLES2 + // default parameters. + bool TexParametersModifed(); + + // Sets the current texture parameters to the GLES2 default + // parameters. This still requires a call to UpdateTexParameters() + // for the changes to take effect. + void SetDefaultTexParameters(); + + // Returns true if the texture we assume to be allocated has been + // deleted externally. In this case we assume the texture name is + // still valid (otherwise we were provided with a bad texture id). + bool TextureWasDeleted() const; + + // Get the (cached) identity shader. + ShaderProgram* GetIdentity() const; + + // The GL environment this frame belongs to + GLEnv* gl_env_; + + // The width, height and format of the frame + int width_; + int height_; + + // The viewport dimensions + int vp_x_; + int vp_y_; + int vp_width_; + int vp_height_; + + // The texture and FBO ids + GLuint texture_id_; + GLuint fbo_id_; + + // The texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES + GLuint texture_target_; + + // Flags whether or not frame holds a texture and FBO + GLObjectState texture_state_; + GLObjectState fbo_state_; + + // Set of current texture parameters + std::map<GLenum, GLint> tex_params_; + + // Flag whether frame owns the texture and FBO + bool owns_texture_; + bool owns_fbo_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_FRAME_H diff --git a/media/mca/filterfw/native/core/native_frame.cpp b/media/mca/filterfw/native/core/native_frame.cpp new file mode 100644 index 0000000..957ecb1 --- /dev/null +++ b/media/mca/filterfw/native/core/native_frame.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#include "core/native_frame.h" + +namespace android { +namespace filterfw { + +NativeFrame::NativeFrame(int size) : data_(NULL), size_(size), capacity_(size) { + data_ = capacity_ == 0 ? NULL : new uint8_t[capacity_]; +} + +NativeFrame::~NativeFrame() { + delete[] data_; +} + +bool NativeFrame::WriteData(const uint8_t* data, int offset, int size) { + if (size_ >= (offset + size)) { + memcpy(data_ + offset, data, size); + return true; + } + return false; +} + +bool NativeFrame::SetData(uint8_t* data, int size) { + delete[] data_; + size_ = capacity_ = size; + data_ = data; + return true; +} + +NativeFrame* NativeFrame::Clone() const { + NativeFrame* result = new NativeFrame(size_); + if (data_) + result->WriteData(data_, 0, size_); + return result; +} + +bool NativeFrame::Resize(int newSize) { + if (newSize <= capacity_ && newSize >= 0) { + size_ = newSize; + return true; + } + return false; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/native_frame.h b/media/mca/filterfw/native/core/native_frame.h new file mode 100644 index 0000000..0d335b3 --- /dev/null +++ b/media/mca/filterfw/native/core/native_frame.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_NATIVE_FRAME_H +#define ANDROID_FILTERFW_CORE_NATIVE_FRAME_H + +#include "base/utilities.h" + +namespace android { +namespace filterfw { + +// A NativeFrame stores data in a memory buffer (on the heap). It is used for +// data processing on the CPU. +class NativeFrame { + public: + // Create an empty native frame. + NativeFrame(int size); + + ~NativeFrame(); + + // Set the frame data and size in bytes. The NativeFrame object takes ownership of the data. + // To copy data into an existing frame, use WriteData(). + bool SetData(uint8_t* data, int size); + + // Write the specified data of the given size to the frame at the specified offset. The + // receiver must be large enough to hold the data. + bool WriteData(const uint8_t* data, int offset, int size); + + // Returns a pointer to the data, or NULL if no data was set. + const uint8_t* Data() const { + return data_; + } + + // Returns a non-const pointer to the data, or NULL if no data was set. + uint8_t* MutableData() { + return data_; + } + + // Resize the frame. You can only resize to a size that fits within the frame's capacity. + // Returns true if the resize was successful. + bool Resize(int newSize); + + // Returns the size of the frame in bytes. + int Size() { + return size_; + } + + // Returns the capacity of the frame in bytes. + int Capacity() { + return capacity_; + } + + // Returns a new native frame + NativeFrame* Clone() const; + + private: + // Pointer to the data. Owned by the frame. + uint8_t* data_; + + // Size of data buffer in bytes. + int size_; + + // Capacity of data buffer in bytes. + int capacity_; + + DISALLOW_COPY_AND_ASSIGN(NativeFrame); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_NATIVE_FRAME_H diff --git a/media/mca/filterfw/native/core/native_program.cpp b/media/mca/filterfw/native/core/native_program.cpp new file mode 100644 index 0000000..c46c46f --- /dev/null +++ b/media/mca/filterfw/native/core/native_program.cpp @@ -0,0 +1,162 @@ +/* + * 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. + */ + +#include <dlfcn.h> + +#include "base/logging.h" +#include "core/native_frame.h" +#include "core/native_program.h" + +#include <string> +#include <vector> + +namespace android { +namespace filterfw { + +NativeProgram::NativeProgram() + : lib_handle_(NULL), + init_function_(NULL), + setvalue_function_(NULL), + getvalue_function_(NULL), + process_function_(NULL), + reset_function_(NULL), + teardown_function_(NULL), + user_data_(NULL) { +} + +NativeProgram::~NativeProgram() { + if (lib_handle_) + dlclose(lib_handle_); +} + +bool NativeProgram::OpenLibrary(const std::string& lib_name) { + if (!lib_handle_) { + lib_handle_ = dlopen(lib_name.c_str(), RTLD_NOW); + if (!lib_handle_) { + ALOGE("NativeProgram: Error opening library: '%s': %s", lib_name.c_str(), dlerror()); + return false; + } + return true; + } + return false; +} + +bool NativeProgram::BindProcessFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + process_function_ = reinterpret_cast<ProcessFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + if (!process_function_) { + ALOGE("NativeProgram: Could not find process function symbol: '%s'!", func_name.c_str()); + return false; + } + return true; +} + +bool NativeProgram::BindInitFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + init_function_ = reinterpret_cast<InitFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return init_function_ != NULL; +} + +bool NativeProgram::BindSetValueFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + setvalue_function_ = reinterpret_cast<SetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return setvalue_function_ != NULL; +} + +bool NativeProgram::BindGetValueFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + getvalue_function_ = reinterpret_cast<GetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return getvalue_function_ != NULL; +} + +bool NativeProgram::BindResetFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + reset_function_ = reinterpret_cast<ResetFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return reset_function_ != NULL; +} + +bool NativeProgram::BindTeardownFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + teardown_function_ = reinterpret_cast<TeardownFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return teardown_function_ != NULL; +} + +bool NativeProgram::CallProcess(const std::vector<const char*>& inputs, + const std::vector<int>& input_sizes, + char* output, + int output_size) { + if (process_function_) { + return process_function_(const_cast<const char**>(&inputs[0]), + &input_sizes[0], + inputs.size(), + output, + output_size, + user_data_) == 1; + } + return false; +} + +bool NativeProgram::CallInit() { + if (init_function_) { + init_function_(&user_data_); + return true; + } + return false; +} + +bool NativeProgram::CallSetValue(const std::string& key, const std::string& value) { + if (setvalue_function_) { + setvalue_function_(key.c_str(), value.c_str(), user_data_); + return true; + } + return false; +} + +std::string NativeProgram::CallGetValue(const std::string& key) { + if (getvalue_function_) { + static const int buffer_size = 1024; + char result[buffer_size]; + result[buffer_size - 1] = '\0'; + getvalue_function_(key.c_str(), result, buffer_size, user_data_); + return std::string(result); + } + return std::string(); +} + +bool NativeProgram::CallReset() { + if (reset_function_) { + reset_function_(user_data_); + return true; + } + return false; +} + +bool NativeProgram::CallTeardown() { + if (teardown_function_) { + teardown_function_(user_data_); + return true; + } + return false; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/native_program.h b/media/mca/filterfw/native/core/native_program.h new file mode 100644 index 0000000..ce704af --- /dev/null +++ b/media/mca/filterfw/native/core/native_program.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H +#define ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H + +#include <vector> +#include <string> + +#include "base/utilities.h" + +namespace android { +namespace filterfw { + +class NativeFrame; + +typedef void (*InitFunctionPtr)(void**); +typedef void (*SetValueFunctionPtr)(const char*, const char*, void*); +typedef void (*GetValueFunctionPtr)(const char*, char*, int, void*); +typedef int (*ProcessFunctionPtr)(const char**, const int*, int, char*, int, void*); +typedef void (*ResetFunctionPtr)(void*); +typedef void (*TeardownFunctionPtr)(void*); + +class NativeProgram { + public: + // Create an empty native frame. + NativeProgram(); + + ~NativeProgram(); + + bool OpenLibrary(const std::string& lib_name); + + bool BindInitFunction(const std::string& func_name); + bool BindSetValueFunction(const std::string& func_name); + bool BindGetValueFunction(const std::string& func_name); + bool BindProcessFunction(const std::string& func_name); + bool BindResetFunction(const std::string& func_name); + bool BindTeardownFunction(const std::string& func_name); + + bool CallInit(); + bool CallSetValue(const std::string& key, const std::string& value); + std::string CallGetValue(const std::string& key); + bool CallProcess(const std::vector<const char*>& inputs, + const std::vector<int>& input_sizes, + char* output, + int output_size); + bool CallReset(); + bool CallTeardown(); + + private: + // Pointer to the data. Owned by the frame. + void* lib_handle_; + + // The function pointers to the native function implementations. + InitFunctionPtr init_function_; + SetValueFunctionPtr setvalue_function_; + GetValueFunctionPtr getvalue_function_; + ProcessFunctionPtr process_function_; + ResetFunctionPtr reset_function_; + TeardownFunctionPtr teardown_function_; + + // Pointer to user data + void* user_data_; + + DISALLOW_COPY_AND_ASSIGN(NativeProgram); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H diff --git a/media/mca/filterfw/native/core/shader_program.cpp b/media/mca/filterfw/native/core/shader_program.cpp new file mode 100644 index 0000000..d92eb31 --- /dev/null +++ b/media/mca/filterfw/native/core/shader_program.cpp @@ -0,0 +1,1122 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/geometry.h" +#include "core/gl_buffer_interface.h" +#include "core/gl_env.h" +#include "core/gl_frame.h" +#include "core/shader_program.h" +#include "core/vertex_frame.h" + +#include <string> +#include <sstream> +#include <vector> + +namespace android { +namespace filterfw { + +// VBO attachment keys +static const int kDefaultVboKey = 1; + +static const char* s_default_vertex_shader_source_ = + "attribute vec4 a_position;\n" + "attribute vec2 a_texcoord;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_Position = a_position;\n" + " v_texcoord = a_texcoord;\n" + "}\n"; + +// Helper Functions //////////////////////////////////////////////////////////// +// Maps coordinates x,y in the unit rectangle over to the quadrangle specified +// by the four points in b. The result coordinates are written to xt and yt. +static void GetTileCoords(const float* b, float x, float y, float* xt, float* yt) { + const float w0 = (1.0f - x) * (1.0f - y); + const float w1 = x * (1.0f - y); + const float w2 = (1.0f - x) * y; + const float w3 = x * y; + + *xt = w0 * b[0] + w1 * b[2] + w2 * b[4] + w3 * b[6]; + *yt = w0 * b[1] + w1 * b[3] + w2 * b[5] + w3 * b[7]; +} + +static inline float AdjustRatio(float current, float next) { + return (current + next) / 2.0; +} + +// VertexAttrib implementation ///////////////////////////////////////////////// +ShaderProgram::VertexAttrib::VertexAttrib() + : is_const(true), + index(-1), + normalized(false), + stride(0), + components(0), + offset(0), + type(GL_FLOAT), + vbo(0), + values(NULL), + owned_data(NULL) { +} + +// ShaderProgram implementation //////////////////////////////////////////////// +ShaderProgram::ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader) + : fragment_shader_source_(fragment_shader), + vertex_shader_source_(s_default_vertex_shader_source_), + fragment_shader_(0), + vertex_shader_(0), + program_(0), + gl_env_(gl_env), + base_texture_unit_(GL_TEXTURE0), + source_coords_(NULL), + target_coords_(NULL), + manage_coordinates_(false), + tile_x_count_(1), + tile_y_count_(1), + vertex_count_(4), + draw_mode_(GL_TRIANGLE_STRIP), + clears_(false), + blending_(false), + sfactor_(GL_SRC_ALPHA), + dfactor_(GL_ONE_MINUS_SRC_ALPHA) { + SetDefaultCoords(); +} + +ShaderProgram::ShaderProgram(GLEnv* gl_env, + const std::string& vertex_shader, + const std::string& fragment_shader) + : fragment_shader_source_(fragment_shader), + vertex_shader_source_(vertex_shader), + fragment_shader_(0), + vertex_shader_(0), + program_(0), + gl_env_(gl_env), + base_texture_unit_(GL_TEXTURE0), + source_coords_(NULL), + target_coords_(NULL), + manage_coordinates_(false), + tile_x_count_(1), + tile_y_count_(1), + vertex_count_(4), + draw_mode_(GL_TRIANGLE_STRIP), + clears_(false), + blending_(false), + sfactor_(GL_SRC_ALPHA), + dfactor_(GL_ONE_MINUS_SRC_ALPHA) { + SetDefaultCoords(); +} + +ShaderProgram::~ShaderProgram() { + // Delete our vertex data + delete[] source_coords_; + delete[] target_coords_; + + // Delete any owned attribute data + VertexAttribMap::const_iterator iter; + for (iter = attrib_values_.begin(); iter != attrib_values_.end(); ++iter) { + const VertexAttrib& attrib = iter->second; + if (attrib.owned_data) + delete[] attrib.owned_data; + } +} + +void ShaderProgram::SetDefaultCoords() { + if (!source_coords_) + source_coords_ = new float[8]; + if (!target_coords_) + target_coords_ = new float[8]; + + source_coords_[0] = 0.0f; + source_coords_[1] = 0.0f; + source_coords_[2] = 1.0f; + source_coords_[3] = 0.0f; + source_coords_[4] = 0.0f; + source_coords_[5] = 1.0f; + source_coords_[6] = 1.0f; + source_coords_[7] = 1.0f; + + target_coords_[0] = -1.0f; + target_coords_[1] = -1.0f; + target_coords_[2] = 1.0f; + target_coords_[3] = -1.0f; + target_coords_[4] = -1.0f; + target_coords_[5] = 1.0f; + target_coords_[6] = 1.0f; + target_coords_[7] = 1.0f; + +} + +ShaderProgram* ShaderProgram::CreateIdentity(GLEnv* gl_env) { + const char* s_id_fragment_shader = + "precision mediump float;\n" + "uniform sampler2D tex_sampler_0;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + "}\n"; + ShaderProgram* result = new ShaderProgram(gl_env, s_id_fragment_shader); + result->CompileAndLink(); + return result; +} + +bool ShaderProgram::IsVarValid(ProgramVar var) { + return var != -1; +} + +bool ShaderProgram::Process(const std::vector<const GLTextureHandle*>& input, + GLFrameBufferHandle* output) { + // TODO: This can be optimized: If the input and output are the same, as in + // the last iteration (typical of a multi-pass filter), a lot of this setup + // may be skipped. + + // Abort if program did not successfully compile and link + if (!IsExecutable()) { + ALOGE("ShaderProgram: unexecutable program!"); + return false; + } + + // Focus the FBO of the output + if (!output->FocusFrameBuffer()) { + ALOGE("Unable to focus frame buffer"); + return false; + } + + // Get all required textures + std::vector<GLuint> textures; + std::vector<GLenum> targets; + for (unsigned i = 0; i < input.size(); ++i) { + // Get the current input frame and make sure it is a GL frame + if (input[i]) { + // Get the texture bound to that frame + const GLuint tex_id = input[i]->GetTextureId(); + const GLenum target = input[i]->GetTextureTarget(); + if (tex_id == 0) { + ALOGE("ShaderProgram: invalid texture id at input: %d!", i); + return false; + } + textures.push_back(tex_id); + targets.push_back(target); + } + } + + // And render! + if (!RenderFrame(textures, targets)) { + ALOGE("Unable to render frame"); + return false; + } + return true; +} + +bool ShaderProgram::Process(const std::vector<const GLFrame*>& input, GLFrame* output) { + std::vector<const GLTextureHandle*> textures(input.size()); + std::copy(input.begin(), input.end(), textures.begin()); + return Process(textures, output); +} + +void ShaderProgram::SetSourceRect(float x, float y, float width, float height) { + Quad quad(Point(x, y), + Point(x + width, y), + Point(x, y + height), + Point(x + width, y + height)); + SetSourceRegion(quad); +} + +void ShaderProgram::SetSourceRegion(const Quad& quad) { + int index = 0; + for (int i = 0; i < 4; ++i, index += 2) { + source_coords_[index] = quad.point(i).x(); + source_coords_[index+1] = quad.point(i).y(); + } +} + +void ShaderProgram::SetTargetRect(float x, float y, float width, float height) { + Quad quad(Point(x, y), + Point(x + width, y), + Point(x, y + height), + Point(x + width, y + height)); + SetTargetRegion(quad); +} + +void ShaderProgram::SetTargetRegion(const Quad& quad) { + int index = 0; + for (int i = 0; i < 4; ++i, index += 2) { + target_coords_[index] = (quad.point(i).x() * 2.0) - 1.0; + target_coords_[index+1] = (quad.point(i).y() * 2.0) - 1.0; + } +} + +bool ShaderProgram::CompileAndLink() { + // Make sure we haven't compiled and linked already + if (vertex_shader_ != 0 || fragment_shader_ != 0 || program_ != 0) { + ALOGE("Attempting to re-compile shaders!"); + return false; + } + + // Compile vertex shader + vertex_shader_ = CompileShader(GL_VERTEX_SHADER, + vertex_shader_source_.c_str()); + if (!vertex_shader_) { + ALOGE("Shader compilation failed!"); + return false; + } + + // Compile fragment shader + fragment_shader_ = CompileShader(GL_FRAGMENT_SHADER, + fragment_shader_source_.c_str()); + if (!fragment_shader_) + return false; + + // Link + GLuint shaders[2] = { vertex_shader_, fragment_shader_ }; + program_ = LinkProgram(shaders, 2); + + // Scan for all uniforms in the program + ScanUniforms(); + + // Check if we manage all coordinates + if (program_ != 0) { + ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str()); + ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str()); + manage_coordinates_ = (tex_coord_attr >= 0 && pos_coord_attr >= 0); + } else { + ALOGE("Could not link shader program!"); + return false; + } + + return true; +} + +GLuint ShaderProgram::CompileShader(GLenum shader_type, const char* source) { + LOG_FRAME("Compiling source:\n[%s]", source); + + // Create shader + GLuint shader = glCreateShader(shader_type); + if (shader) { + // Compile source + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + + // Check for compilation errors + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + // Log the compilation error messages + ALOGE("Problem compiling shader! Source:"); + ALOGE("%s", source); + std::string src(source); + unsigned int cur_pos = 0; + unsigned int next_pos = 0; + int line_number = 1; + while ( (next_pos = src.find_first_of('\n', cur_pos)) != std::string::npos) { + ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str()); + cur_pos = next_pos + 1; + line_number++; + } + ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str()); + + GLint log_length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); + if (log_length) { + char* error_log = new char[log_length]; + if (error_log) { + glGetShaderInfoLog(shader, log_length, NULL, error_log); + ALOGE("Shader compilation error %d:\n%s\n", shader_type, error_log); + delete[] error_log; + } + } + glDeleteShader(shader); + shader = 0; + } + } + return shader; +} + +GLuint ShaderProgram::LinkProgram(GLuint* shaders, GLuint count) { + GLuint program = glCreateProgram(); + if (program) { + // Attach all compiled shaders + for (GLuint i = 0; i < count; ++i) { + glAttachShader(program, shaders[i]); + if (GLEnv::CheckGLError("glAttachShader")) return 0; + } + + // Link + glLinkProgram(program); + + // Check for linking errors + GLint linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (linked != GL_TRUE) { + // Log the linker error messages + GLint log_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + if (log_length) { + char* error_log = new char[log_length]; + if (error_log) { + glGetProgramInfoLog(program, log_length, NULL, error_log); + ALOGE("Program Linker Error:\n%s\n", error_log); + delete[] error_log; + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; +} + +void ShaderProgram::ScanUniforms() { + int uniform_count; + int buffer_size; + GLenum type; + GLint capacity; + glGetProgramiv(program_, GL_ACTIVE_UNIFORMS, &uniform_count); + glGetProgramiv(program_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &buffer_size); + std::vector<GLchar> name(buffer_size); + for (int i = 0; i < uniform_count; ++i) { + glGetActiveUniform(program_, i, buffer_size, NULL, &capacity, &type, &name[0]); + ProgramVar uniform_id = glGetUniformLocation(program_, &name[0]); + uniform_indices_[uniform_id] = i; + } +} + +bool ShaderProgram::PushCoords(ProgramVar attr, float* coords) { + // If the shader does not define these, we simply ignore the coordinates, and assume that the + // user is managing coordinates. + if (attr >= 0) { + const uint8_t* data = reinterpret_cast<const uint8_t*>(coords); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glVertexAttribPointer(attr, 2, GL_FLOAT, false, 2 * sizeof(float), data); + glEnableVertexAttribArray(attr); + return !GLEnv::CheckGLError("Pushing vertex coordinates"); + } + return true; +} + +bool ShaderProgram::PushSourceCoords(float* coords) { + ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str()); + return PushCoords(tex_coord_attr, coords); +} + +bool ShaderProgram::PushTargetCoords(float* coords) { + ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str()); + return PushCoords(pos_coord_attr, coords); +} + +std::string ShaderProgram::InputTextureUniformName(int index) { + std::stringstream tex_name; + tex_name << "tex_sampler_" << index; + return tex_name.str(); +} + +bool ShaderProgram::BindInputTextures(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets) { + for (unsigned i = 0; i < textures.size(); ++i) { + // Activate texture unit i + glActiveTexture(BaseTextureUnit() + i); + if (GLEnv::CheckGLError("Activating Texture Unit")) + return false; + + // Bind our texture + glBindTexture(targets[i], textures[i]); + LOG_FRAME("Binding texture %d", textures[i]); + if (GLEnv::CheckGLError("Binding Texture")) + return false; + + // Set the texture handle in the shader to unit i + ProgramVar tex_var = GetUniform(InputTextureUniformName(i)); + if (tex_var >= 0) { + glUniform1i(tex_var, i); + } else { + ALOGE("ShaderProgram: Shader does not seem to support %d number of " + "inputs! Missing uniform 'tex_sampler_%d'!", textures.size(), i); + return false; + } + + if (GLEnv::CheckGLError("Texture Variable Binding")) + return false; + } + + return true; +} + +bool ShaderProgram::UseProgram() { + if (GLEnv::GetCurrentProgram() != program_) { + LOG_FRAME("Using program %d", program_); + glUseProgram(program_); + return !GLEnv::CheckGLError("Use Program"); + } + return true; +} + +bool ShaderProgram::RenderFrame(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets) { + // Make sure we have enough texture units to accomodate the textures + if (textures.size() > static_cast<unsigned>(MaxTextureUnits())) { + ALOGE("ShaderProgram: Number of input textures is unsupported on this " + "platform!"); + return false; + } + + // Prepare to render + if (!BeginDraw()) { + ALOGE("ShaderProgram: couldn't initialize gl for drawing!"); + return false; + } + + // Bind input textures + if (!BindInputTextures(textures, targets)) { + ALOGE("BindInputTextures failed"); + return false; + } + + if (LOG_EVERY_FRAME) { + int fbo, program, buffer; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo); + glGetIntegerv(GL_CURRENT_PROGRAM, &program); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &buffer); + ALOGV("RenderFrame: fbo %d prog %d buff %d", fbo, program, buffer); + } + + // Render! + const bool requestTile = (tile_x_count_ != 1 || tile_y_count_ != 1); + const bool success = (!requestTile || !manage_coordinates_ || vertex_count_ != 4) + ? Draw() + : DrawTiled(); + + // Pop vertex attributes + PopAttributes(); + + return success && !GLEnv::CheckGLError("Rendering"); +} + +bool ShaderProgram::Draw() { + if (PushSourceCoords(source_coords_) && PushTargetCoords(target_coords_)) { + glDrawArrays(draw_mode_, 0, vertex_count_); + return true; + } + return false; +} + +bool ShaderProgram::DrawTiled() { + // Target coordinate step size + float s[8]; + float t[8]; + + // Step sizes + const float xs = 1.0f / static_cast<float>(tile_x_count_); + const float ys = 1.0f / static_cast<float>(tile_y_count_); + + // Tile drawing loop + for (int i = 0; i < tile_x_count_; ++i) { + for (int j = 0; j < tile_y_count_; ++j) { + // Current coordinates in unit rectangle + const float x = i / static_cast<float>(tile_x_count_); + const float y = j / static_cast<float>(tile_y_count_); + + // Source coords + GetTileCoords(source_coords_, x, y, &s[0], &s[1]); + GetTileCoords(source_coords_, x + xs, y, &s[2], &s[3]); + GetTileCoords(source_coords_, x, y + ys, &s[4], &s[5]); + GetTileCoords(source_coords_, x + xs, y + ys, &s[6], &s[7]); + + // Target coords + GetTileCoords(target_coords_, x, y, &t[0], &t[1]); + GetTileCoords(target_coords_, x + xs, y, &t[2], &t[3]); + GetTileCoords(target_coords_, x, y + ys, &t[4], &t[5]); + GetTileCoords(target_coords_, x + xs, y + ys, &t[6], &t[7]); + + if (PushSourceCoords(s) && PushTargetCoords(t)) { + glDrawArrays(draw_mode_, 0, vertex_count_); + Yield(); + } else { + return false; + } + } + } + return true; +} + +void ShaderProgram::Yield() { + glFinish(); +} + +bool ShaderProgram::BeginDraw() { + // Activate shader program + if (!UseProgram()) + return false; + + // Push vertex attributes + PushAttributes(); + + // Clear output, if requested + if (clears_) { + glClearColor(clear_color_.red, + clear_color_.green, + clear_color_.blue, + clear_color_.alpha); + glClear(GL_COLOR_BUFFER_BIT); + } + + // Enable/Disable blending + if (blending_) { + glEnable(GL_BLEND); + glBlendFunc(sfactor_, dfactor_); + } else glDisable(GL_BLEND); + + return true; +} + +int ShaderProgram::MaxVaryingCount() { + GLint result; + glGetIntegerv(GL_MAX_VARYING_VECTORS, &result); + return result; +} + +int ShaderProgram::MaxTextureUnits() { + return GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1; +} + +void ShaderProgram::SetDrawMode(GLenum mode) { + draw_mode_ = mode; +} + +void ShaderProgram::SetClearsOutput(bool clears) { + clears_ = clears; +} + +void ShaderProgram::SetClearColor(float red, float green, float blue, float alpha) { + clear_color_.red = red; + clear_color_.green = green; + clear_color_.blue = blue; + clear_color_.alpha = alpha; +} + +void ShaderProgram::SetTileCounts(int x_count, int y_count) { + tile_x_count_ = x_count; + tile_y_count_ = y_count; +} + +// Variable Value Setting Helpers ////////////////////////////////////////////// +bool ShaderProgram::CheckValueCount(const std::string& var_type, + const std::string& var_name, + int expected_count, + int components, + int value_size) { + if (expected_count != (value_size / components)) { + ALOGE("Shader Program: %s Value Error (%s): Expected value length %d " + "(%d components), but received length of %d (%d components)!", + var_type.c_str(), var_name.c_str(), + expected_count, components * expected_count, + value_size / components, value_size); + return false; + } + return true; +} + +bool ShaderProgram::CheckValueMult(const std::string& var_type, + const std::string& var_name, + int components, + int value_size) { + if (value_size % components != 0) { + ALOGE("Shader Program: %s Value Error (%s): Value must be multiple of %d, " + "but %d elements were passed!", var_type.c_str(), var_name.c_str(), + components, value_size); + return false; + } + return true; +} + +bool ShaderProgram::CheckVarValid(ProgramVar var) { + if (!IsVarValid(var)) { + ALOGE("Shader Program: Attempting to access invalid variable!"); + return false; + } + return true; +} + +// Uniforms //////////////////////////////////////////////////////////////////// +bool ShaderProgram::CheckUniformValid(ProgramVar var) { + if (!IsVarValid(var) || uniform_indices_.find(var) == uniform_indices_.end()) { + ALOGE("Shader Program: Attempting to access unknown uniform %d!", var); + return false; + } + return true; +} + +int ShaderProgram::MaxUniformCount() { + // Here we return the minimum of the max uniform count for fragment and vertex + // shaders. + GLint count1, count2; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &count1); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &count2); + return count1 < count2 ? count1 : count2; +} + +ProgramVar ShaderProgram::GetUniform(const std::string& name) const { + if (!IsExecutable()) { + ALOGE("ShaderProgram: Error: Must link program before querying uniforms!"); + return -1; + } + return glGetUniformLocation(program_, name.c_str()); +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, int value) { + if (!CheckVarValid(var)) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + glUniform1i(var, value); + return !GLEnv::CheckGLError("Set Uniform Value (int)"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, float value) { + if (!CheckVarValid(var)) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + glUniform1f(var, value); + return !GLEnv::CheckGLError("Set Uniform Value (float)"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const int* values, + int count) { + if (!CheckUniformValid(var)) + return false; + + // Make sure we have values at all + if (count == 0) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + // Get uniform information + GLint capacity; + GLenum type; + char name[128]; + glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name); + + // Make sure passed values are compatible + const int components = GLEnv::NumberOfComponents(type); + if (!CheckValueCount("Uniform (int)", name, capacity, components, count) + || !CheckValueMult ("Uniform (int)", name, components, count)) + return false; + + // Set value based on type + const int n = count / components; + switch(type) { + case GL_INT: + glUniform1iv(var, n, values); + break; + + case GL_INT_VEC2: + glUniform2iv(var, n, values); + break; + + case GL_INT_VEC3: + glUniform3iv(var, n, values); + break; + + case GL_INT_VEC4: + glUniform4iv(var, n, values); + break; + + default: + return false; + }; + return !GLEnv::CheckGLError("Set Uniform Value"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const float* values, + int count) { + if (!CheckUniformValid(var)) + return false; + + // Make sure we have values at all + if (count == 0) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + // Get uniform information + GLint capacity; + GLenum type; + char name[128]; + glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name); + + // Make sure passed values are compatible + const int components = GLEnv::NumberOfComponents(type); + if (!CheckValueCount("Uniform (float)", name, capacity, components, count) + || !CheckValueMult ("Uniform (float)", name, components, count)) + return false; + + // Set value based on type + const int n = count / components; + switch(type) { + case GL_FLOAT: + glUniform1fv(var, n, values); + break; + + case GL_FLOAT_VEC2: + glUniform2fv(var, n, values); + break; + + case GL_FLOAT_VEC3: + glUniform3fv(var, n, values); + break; + + case GL_FLOAT_VEC4: + glUniform4fv(var, n, values); + break; + + case GL_FLOAT_MAT2: + glUniformMatrix2fv(var, n, GL_FALSE, values); + break; + + case GL_FLOAT_MAT3: + glUniformMatrix3fv(var, n, GL_FALSE, values); + break; + + case GL_FLOAT_MAT4: + glUniformMatrix4fv(var, n, GL_FALSE, values); + break; + + default: + return false; + }; + return !GLEnv::CheckGLError("Set Uniform Value"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, const std::vector<int>& values) { + return SetUniformValue(var, &values[0], values.size()); +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const std::vector<float>& values) { + return SetUniformValue(var, &values[0], values.size()); +} + +bool ShaderProgram::SetUniformValue(const std::string& name, const Value& value) { + if (ValueIsFloat(value)) + return SetUniformValue(GetUniform(name), GetFloatValue(value)); + else if (ValueIsInt(value)) + return SetUniformValue(GetUniform(name), GetIntValue(value)); + else if (ValueIsFloatArray(value)) + return SetUniformValue(GetUniform(name), GetFloatArrayValue(value), GetValueCount(value)); + else if (ValueIsIntArray(value)) + return SetUniformValue(GetUniform(name), GetIntArrayValue(value), GetValueCount(value)); + else + return false; +} + +Value ShaderProgram::GetUniformValue(const std::string& name) { + ProgramVar var = GetUniform(name); + if (CheckUniformValid(var)) { + // Get uniform information + GLint capacity; + GLenum type; + glGetActiveUniform(program_, IndexOfUniform(var), 0, NULL, &capacity, &type, NULL); + if (!GLEnv::CheckGLError("Get Active Uniform")) { + // Get value based on type, and wrap in value object + switch(type) { + case GL_INT: { + int value; + glGetUniformiv(program_, var, &value); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntValue(value) + : MakeNullValue(); + } break; + + case GL_INT_VEC2: { + int value[2]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 2) + : MakeNullValue(); + } break; + + case GL_INT_VEC3: { + int value[3]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 3) + : MakeNullValue(); + } break; + + case GL_INT_VEC4: { + int value[4]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT: { + float value; + glGetUniformfv(program_, var, &value); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatValue(value) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC2: { + float value[2]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 2) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC3: { + float value[3]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 3) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC4: { + float value[4]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT2: { + float value[4]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT3: { + float value[9]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 9) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT4: { + float value[16]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 16) + : MakeNullValue(); + } break; + } + } + } + return MakeNullValue(); +} + +GLuint ShaderProgram::IndexOfUniform(ProgramVar var) { + return uniform_indices_[var]; +} + +// Attributes ////////////////////////////////////////////////////////////////////////////////////// +int ShaderProgram::MaxAttributeCount() { + GLint result; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &result); + return result; +} + +ProgramVar ShaderProgram::GetAttribute(const std::string& name) const { + if (!IsExecutable()) { + ALOGE("ShaderProgram: Error: Must link program before querying attributes!"); + return -1; + } else if (name == PositionAttributeName() || name == TexCoordAttributeName()) { + ALOGW("ShaderProgram: Attempting to overwrite internal vertex attribute '%s'!", name.c_str()); + } + return glGetAttribLocation(program_, name.c_str()); +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const VertexFrame* vbo, + GLenum type, + int components, + int stride, + int offset, + bool normalize) { + if (!CheckVarValid(var)) + return false; + + if (vbo) { + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = normalize; + attrib.stride = stride; + attrib.type = type; + attrib.vbo = vbo->GetVboId(); + attrib.offset = offset; + + return StoreAttribute(attrib); + } + return false; +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const uint8_t* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize) { + if (!CheckVarValid(var)) + return false; + + if (data) { + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = normalize; + attrib.stride = stride; + attrib.type = type; + attrib.values = data + offset; + + return StoreAttribute(attrib); + } + return false; +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const std::vector<float>& data, + int components) { + return SetAttributeValues(var, &data[0], data.size(), components); +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const float* data, + int total, + int components) { + if (!CheckVarValid(var)) + return false; + + // Make sure the passed data vector has a valid size + if (total % components != 0) { + ALOGE("ShaderProgram: Invalid attribute vector given! Specified a component " + "count of %d, but passed a non-multiple vector of size %d!", + components, total); + return false; + } + + // Copy the data to a buffer we own + float* data_cpy = new float[total]; + memcpy(data_cpy, data, sizeof(float) * total); + + // Store the attribute + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = false; + attrib.stride = components * sizeof(float); + attrib.type = GL_FLOAT; + attrib.values = data_cpy; + attrib.owned_data = data_cpy; // Marks this for deletion later on + + return StoreAttribute(attrib); +} + +bool ShaderProgram::StoreAttribute(VertexAttrib attrib) { + if (attrib.index >= 0) { + attrib_values_[attrib.index] = attrib; + return true; + } + return false; +} + +bool ShaderProgram::PushAttributes() { + for (VertexAttribMap::const_iterator iter = attrib_values_.begin(); + iter != attrib_values_.end(); + ++iter) { + const VertexAttrib& attrib = iter->second; + + if (attrib.is_const) { + // Set constant attribute values (must be specified as host values) + if (!attrib.values) + return false; + + const float* values = reinterpret_cast<const float*>(attrib.values); + switch (attrib.components) { + case 1: glVertexAttrib1fv(attrib.index, values); break; + case 2: glVertexAttrib2fv(attrib.index, values); break; + case 3: glVertexAttrib3fv(attrib.index, values); break; + case 4: glVertexAttrib4fv(attrib.index, values); break; + default: return false; + } + glDisableVertexAttribArray(attrib.index); + } else { + // Set per-vertex values + if (attrib.values) { + // Make sure no buffer is bound and set attribute + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glVertexAttribPointer(attrib.index, + attrib.components, + attrib.type, + attrib.normalized, + attrib.stride, + attrib.values); + } else if (attrib.vbo) { + // Bind VBO and set attribute + glBindBuffer(GL_ARRAY_BUFFER, attrib.vbo); + + glVertexAttribPointer(attrib.index, + attrib.components, + attrib.type, + attrib.normalized, + attrib.stride, + reinterpret_cast<const void*>(attrib.offset)); + } else { + return false; + } + glEnableVertexAttribArray(attrib.index); + } + + // Make sure everything worked + if (GLEnv::CheckGLError("Pushing Vertex Attributes")) + return false; + } + + return true; +} + +bool ShaderProgram::PopAttributes() { + // Disable vertex attributes + for (VertexAttribMap::const_iterator iter = attrib_values_.begin(); + iter != attrib_values_.end(); + ++iter) { + glDisableVertexAttribArray(iter->second.index); + } + // Unbind buffer: Very important as this greatly affects what glVertexAttribPointer does! + glBindBuffer(GL_ARRAY_BUFFER, 0); + return !GLEnv::CheckGLError("Popping Vertex Attributes"); +} + +void ShaderProgram::SetVertexCount(int count) { + vertex_count_ = count; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/shader_program.h b/media/mca/filterfw/native/core/shader_program.h new file mode 100644 index 0000000..2063175 --- /dev/null +++ b/media/mca/filterfw/native/core/shader_program.h @@ -0,0 +1,553 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H +#define ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H + +#include <vector> +#include <map> +#include <string> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <EGL/egl.h> + +#include "core/gl_env.h" +#include "core/value.h" + +namespace android { +namespace filterfw { + +class GLFrame; +class GLFrameBufferHandle; +class GLTextureHandle; +class Quad; +class VertexFrame; + +typedef GLint ProgramVar; + +// A ShaderProgram is a Program object that holds a GLSL shader implementation. +// It provides functionality for compiling, linking, and executing the shader. +// On top of that, it provides access to the shaders source code, uniforms, +// attributes, and other properties. +// By default a ShaderProgram provides its own vertex shader. However, a custom +// vertex shader may be passed and used instead. +// When implementing a vertex shader, the following attribute names have special +// meaning: +// +// - a_position: The vertex position +// - a_texcoord: The texture cooridnates +// +// The shader program will bind these attributes to the correct values, if they +// are present in the vertex shader source code. +// +// When implementing the fragment shader, the following variable names must be +// defined: +// +// - tex_sampler_<n>: The n'th input texture. For instance, use tex_sampler_0 +// for the first input texture. Must be a uniform sampler2D. +// - v_texcoord: The current texture coordinate. +// +// If more input textures are given than the shader can handle, this will result +// in an error. +// +class ShaderProgram { + public: + // General Functionality /////////////////////////////////////////////////// + // Create a new shader program with the given fragment shader source code. + // A default vertex shader is used, which renders the input texture to a + // rectangular region of the output texture. You can modify the input and + // output regions by using the SetSourceRegion(...) and SetTargetRegion(...) + // (and related) functions below. + // This program will not be executable until you have compiled and linked + // it. + // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The + // caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + explicit ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader); + + // Create a new shader program with the given fragment and vertex shader + // source code. This program will not be executable until you have compiled + // and linked it. + // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The + // caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + ShaderProgram(GLEnv* gl_env, + const std::string& vertex_shader, + const std::string& fragment_shader); + + // Destructor. + ~ShaderProgram(); + + // Process the given input frames and write the result to the output frame. + // Returns false if there was an error processing. + bool Process(const std::vector<const GLFrame*>& inputs, GLFrame* output); + + // Same as above, but pass GL interfaces rather than frame objects. Use this + // only if you are not working on Frame objects, but rather directly on GL + // textures and FBOs. + bool Process(const std::vector<const GLTextureHandle*>& input, + GLFrameBufferHandle* output); + + // Compile and link the shader source code. Returns true if compilation + // and linkage was successful. Compilation and linking error messages are + // written to the error log. + bool CompileAndLink(); + + // Returns true if this Program has been compiled and linked successfully. + bool IsExecutable() const { + return program_ != 0; + } + + // Returns true if the shader program variable is valid. + static bool IsVarValid(ProgramVar var); + + // Special ShaderPrograms ////////////////////////////////////////////////// + // A (compiled) shader program which assigns the sampled pixels from the + // input to the output. Note that transformations may be applied to achieve + // effects such as cropping, scaling or rotation. + // The caller takes ownership of the result! + static ShaderProgram* CreateIdentity(GLEnv* env); + + // Geometry //////////////////////////////////////////////////////////////// + // These functions modify the source and target regions used during + // rasterization. Note, that these functions will ONLY take effect if + // the default vertex shader is used, or your custom vertex shader defines + // the a_position and a_texcoord attributes. + + // Set the program to read from a subregion of the input frame, given by + // the origin (x, y) and dimensions (width, height). Values are considered + // normalized between 0.0 and 1.0. If this region exceeds the input frame + // dimensions the results are undefined. + void SetSourceRect(float x, float y, float width, float height) ; + + // Set the program to read from a subregion of the input frame, given by + // the passed Quad. Values are considered normalized between 0.0 and 1.0. + // The Quad points are expected to be in the order top-left, top-right, + // bottom-left, bottom-right. + // If this region exceeds the input frame dimensions the results are + // undefined. + void SetSourceRegion(const Quad& quad); + + // Set the program to write to a subregion of the output frame, given by + // the origin (x, y) and dimensions (width, height). Values are considered + // normalized between 0.0 and 1.0. If this region exceeds the output frame + // dimensions the image will be clipped. + void SetTargetRect(float x, float y, float width, float height); + + // Set the program to write to a subregion of the output frame, given by + // the passed Quad. Values are considered normalized between 0.0 and 1.0. + // The Quad points are expected to be in the order top-left, top-right, + // bottom-left, bottom-right. + // If this region exceeds the output frame dimensions the image will be + // clipped. + void SetTargetRegion(const Quad& quad); + + // Uniform Variable access ///////////////////////////////////////////////// + // Note: In order to get and set uniforms, the program must have been + // successfully compiled and linked. Otherwise, the getters will return an + // invalid ProgramVar variable (check with IsVarValid()). + // When setting values, the value type must be match the type of the uniform + // in the shader. For instance, a vector of 3 elements cannot be assigned to + // a vec2. Similarly, an integer value cannot be assigned to a float value. + // Such a type mismatch will result in failure to set the value (which will + // remain untouched). Check the return value of the setters to determine + // success. + + // Returns the maximum number of uniforms supported by this implementation. + static int MaxUniformCount(); + + // Returns a handle to the uniform with the given name, or invalid if no + // such uniform variable exists in the shader. + ProgramVar GetUniform(const std::string& name) const; + + // Set the specified uniform value to the given integer value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, int value); + + // Set the specified uniform value to the given float value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, float value); + + // Set the specified uniform value to the given values. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const int* values, int count); + + // Set the specified uniform value to the given values. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const float* values, int count); + + // Set the specified uniform value to the given vector value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const std::vector<int>& values); + + // Set the specified uniform value to the given vector value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const std::vector<float>& values); + + // Generic variable setter, which in the case of GL programs always attempts + // to set the value of a uniform variable with the given name. Only values + // of type float, float array (or vector), and int are supported. + bool SetUniformValue(const std::string& name, const Value& value); + + // Generic variable getter, which in the case of GL programs always attempts + // to get the value of a uniform variable with the given name. + Value GetUniformValue(const std::string& name); + + // Returns the default name of the input texture uniform variable for the + // given input index. + static std::string InputTextureUniformName(int index); + + // Attribute access //////////////////////////////////////////////////////// + // Note: In order to get and set attributes, the program must have been + // successfully compiled and linked. Otherwise, the getters will return an + // invalid ProgramVar variable (check with IsVarValid()). Constant attribute + // values must be floats. Attribute pointers must be associated with a + // specific type, which can be any of the following: + // GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FLOAT, + // GL_FIXED, GL_HALF_FLOAT_OES. + // When storing vertex data, it is recommended to use VertexFrames when + // possible as these will be kept in GPU memory, and no copying of vertex + // attributes between system and GPU memory needs to take place. + + // Returns the maximum number of attributes supported by this + // implementation. + static int MaxAttributeCount(); + + // Returns a handle to the attribute with the given name, or invalid if no + // such attribute exists in the vertex shader. + ProgramVar GetAttribute(const std::string& name) const; + + // Set an attribute value that will be constant for each vertex. Returns + // true if the assignment was successful. + bool SetConstAttributeValue(ProgramVar var, float value); + + // Set an attribute vector value that will be constant for each vertex. + // Returns true if the assignment was successful. + bool SetConstAttributeValue(ProgramVar var, const std::vector<float>& value); + + // Set attribute values that differ across vertexes, using a VertexFrame. + // This is the recommended method of specifying vertex data, that does not + // change from iteration to iteration. The parameters are as follows: + // var: The shader variable to bind the values to. + // data: The vertex frame which holds the vertex data. This may be a + // superset of the data required for this particular vertex. Use the + // offset and stride to select the correct data portion. + // type: The type of the data values. This may differ from the type of the + // shader variables. See the normalize flag on how values are + // converted. + // components: The number of components per value. Valid values are 1-4. + // stride: The delta of one element to the next in bytes. + // offset: The offset of the first element. + // normalize: True, if not float values should be normalized to the range + // 0-1, when converted to a float. + // Returns true, if the assignment was successful. + bool SetAttributeValues(ProgramVar var, + const VertexFrame* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize); + + // Set attribute values that differ across vertexes, using a data buffer. + // This is the recommended method of specifying vertex data, if your data + // changes often. Note that this data may need to be copied to GPU memory + // for each render pass. Please see above for a description of the + // parameters. + // Note: The data passed here MUST be valid until all executions of this + // Program instance have been completed! + bool SetAttributeValues(ProgramVar var, + const uint8_t* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize); + + // Convenience method for setting vertex values using a vector of floats. + // The components parameter specifies how many elements per variable should + // be assigned (The variable must be able to fit the number of components). + // It must be a value between 1 and 4. + // While this method is not as flexible as the methods above, this can be + // used when more advanced methods are not necessary. Note, that if your + // vertex data does not change, it is recommended to use a VertexFrame. + bool SetAttributeValues(ProgramVar var, + const std::vector<float>& data, + int components); + + // Same as above, but using a float pointer instead of vector. Pass the + // total number of elements in total. + bool SetAttributeValues(ProgramVar var, + const float* data, + int total, + int components); + + // By default, rendering only uses the first 4 vertices. You should only + // adjust this value if you are providing your own vertex attributes with + // a count unequal to 4. Adjust this value before calling Process(). + void SetVertexCount(int count); + + // Returns the default name of the attribute used to hold the texture + // coordinates. Use this when you need to access the texture coordinate + // attribute of the shader's default vertex shader. + static const std::string& TexCoordAttributeName() { + static std::string s_texcoord("a_texcoord"); + return s_texcoord; + } + + // Returns the default name of the attribute used to hold the output + // coordinates. Use this when you need to access the output coordinate + // attribute of the shader's default vertex shader. + static const std::string& PositionAttributeName() { + static std::string s_position("a_position"); + return s_position; + } + + // Rendering /////////////////////////////////////////////////////////////// + // Set the draw mode, which can be any of GL_POINTS, GL_LINES, + // GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, + // GL_TRIANGLE_FAN. The default is GL_TRIANGLE_STRIP. + // Warning: Do NOT change this if you are not specifying your own vertex + // data with SetAttributeValues(...). + void SetDrawMode(GLenum mode); + + // If you are doing your own drawing you should call this before beginning + // to draw. This will activate the program, push all used attributes, and + // clear the frame if requested. You do not need to call this if you are + // not doing your own GL drawing! + bool BeginDraw(); + + // Render a single frame with the given input textures. You may override + // this, if you need custom rendering behavior. However, you must take + // care of the following things when overriding: + // - Use the correct program (e.g. by calling UseProgram()). + // - Bind the given textures + // - Bind vertex attributes + // - Draw + bool RenderFrame(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets); + + // Pass true to clear the output frame before rendering. The color used + // to clear is set in SetClearColor(). + void SetClearsOutput(bool clears); + + // Set the color used to clear the output frame before rendering. You + // must activate clearing by calling SetClearsOutput(true). + void SetClearColor(float red, float green, float blue, float alpha); + + // Set the number of tiles to split rendering into. Higher tile numbers + // will affect performance negatively, but will allow other GPU threads + // to render more frequently. Defaults to 1, 1. + void SetTileCounts(int x_count, int y_count); + + // Enable or Disable Blending + // Set to true to enable, false to disable. + void SetBlendEnabled(bool enable) { + blending_ = enable; + } + + // Specify pixel arithmetic for blending + // The values of sfactor and dfactor can be: + // GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, + // GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, + // GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA_SATURATE + // Default values for blending are set to: + // sfactor = GL_SRC_ALPHA + // dfactor = GL_ONE_MINUS_SRC_ALPHA + void SetBlendFunc(int sfactor, int dfactor) { + sfactor_ = sfactor; + dfactor_ = dfactor; + } + + // Accessing the Compiled Program ////////////////////////////////////////// + // Use the compiled and linked program for rendering. You should not need + // to call this, unless you are implementing your own rendering method. + bool UseProgram(); + + // Other Properties //////////////////////////////////////////////////////// + // Returns the maximum number of varyings supported by this implementation. + static int MaxVaryingCount(); + + // Returns the maximum number of texture units supported by this + // implementation. + static int MaxTextureUnits(); + + // Lower level functionality /////////////////////////////////////////////// + // Compile the shader with the given source. The shader_type must be either + // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + static GLuint CompileShader(GLenum shader_type, const char* source); + + // Link the compiled shader objects and return the resulting program. + static GLuint LinkProgram(GLuint* shaders, GLuint count); + + // Returns the lowest texture unit that will be used to bind textures. + GLuint BaseTextureUnit() const { + return base_texture_unit_; + } + + // Sets the lowest texture unit that will be used to bind textures. The + // default value is GL_TEXTURE0. + void SetBaseTextureUnit(GLuint texture_unit) { + base_texture_unit_ = texture_unit; + } + + private: + // Structure to store vertex attribute data. + struct VertexAttrib { + bool is_const; + int index; + bool normalized; + int stride; + int components; + int offset; + GLenum type; + GLuint vbo; + const void* values; + float* owned_data; + + VertexAttrib(); + }; + typedef std::map<ProgramVar, VertexAttrib> VertexAttribMap; + + struct RGBAColor { + float red; + float green; + float blue; + float alpha; + + RGBAColor() : red(0), green(0), blue(0), alpha(1) { + } + }; + + // Scans for all uniforms in the shader and creates index -> id map. + void ScanUniforms(); + + // Returns the index of the given uniform. The caller must make sure + // that the variable id passed is valid. + GLuint IndexOfUniform(ProgramVar var); + + // Binds the given input textures. + bool BindInputTextures(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets); + + // Sets the default source and target coordinates. + void SetDefaultCoords(); + + // Pushes the specified coordinates to the shader attribute. + bool PushCoords(ProgramVar attr, float* coords); + + // Pushes the source coordinates. + bool PushSourceCoords(float* coords); + + // Pushes the target coordinates. + bool PushTargetCoords(float* coords); + + // Performs (simple) GL drawing. + bool Draw(); + + // Performs tiled GL drawing. + bool DrawTiled(); + + // Yields to other GPU threads. + void Yield(); + + // Helper method to assert that the variable value passed has the correct + // total size. + static bool CheckValueCount(const std::string& var_type, + const std::string& var_name, + int expected_count, + int components, + int value_size); + + // Helper method to assert that the variable value passed has a size, that + // is compatible with the type size (must be divisible). + static bool CheckValueMult(const std::string& var_type, + const std::string& var_name, + int components, + int value_size); + + // Checks that the variable is valid. Logs an error and returns false if + // not. + static bool CheckVarValid(ProgramVar var); + + // Returns true if the uniform specified by var is an active uniform in the + // program. + bool CheckUniformValid(ProgramVar var); + + // Store an attribute to use when rendering. + bool StoreAttribute(VertexAttrib attrib); + + // Push all assigned attributes before rendering. + bool PushAttributes(); + + // Pop all assigned attributes after rendering. + bool PopAttributes(); + + // The shader source code + std::string fragment_shader_source_; + std::string vertex_shader_source_; + + // The compiled shaders and linked program + GLuint fragment_shader_; + GLuint vertex_shader_; + GLuint program_; + + // The GL environment this shader lives in. + GLEnv* gl_env_; + + // The lowest texture unit this program will use + GLuint base_texture_unit_; + + // The current source and target coordinates to render from/to. + float* source_coords_; + float* target_coords_; + + // True, if the program has control over both source and target coordinates. + bool manage_coordinates_; + + // The number of tiles to split rendering into. + int tile_x_count_; + int tile_y_count_; + + // List of attribute data that we need to set before rendering + VertexAttribMap attrib_values_; + + // The number of vertices to render + int vertex_count_; + + // The draw mode used during rendering + GLenum draw_mode_; + + // True, iff the output frame is cleared before rendering + bool clears_; + + // The color used to clear the output frame. + RGBAColor clear_color_; + + // Set to true to enable blending. + bool blending_; + int sfactor_; + int dfactor_; + + // Map from uniform ids to indices + std::map<ProgramVar, GLuint> uniform_indices_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H diff --git a/media/mca/filterfw/native/core/statistics.cpp b/media/mca/filterfw/native/core/statistics.cpp new file mode 100644 index 0000000..6f7fee7 --- /dev/null +++ b/media/mca/filterfw/native/core/statistics.cpp @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#include "core/statistics.h" + +#include <math.h> + +namespace android { +namespace filterfw { + +IncrementalGaussian::IncrementalGaussian() + : n_(0), + sum_x_(0.0f), + sum_x2_(0.0f), + mean_(0.0f), + var_(0.0f), + exp_denom_(0.0f), + pdf_denom_(0.0f) { +} + +void IncrementalGaussian::Add(float value) { + ++n_; + sum_x_ += value; + sum_x2_ += value * value; + + mean_ = sum_x_ / n_; + var_ = sum_x2_ / n_ - mean_ * mean_; + + exp_denom_ = 2.0f * var_; + pdf_denom_ = sqrtf(M_PI * exp_denom_); +} + +float IncrementalGaussian::Std() const { + return sqrtf(var_); +} + +float IncrementalGaussian::Pdf(float value) const { + if (var_ == 0.0f) { return n_ > 0 ? value == mean_ : 0.0f; } + const float diff = value - mean_; + return expf(-diff * diff / exp_denom_) / pdf_denom_; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/statistics.h b/media/mca/filterfw/native/core/statistics.h new file mode 100644 index 0000000..ce73b2b --- /dev/null +++ b/media/mca/filterfw/native/core/statistics.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_STATISTICS_H +#define ANDROID_FILTERFW_CORE_STATISTICS_H + +namespace android { +namespace filterfw { + +// An incrementally-constructed Normal distribution. +class IncrementalGaussian { + public: + IncrementalGaussian(); + + void Add(float value); + + float NumSamples() const { return n_; } + float Mean() const { return mean_; } + float Var() const { return var_; } + float Std() const; + float Pdf(float value) const; + + private: + int n_; + float sum_x_; + float sum_x2_; + float mean_; + float var_; + float exp_denom_; + float pdf_denom_; +}; + +// Discrete-time implementation of a simple RC low-pass filter: +// exponentially-weighted moving average. +class RCFilter { + public: + explicit RCFilter(float gain) + : gain_(gain), n_(0), value_(0.0f) {} + + void Add(float measurement) { + value_ = n_++ ? gain_ * measurement + (1.0f - gain_) * value_ : measurement; + } + + void Reset() { n_ = 0; } + + int NumMeasurements() const { return n_; } + float Output() const { return value_; } + + private: + float gain_; + int n_; + float value_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_STATISTICS_H diff --git a/media/mca/filterfw/native/core/time_util.cpp b/media/mca/filterfw/native/core/time_util.cpp new file mode 100644 index 0000000..c86c80d --- /dev/null +++ b/media/mca/filterfw/native/core/time_util.cpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "base/logging.h" +#include "base/utilities.h" + +#include "core/time_util.h" + +#include <map> +#include <string> +#include <sys/time.h> + +namespace android { +namespace filterfw { + +uint64_t getTimeUs() { + static long basesec; + struct timeval tv; + uint64_t nowtime; + gettimeofday(&tv, 0); + if (basesec == 0) { + basesec = tv.tv_sec; + } + nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 + + (uint64_t)tv.tv_usec; + return nowtime; +} + +const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100; + +NamedStopWatch::NamedStopWatch(const std::string& name) + : mName(name), + mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames), + mStartUSec(0), + mNumCalls(0), + mTotalUSec(0) { +} + +void NamedStopWatch::Start() { + mStartUSec = getTimeUs(); +} + +void NamedStopWatch::Stop() { + if (!mStartUSec) { + return; + } + uint64_t stopUSec = getTimeUs(); + if (stopUSec > mStartUSec) { + ++mNumCalls; + mTotalUSec += stopUSec - mStartUSec; + if (mNumCalls % mLoggingPeriodInFrames == 0) { + const float mSec = TotalUSec() * 1.0E-3f / NumCalls(); + ALOGE("%s: %f ms", Name().c_str(), mSec); + } + } + mStartUSec = 0; +} + +namespace { +static NamedStopWatch* GetWatchForName(const string& watch_name) { + // TODO: this leaks the NamedStopWatch objects. Replace it with a + // singleton to avoid that and make it thread safe. + static map<string, NamedStopWatch*> watches; + NamedStopWatch* watch = FindPtrOrNull(watches, watch_name); + if (!watch) { + watch = new NamedStopWatch(watch_name); + watches[watch_name] = watch; + } + return watch; +}; +} // namespace + +ScopedTimer::ScopedTimer(const string& stop_watch_name) { + mWatch = GetWatchForName(stop_watch_name); + mWatch->Start(); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/time_util.h b/media/mca/filterfw/native/core/time_util.h new file mode 100644 index 0000000..3cf2ec9 --- /dev/null +++ b/media/mca/filterfw/native/core/time_util.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_TIME_UTIL_H +#define ANDROID_FILTERFW_CORE_TIME_UTIL_H + +#include <string> +#include <utils/RefBase.h> + +#define LOG_MFF_RUNNING_TIMES 0 + +namespace android { +namespace filterfw { + +uint64_t getTimeUs(); + +class NamedStopWatch : public RefBase { + public: + static const uint64_t kDefaultLoggingPeriodInFrames; + + explicit NamedStopWatch(const string& name); + void Start(); + void Stop(); + + void SetName(const string& name) { mName = name; } + void SetLoggingPeriodInFrames(uint64_t numFrames) { + mLoggingPeriodInFrames = numFrames; + } + + const string& Name() const { return mName; } + uint64_t NumCalls() const { return mNumCalls; } + uint64_t TotalUSec() const { return mTotalUSec; } + + private: + string mName; + uint64_t mLoggingPeriodInFrames; + uint64_t mStartUSec; + uint64_t mNumCalls; + uint64_t mTotalUSec; +}; + +class ScopedTimer { + public: + explicit ScopedTimer(const string& stop_watch_name); + explicit ScopedTimer(NamedStopWatch* watch) + : mWatch(watch) { mWatch->Start(); } + ~ScopedTimer() { mWatch->Stop(); } + + private: + NamedStopWatch* mWatch; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_TIME_UTIL_H diff --git a/media/mca/filterfw/native/core/value.cpp b/media/mca/filterfw/native/core/value.cpp new file mode 100644 index 0000000..04bf0ef --- /dev/null +++ b/media/mca/filterfw/native/core/value.cpp @@ -0,0 +1,236 @@ +/* + * 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. + */ + +#include <stddef.h> +#include <stdlib.h> + +#include "value.h" + +#define NULL_VALUE_TYPE 0 +#define INT_VALUE_TYPE 1 +#define FLOAT_VALUE_TYPE 2 +#define STRING_VALUE_TYPE 3 +#define BUFFER_VALUE_TYPE 4 +#define MUTABLE_BUFFER_VALUE_TYPE 5 +#define INT_ARRAY_VALUE_TYPE 6 +#define FLOAT_ARRAY_VALUE_TYPE 7 + +// Templated versions ////////////////////////////////////////////////////////////////////////////// +template<typename POD, int TYPEID> +POD GetPODValue(Value value) { + return value.type == TYPEID ? *reinterpret_cast<POD*>(value.value) : POD(); +} + +template<typename PTR, int TYPEID> +PTR GetPtrValue(Value value) { + return value.type == TYPEID ? reinterpret_cast<PTR>(value.value) : NULL; +} + +template<typename POD, int TYPEID> +Value MakePODValue(POD value) { + Value result; + result.type = TYPEID; + result.value = malloc(sizeof(POD)); + result.count = 1; + *reinterpret_cast<POD*>(result.value) = value; + return result; +} + +template<typename BASE, int TYPEID> +Value MakePtrValue(const BASE* values, int count) { + Value result; + result.type = TYPEID; + result.value = malloc(sizeof(BASE) * count); + memcpy(result.value, values, sizeof(BASE) * count); + result.count = count; + return result; +} + +template<typename POD, int TYPEID> +int SetPODValue(Value* value, POD new_value) { + if (value->type == NULL_VALUE_TYPE) { + value->type = TYPEID; + value->value = malloc(sizeof(POD)); + value->count = 1; + } + if (value->type == TYPEID) { + *reinterpret_cast<POD*>(value->value) = new_value; + return 1; + } + return 0; +} + +template<typename BASE, int TYPEID> +int SetPtrValue(Value* value, const BASE* new_values, int count) { + if (value->type == NULL_VALUE_TYPE) { + value->type = TYPEID; + value->value = malloc(sizeof(BASE) * count); + value->count = count; + } + if (value->type == TYPEID && value->count == count) { + memcpy(value->value, new_values, sizeof(BASE) * count); + return 1; + } + return 0; +} + +// C Wrappers ////////////////////////////////////////////////////////////////////////////////////// +int GetIntValue(Value value) { + return GetPODValue<int, INT_VALUE_TYPE>(value); +} + +float GetFloatValue(Value value) { + return GetPODValue<float, FLOAT_VALUE_TYPE>(value); +} + +const char* GetStringValue(Value value) { + return GetPtrValue<const char*, STRING_VALUE_TYPE>(value); +} + +const char* GetBufferValue(Value value) { + return (value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE) + ? (const char*)value.value + : NULL; +} + +char* GetMutableBufferValue(Value value) { + return GetPtrValue<char*, MUTABLE_BUFFER_VALUE_TYPE>(value); +} + +int* GetIntArrayValue(Value value) { + return GetPtrValue<int*, INT_ARRAY_VALUE_TYPE>(value); +} + +float* GetFloatArrayValue(Value value) { + return GetPtrValue<float*, FLOAT_ARRAY_VALUE_TYPE>(value); +} + +int ValueIsNull(Value value) { + return value.type == NULL_VALUE_TYPE; +} + +int ValueIsInt(Value value) { + return value.type == INT_VALUE_TYPE; +} + +int ValueIsFloat(Value value) { + return value.type == FLOAT_VALUE_TYPE; +} + +int ValueIsString(Value value) { + return value.type == STRING_VALUE_TYPE; +} + +int ValueIsBuffer(Value value) { + return value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE; +} + +int ValueIsIntArray(Value value) { + return value.type == INT_ARRAY_VALUE_TYPE; +} + +int ValueIsFloatArray(Value value) { + return value.type == FLOAT_ARRAY_VALUE_TYPE; +} + +Value MakeNullValue() { + Value result; + result.type = NULL_VALUE_TYPE; + result.value = NULL; + result.count = 0; + return result; +} + +Value MakeIntValue(int value) { + return MakePODValue<int, INT_VALUE_TYPE>(value); +} + +Value MakeFloatValue(float value) { + return MakePODValue<float, FLOAT_VALUE_TYPE>(value); +} + +Value MakeStringValue(const char* value) { + return MakePtrValue<char, STRING_VALUE_TYPE>(value, strlen(value) + 1); +} + +Value MakeBufferValue(const char* buffer, int size) { + return MakePtrValue<char, BUFFER_VALUE_TYPE>(buffer, size); +} + +Value MakeBufferValueNoCopy(const char* buffer, int size) { + Value result; + result.type = BUFFER_VALUE_TYPE; + result.value = (void*)buffer; + result.count = size; + return result; +} + +Value MakeMutableBufferValue(const char* buffer, int size) { + return MakePtrValue<const char, MUTABLE_BUFFER_VALUE_TYPE>(buffer, size); +} + +Value MakeMutableBufferValueNoCopy(char* buffer, int size) { + Value result; + result.type = MUTABLE_BUFFER_VALUE_TYPE; + result.value = (void*)buffer; + result.count = size; + return result; +} + +Value MakeIntArrayValue(const int* values, int count) { + return MakePtrValue<int, INT_ARRAY_VALUE_TYPE>(values, count); +} + +Value MakeFloatArrayValue(const float* values, int count) { + return MakePtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(values, count); +} + +int SetIntValue(Value* value, int new_value) { + return SetPODValue<int, INT_VALUE_TYPE>(value, new_value); +} + +int SetFloatValue(Value* value, float new_value) { + return SetPODValue<float, FLOAT_VALUE_TYPE>(value, new_value); +} + +int SetStringValue(Value* value, const char* new_value) { + return SetPtrValue<char, STRING_VALUE_TYPE>(value, new_value, strlen(new_value) + 1); +} + +int SetMutableBufferValue(Value* value, const char* new_data, int size) { + return SetPtrValue<char, MUTABLE_BUFFER_VALUE_TYPE>(value, new_data, size); +} + +int SetIntArrayValue(Value* value, const int* new_values, int count) { + return SetPtrValue<int, INT_ARRAY_VALUE_TYPE>(value, new_values, count); +} + +int SetFloatArrayValue(Value* value, const float* new_values, int count) { + return SetPtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(value, new_values, count); +} + +int GetValueCount(Value value) { + return value.count; +} + +void ReleaseValue(Value* value) { + if (value && value->value) { + free(value->value); + value->value = NULL; + value->type = NULL_VALUE_TYPE; + } +} + diff --git a/media/mca/filterfw/native/core/value.h b/media/mca/filterfw/native/core/value.h new file mode 100644 index 0000000..37e8800 --- /dev/null +++ b/media/mca/filterfw/native/core/value.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_VALUE_H +#define ANDROID_FILTERFW_CORE_VALUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: As this is no longer part of the proposed NDK, should we make this object-oriented (C++) +// instead? We can also probably clean this up a bit. + +// TODO: Change this to an opaque handle? +typedef struct { + void* value; + int type; + int count; +} Value; + +// TODO: Probably should make these const Value*? +int GetIntValue(Value value); +float GetFloatValue(Value value); +const char* GetStringValue(Value value); +const char* GetBufferValue(Value value); +char* GetMutableBufferValue(Value value); +int* GetIntArrayValue(Value value); +float* GetFloatArrayValue(Value value); + +// TODO: Probably should make these const Value*? +int ValueIsNull(Value value); +int ValueIsInt(Value value); +int ValueIsFloat(Value value); +int ValueIsString(Value value); +int ValueIsBuffer(Value value); +int ValueIsMutableBuffer(Value value); +int ValueIsIntArray(Value value); +int ValueIsFloatArray(Value value); + +Value MakeNullValue(); +Value MakeIntValue(int value); +Value MakeFloatValue(float value); +Value MakeStringValue(const char* value); +Value MakeBufferValue(const char* data, int size); +Value MakeBufferValueNoCopy(const char* data, int size); +Value MakeMutableBufferValue(const char* data, int size); +Value MakeMutableBufferValueNoCopy(char* data, int size); +Value MakeIntArrayValue(const int* values, int count); +Value MakeFloatArrayValue(const float* values, int count); + +// Note: These only alloc if value is Null! Otherwise they overwrite, so data must fit! +int SetIntValue(Value* value, int new_value); +int SetFloatValue(Value* value, float new_value); +int SetStringValue(Value* value, const char* new_value); +int SetMutableBufferValue(Value* value, const char* new_data, int size); +int SetIntArrayValue(Value* value, const int* new_values, int count); +int SetFloatArrayValue(Value* value, const float* new_values, int count); + +int GetValueCount(Value value); + +void ReleaseValue(Value* value); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROID_FILTERFW_FILTER_VALUE_H diff --git a/media/mca/filterfw/native/core/vertex_frame.cpp b/media/mca/filterfw/native/core/vertex_frame.cpp new file mode 100644 index 0000000..822573f --- /dev/null +++ b/media/mca/filterfw/native/core/vertex_frame.cpp @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/gl_env.h" +#include "core/vertex_frame.h" + +#include <GLES2/gl2ext.h> +#include <EGL/egl.h> + +namespace android { +namespace filterfw { + +// GL Extensions that are dynamically looked up at runtime +static PFNGLMAPBUFFEROESPROC GLMapBufferOES = NULL; +static PFNGLUNMAPBUFFEROESPROC GLUnmapBufferOES = NULL; + +VertexFrame::VertexFrame(int size) + : vbo_(0), + size_(size) { +} + +VertexFrame::~VertexFrame() { + glDeleteBuffers(1, &vbo_); +} + +bool VertexFrame::CreateBuffer() { + glGenBuffers(1, &vbo_); + return !GLEnv::CheckGLError("Generating VBO"); +} + +bool VertexFrame::WriteData(const uint8_t* data, int size) { + // Create buffer if not created already + const bool first_upload = !HasVBO(); + if (first_upload && !CreateBuffer()) { + ALOGE("VertexFrame: Could not create vertex buffer!"); + return false; + } + + // Upload the data + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + if (GLEnv::CheckGLError("VBO Bind Buffer")) + return false; + + if (first_upload && size == size_) + glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); + else if (!first_upload && size <= size_) + glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + else { + ALOGE("VertexFrame: Attempting to upload more data (%d bytes) than fits " + "inside the vertex frame (%d bytes)!", size, size_); + return false; + } + + // Make sure it worked + if (GLEnv::CheckGLError("VBO Data Upload")) + return false; + + // Subsequent uploads are now bound to the size given here + size_ = size; + + return true; +} + +int VertexFrame::Size() const { + return size_; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/vertex_frame.h b/media/mca/filterfw/native/core/vertex_frame.h new file mode 100644 index 0000000..1205096 --- /dev/null +++ b/media/mca/filterfw/native/core/vertex_frame.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_VERTEXFRAME_H +#define ANDROID_FILTERFW_CORE_VERTEXFRAME_H + +#include <GLES2/gl2.h> + +namespace android { +namespace filterfw { + +// A VertexFrame stores vertex attribute data in a VBO. Unlike other frames, +// you often create instances of VertexFrame yourself, to pass vertex data to +// a ShaderProgram. Note, that any kind of reading from VertexFrames is NOT +// supported. Once data is uploaded to a VertexFrame, it cannot be read from +// again. +class VertexFrame { + public: + // Create a VertexFrame of the specified size (in bytes). + explicit VertexFrame(int size); + + ~VertexFrame(); + + // Upload the given data to the vertex buffer. The size must match the size + // passed in the constructor for the first upload. Subsequent uploads must + // be able to fit within the allocated space (i.e. size must not exceed the + // frame's size). + bool WriteData(const uint8_t* data, int size); + + // The size of the vertex buffer in bytes. + int Size() const; + + // Return the id of the internal VBO. Returns 0 if no VBO has been + // generated yet. The internal VBO is generated the first time data is + // uploaded. + GLuint GetVboId() const { + return vbo_; + } + + // Returns true if the frame contains an allocated VBO. + bool HasBuffer() const { + return vbo_ != 0; + } + + private: + // Create the VBO + bool CreateBuffer(); + + // Returns true if the VBO has been created. + bool HasVBO() const { + return vbo_ != 0; + } + + // The internal VBO handle + GLuint vbo_; + + // The size of this frame in bytes + int size_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_VERTEXFRAME_H diff --git a/media/mca/filterfw/native/libfilterfw.mk b/media/mca/filterfw/native/libfilterfw.mk new file mode 100644 index 0000000..4e88e6f --- /dev/null +++ b/media/mca/filterfw/native/libfilterfw.mk @@ -0,0 +1,33 @@ +# 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. +# + +# Add include paths for native code. +FFW_PATH := $(call my-dir) + +# Uncomment the requirements below, once we need them: + +# STLport +include external/stlport/libstlport.mk + +# Neven FaceDetect SDK +#LOCAL_C_INCLUDES += external/neven/FaceRecEm/common/src/b_FDSDK \ +# external/neven/FaceRecEm/common/src \ +# external/neven/Embedded/common/conf \ +# external/neven/Embedded/common/src \ +# external/neven/unix/src + +# Finally, add this directory +LOCAL_C_INCLUDES += $(FFW_PATH) + diff --git a/media/mca/filterpacks/Android.mk b/media/mca/filterpacks/Android.mk new file mode 100644 index 0000000..6166b1e --- /dev/null +++ b/media/mca/filterpacks/Android.mk @@ -0,0 +1,55 @@ +# 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. +# + +LOCAL_PATH := $(call my-dir) + +## +# base +## +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterpack_base +LOCAL_SRC_FILES := native/base/geometry.cpp \ + native/base/time_util.cpp + +LOCAL_CFLAGS := -DANDROID + +include external/stlport/libstlport.mk + +include $(BUILD_STATIC_LIBRARY) + +## +# filterpack_imageproc +## +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterpack_imageproc + +LOCAL_SRC_FILES += native/imageproc/brightness.c \ + native/imageproc/contrast.c \ + native/imageproc/invert.c \ + native/imageproc/to_rgba.c + +LOCAL_SHARED_LIBRARIES := libutils libfilterfw + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java new file mode 100644 index 0000000..4185343 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java @@ -0,0 +1,103 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.os.Handler; +import android.os.Looper; + +import java.lang.Runnable; + +/** + * @hide + */ +public class CallbackFilter extends Filter { + + @GenerateFieldPort(name = "listener", hasDefault = true) + private FilterContext.OnFrameReceivedListener mListener; + + @GenerateFieldPort(name = "userData", hasDefault = true) + private Object mUserData; + + @GenerateFinalPort(name = "callUiThread", hasDefault = true) + private boolean mCallbacksOnUiThread = true; + + private Handler mUiThreadHandler; + + private class CallbackRunnable implements Runnable { + private Filter mFilter; + private Frame mFrame; + private Object mUserData; + private FilterContext.OnFrameReceivedListener mListener; + + public CallbackRunnable(FilterContext.OnFrameReceivedListener listener, Filter filter, Frame frame, Object userData) { + mListener = listener; + mFilter = filter; + mFrame = frame; + mUserData = userData; + } + + public void run() { + mListener.onFrameReceived(mFilter, mFrame, mUserData); + mFrame.release(); + } + } + + public CallbackFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + public void prepare(FilterContext context) { + if (mCallbacksOnUiThread) { + mUiThreadHandler = new Handler(Looper.getMainLooper()); + } + } + + public void process(FilterContext context) { + // Get frame and forward to listener + final Frame input = pullInput("frame"); + if (mListener != null) { + if (mCallbacksOnUiThread) { + input.retain(); + CallbackRunnable uiRunnable = new CallbackRunnable(mListener, this, input, mUserData); + if (!mUiThreadHandler.post(uiRunnable)) { + throw new RuntimeException("Unable to send callback to UI thread!"); + } + } else { + mListener.onFrameReceived(this, input, mUserData); + } + } else { + throw new RuntimeException("CallbackFilter received frame, but no listener set!"); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java new file mode 100644 index 0000000..6b8cbc7 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java @@ -0,0 +1,62 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; + +/** + * @hide + */ +public class FrameBranch extends Filter { + + @GenerateFinalPort(name = "outputs", hasDefault = true) + private int mNumberOfOutputs = 2; + + public FrameBranch(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("in"); + for (int i = 0; i < mNumberOfOutputs; ++i) { + addOutputBasedOnInput("out" + i, "in"); + } + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("in"); + + // Push output + for (int i = 0; i < mNumberOfOutputs; ++i) { + pushOutput("out" + i, input); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java new file mode 100644 index 0000000..518b837 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java @@ -0,0 +1,64 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; + +import android.util.Log; + +/** + * @hide + */ +public class FrameFetch extends Filter { + + @GenerateFinalPort(name = "format", hasDefault = true) + private FrameFormat mFormat; + + @GenerateFieldPort(name = "key") + private String mKey; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + public FrameFetch(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mFormat == null ? FrameFormat.unspecified() : mFormat); + } + + public void process(FilterContext context) { + Frame output = context.fetchFrame(mKey); + if (output != null) { + pushOutput("frame", output); + if (!mRepeatFrame) { + closeOutputPort("frame"); + } + } else { + delayNextProcess(250); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java new file mode 100644 index 0000000..1218d1a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java @@ -0,0 +1,63 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; + +/** + * @hide + */ +public class FrameSource extends Filter { + + @GenerateFinalPort(name = "format") + private FrameFormat mFormat; + + @GenerateFieldPort(name = "frame", hasDefault = true) + private Frame mFrame = null; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + public FrameSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mFormat); + } + + @Override + public void process(FilterContext context) { + if (mFrame != null) { + // Push output + pushOutput("frame", mFrame); + } + + if (!mRepeatFrame) { + // Close output port as we are done here + closeOutputPort("frame"); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java new file mode 100644 index 0000000..3aadaac --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java @@ -0,0 +1,51 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; + +/** + * @hide + */ +public class FrameStore extends Filter { + + @GenerateFieldPort(name = "key") + private String mKey; + + public FrameStore(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + // Store frame + context.storeFrame(mKey, input); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java new file mode 100644 index 0000000..1776820 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java @@ -0,0 +1,106 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class GLTextureSource extends Filter { + + @GenerateFieldPort(name = "texId") + private int mTexId; + + @GenerateFieldPort(name = "width") + private int mWidth; + + @GenerateFieldPort(name = "height") + private int mHeight; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + private boolean mRepeatFrame = false; + + /* This timestamp will be used for all output frames from this source. They + * represent nanoseconds, and should be positive and monotonically + * increasing. Set to Frame.TIMESTAMP_UNKNOWN if timestamps are not + * meaningful for these textures. + */ + @GenerateFieldPort(name = "timestamp", hasDefault = true) + private long mTimestamp = Frame.TIMESTAMP_UNKNOWN; + + private Frame mFrame; + + public GLTextureSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Release frame, so that it is recreated during the next process call + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Generate frame if not generated already + if (mFrame == null) { + FrameFormat outputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mFrame = context.getFrameManager().newBoundFrame(outputFormat, + GLFrame.EXISTING_TEXTURE_BINDING, + mTexId); + mFrame.setTimestamp(mTimestamp); + } + + // Push output + pushOutput("frame", mFrame); + + if (!mRepeatFrame) { + // Close output port as we are done here + closeOutputPort("frame"); + } + } + + @Override + public void tearDown(FilterContext context) { + if (mFrame != null) { + mFrame.release(); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java new file mode 100644 index 0000000..b2285cd --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java @@ -0,0 +1,64 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class GLTextureTarget extends Filter { + + @GenerateFieldPort(name = "texId") + private int mTexId; + + public GLTextureTarget(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + FrameFormat format = ImageFormat.create(input.getFormat().getWidth(), + input.getFormat().getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + Frame frame = context.getFrameManager().newBoundFrame(format, GLFrame.EXISTING_TEXTURE_BINDING, mTexId); + + // Copy to our texture frame + frame.setDataFromFrame(input); + frame.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java new file mode 100644 index 0000000..6c22ee7 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java @@ -0,0 +1,96 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.PrimitiveFormat; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class InputStreamSource extends Filter { + + @GenerateFinalPort(name = "target") + private String mTarget; + + @GenerateFieldPort(name = "stream") + private InputStream mInputStream; + + @GenerateFinalPort(name = "format", hasDefault = true) + private MutableFrameFormat mOutputFormat = null; + + public InputStreamSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + int target = FrameFormat.readTargetString(mTarget); + if (mOutputFormat == null) { + mOutputFormat = PrimitiveFormat.createByteFormat(target); + } + addOutputPort("data", mOutputFormat); + } + + @Override + public void process(FilterContext context) { + int fileSize = 0; + ByteBuffer byteBuffer = null; + + // Read the file + try { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = mInputStream.read(buffer)) > 0) { + byteStream.write(buffer, 0, bytesRead); + fileSize += bytesRead; + } + byteBuffer = ByteBuffer.wrap(byteStream.toByteArray()); + } catch (IOException exception) { + throw new RuntimeException( + "InputStreamSource: Could not read stream: " + exception.getMessage() + "!"); + } + + // Put it into a frame + mOutputFormat.setDimensions(fileSize); + Frame output = context.getFrameManager().newFrame(mOutputFormat); + output.setData(byteBuffer); + + // Push output + pushOutput("data", output); + + // Release pushed frame + output.release(); + + // Close output port as we are done here + closeOutputPort("data"); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java new file mode 100644 index 0000000..f3e08e4 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java @@ -0,0 +1,44 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; + +/** + * @hide + */ +public class NullFilter extends Filter { + + public NullFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("frame"); + } + + @Override + public void process(FilterContext context) { + pullInput("frame"); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java new file mode 100644 index 0000000..d511e44 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java @@ -0,0 +1,93 @@ +/* + * 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 android.filterpacks.base; + +import java.util.Set; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class ObjectSource extends Filter { + + @GenerateFieldPort(name = "object") + private Object mObject; + + @GenerateFinalPort(name = "format", hasDefault = true) + private FrameFormat mOutputFormat = FrameFormat.unspecified(); + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + boolean mRepeatFrame = false; + + private Frame mFrame; + + public ObjectSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + addOutputPort("frame", mOutputFormat); + } + + @Override + public void process(FilterContext context) { + // If no frame has been created, create one now. + if (mFrame == null) { + if (mObject == null) { + throw new NullPointerException("ObjectSource producing frame with no object set!"); + } + FrameFormat outputFormat = ObjectFormat.fromObject(mObject, FrameFormat.TARGET_SIMPLE); + mFrame = context.getFrameManager().newFrame(outputFormat); + mFrame.setObjectValue(mObject); + mFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + } + + // Push output + pushOutput("frame", mFrame); + + // Wait for free output + if (!mRepeatFrame) { + closeOutputPort("frame"); + } + } + + @Override + public void tearDown(FilterContext context) { + mFrame.release(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Release our internal frame, so that it is regenerated on the next call to process(). + if (name.equals("object")) { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java new file mode 100644 index 0000000..3d3d0f1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java @@ -0,0 +1,66 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; + +import java.io.OutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class OutputStreamTarget extends Filter { + + @GenerateFieldPort(name = "stream") + private OutputStream mOutputStream; + + public OutputStreamTarget(String name) { + super(name); + } + + @Override + public void setupPorts() { + addInputPort("data"); + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("data"); + ByteBuffer data; + + if (input.getFormat().getObjectClass() == String.class) { + String stringVal = (String)input.getObjectValue(); + data = ByteBuffer.wrap(stringVal.getBytes()); + } else { + data = input.getData(); + } + try { + mOutputStream.write(data.array(), 0, data.limit()); + mOutputStream.flush(); + } catch (IOException exception) { + throw new RuntimeException( + "OutputStreamTarget: Could not write to stream: " + exception.getMessage() + "!"); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java new file mode 100644 index 0000000..254167a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java @@ -0,0 +1,76 @@ +/* + * 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 android.filterpacks.base; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public class RetargetFilter extends Filter { + + @GenerateFinalPort(name = "target", hasDefault = false) + private String mTargetString; + + private MutableFrameFormat mOutputFormat; + private int mTarget = -1; + + public RetargetFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + // Setup target + mTarget = FrameFormat.readTargetString(mTargetString); + + // Add ports + addInputPort("frame"); + addOutputBasedOnInput("frame", "frame"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + MutableFrameFormat retargeted = inputFormat.mutableCopy(); + retargeted.setTarget(mTarget); + return retargeted; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("frame"); + + // Create output frame + Frame output = context.getFrameManager().duplicateFrameToTarget(input, mTarget); + + // Push output + pushOutput("frame", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java new file mode 100644 index 0000000..473369c --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java @@ -0,0 +1,66 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * @hide + */ +public class AlphaBlendFilter extends ImageCombineFilter { + + private final String mAlphaBlendShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float weight;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" + + " float blend = texture2D(tex_sampler_2, v_texcoord).r * weight;\n" + + " gl_FragColor = colorL * (1.0 - blend) + colorR * blend;\n" + + "}\n"; + + public AlphaBlendFilter(String name) { + super(name, new String[] { "source", "overlay", "mask" }, "blended", "weight"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("TODO: Write native implementation for AlphaBlend!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mAlphaBlendShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java new file mode 100644 index 0000000..c71c1c9 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java @@ -0,0 +1,309 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class AutoFixFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + @GenerateFieldPort(name = "scale") + private float mScale; + + private static final int normal_cdf[] = { + 9, 33, 50, 64, 75, 84, 92, 99, 106, 112, 117, 122, 126, 130, 134, 138, 142, + 145, 148, 150, 154, 157, 159, 162, 164, 166, 169, 170, 173, 175, 177, 179, + 180, 182, 184, 186, 188, 189, 190, 192, 194, 195, 197, 198, 199, 200, 202, + 203, 205, 206, 207, 208, 209, 210, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 229, 230, 231, 232, 233, + 234, 235, 236, 236, 237, 238, 239, 239, 240, 240, 242, 242, 243, 244, 245, + 245, 246, 247, 247, 248, 249, 249, 250, 250, 251, 252, 253, 253, 254, 255, + 255, 256, 256, 257, 258, 258, 259, 259, 259, 260, 261, 262, 262, 263, 263, + 264, 264, 265, 265, 266, 267, 267, 268, 268, 269, 269, 269, 270, 270, 271, + 272, 272, 273, 273, 274, 274, 275, 275, 276, 276, 277, 277, 277, 278, 278, + 279, 279, 279, 280, 280, 281, 282, 282, 282, 283, 283, 284, 284, 285, 285, + 285, 286, 286, 287, 287, 288, 288, 288, 289, 289, 289, 290, 290, 290, 291, + 292, 292, 292, 293, 293, 294, 294, 294, 295, 295, 296, 296, 296, 297, 297, + 297, 298, 298, 298, 299, 299, 299, 299, 300, 300, 301, 301, 302, 302, 302, + 303, 303, 304, 304, 304, 305, 305, 305, 306, 306, 306, 307, 307, 307, 308, + 308, 308, 309, 309, 309, 309, 310, 310, 310, 310, 311, 312, 312, 312, 313, + 313, 313, 314, 314, 314, 315, 315, 315, 315, 316, 316, 316, 317, 317, 317, + 318, 318, 318, 319, 319, 319, 319, 319, 320, 320, 320, 321, 321, 322, 322, + 322, 323, 323, 323, 323, 324, 324, 324, 325, 325, 325, 325, 326, 326, 326, + 327, 327, 327, 327, 328, 328, 328, 329, 329, 329, 329, 329, 330, 330, 330, + 330, 331, 331, 332, 332, 332, 333, 333, 333, 333, 334, 334, 334, 334, 335, + 335, 335, 336, 336, 336, 336, 337, 337, 337, 337, 338, 338, 338, 339, 339, + 339, 339, 339, 339, 340, 340, 340, 340, 341, 341, 342, 342, 342, 342, 343, + 343, 343, 344, 344, 344, 344, 345, 345, 345, 345, 346, 346, 346, 346, 347, + 347, 347, 347, 348, 348, 348, 348, 349, 349, 349, 349, 349, 349, 350, 350, + 350, 350, 351, 351, 352, 352, 352, 352, 353, 353, 353, 353, 354, 354, 354, + 354, 355, 355, 355, 355, 356, 356, 356, 356, 357, 357, 357, 357, 358, 358, + 358, 358, 359, 359, 359, 359, 359, 359, 359, 360, 360, 360, 360, 361, 361, + 362, 362, 362, 362, 363, 363, 363, 363, 364, 364, 364, 364, 365, 365, 365, + 365, 366, 366, 366, 366, 366, 367, 367, 367, 367, 368, 368, 368, 368, 369, + 369, 369, 369, 369, 369, 370, 370, 370, 370, 370, 371, 371, 372, 372, 372, + 372, 373, 373, 373, 373, 374, 374, 374, 374, 374, 375, 375, 375, 375, 376, + 376, 376, 376, 377, 377, 377, 377, 378, 378, 378, 378, 378, 379, 379, 379, + 379, 379, 379, 380, 380, 380, 380, 381, 381, 381, 382, 382, 382, 382, 383, + 383, 383, 383, 384, 384, 384, 384, 385, 385, 385, 385, 385, 386, 386, 386, + 386, 387, 387, 387, 387, 388, 388, 388, 388, 388, 389, 389, 389, 389, 389, + 389, 390, 390, 390, 390, 391, 391, 392, 392, 392, 392, 392, 393, 393, 393, + 393, 394, 394, 394, 394, 395, 395, 395, 395, 396, 396, 396, 396, 396, 397, + 397, 397, 397, 398, 398, 398, 398, 399, 399, 399, 399, 399, 399, 400, 400, + 400, 400, 400, 401, 401, 402, 402, 402, 402, 403, 403, 403, 403, 404, 404, + 404, 404, 405, 405, 405, 405, 406, 406, 406, 406, 406, 407, 407, 407, 407, + 408, 408, 408, 408, 409, 409, 409, 409, 409, 409, 410, 410, 410, 410, 411, + 411, 412, 412, 412, 412, 413, 413, 413, 413, 414, 414, 414, 414, 415, 415, + 415, 415, 416, 416, 416, 416, 417, 417, 417, 417, 418, 418, 418, 418, 419, + 419, 419, 419, 419, 419, 420, 420, 420, 420, 421, 421, 422, 422, 422, 422, + 423, 423, 423, 423, 424, 424, 424, 425, 425, 425, 425, 426, 426, 426, 426, + 427, 427, 427, 427, 428, 428, 428, 429, 429, 429, 429, 429, 429, 430, 430, + 430, 430, 431, 431, 432, 432, 432, 433, 433, 433, 433, 434, 434, 434, 435, + 435, 435, 435, 436, 436, 436, 436, 437, 437, 437, 438, 438, 438, 438, 439, + 439, 439, 439, 439, 440, 440, 440, 441, 441, 442, 442, 442, 443, 443, 443, + 443, 444, 444, 444, 445, 445, 445, 446, 446, 446, 446, 447, 447, 447, 448, + 448, 448, 449, 449, 449, 449, 449, 450, 450, 450, 451, 451, 452, 452, 452, + 453, 453, 453, 454, 454, 454, 455, 455, 455, 456, 456, 456, 457, 457, 457, + 458, 458, 458, 459, 459, 459, 459, 460, 460, 460, 461, 461, 462, 462, 462, + 463, 463, 463, 464, 464, 465, 465, 465, 466, 466, 466, 467, 467, 467, 468, + 468, 469, 469, 469, 469, 470, 470, 470, 471, 472, 472, 472, 473, 473, 474, + 474, 474, 475, 475, 476, 476, 476, 477, 477, 478, 478, 478, 479, 479, 479, + 480, 480, 480, 481, 482, 482, 483, 483, 484, 484, 484, 485, 485, 486, 486, + 487, 487, 488, 488, 488, 489, 489, 489, 490, 490, 491, 492, 492, 493, 493, + 494, 494, 495, 495, 496, 496, 497, 497, 498, 498, 499, 499, 499, 500, 501, + 502, 502, 503, 503, 504, 504, 505, 505, 506, 507, 507, 508, 508, 509, 509, + 510, 510, 511, 512, 513, 513, 514, 515, 515, 516, 517, 517, 518, 519, 519, + 519, 520, 521, 522, 523, 524, 524, 525, 526, 526, 527, 528, 529, 529, 530, + 531, 532, 533, 534, 535, 535, 536, 537, 538, 539, 539, 540, 542, 543, 544, + 545, 546, 547, 548, 549, 549, 550, 552, 553, 554, 555, 556, 558, 559, 559, + 561, 562, 564, 565, 566, 568, 569, 570, 572, 574, 575, 577, 578, 579, 582, + 583, 585, 587, 589, 590, 593, 595, 597, 599, 602, 604, 607, 609, 612, 615, + 618, 620, 624, 628, 631, 635, 639, 644, 649, 654, 659, 666, 673, 680, 690, + 700, 714 }; + + private final String mAutoFixShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float scale;\n" + + "uniform float shift_scale;\n" + + "uniform float hist_offset;\n" + + "uniform float hist_scale;\n" + + "uniform float density_offset;\n" + + "uniform float density_scale;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const vec3 weights = vec3(0.33333, 0.33333, 0.33333);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = dot(color.rgb, weights);\n" + + " float mask_value = energy - 0.5;\n" + + " float alpha;\n" + + " if (mask_value > 0.0) {\n" + + " alpha = (pow(2.0 * mask_value, 1.5) - 1.0) * scale + 1.0;\n" + + " } else { \n" + + " alpha = (pow(2.0 * mask_value, 2.0) - 1.0) * scale + 1.0;\n" + + " }\n" + + " float index = energy * hist_scale + hist_offset;\n" + + " vec4 temp = texture2D(tex_sampler_1, vec2(index, 0.5));\n" + + " float value = temp.g + temp.r * shift_scale;\n" + + " index = value * density_scale + density_offset;\n" + + " temp = texture2D(tex_sampler_2, vec2(index, 0.5));\n" + + " value = temp.g + temp.r * shift_scale;\n" + + " float dst_energy = energy * alpha + value * (1.0 - alpha);\n" + + " float max_energy = energy / max(color.r, max(color.g, color.b));\n" + + " if (dst_energy > max_energy) {\n" + + " dst_energy = max_energy;\n" + + " }\n" + + " if (energy == 0.0) {\n" + + " gl_FragColor = color;\n" + + " } else {\n" + + " gl_FragColor = vec4(color.rgb * dst_energy / energy, color.a);\n" + + " }\n" + + "}\n"; + + private Program mShaderProgram; + private Program mNativeProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mHistFrame; + private Frame mDensityFrame; + + public AutoFixFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mAutoFixShader); + shaderProgram.setMaximumTileSize(mTileSize); + mShaderProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + mShaderProgram.setHostValue("shift_scale", 1.0f / 256f); + mShaderProgram.setHostValue("hist_offset", 0.5f / 766f); + mShaderProgram.setHostValue("hist_scale", 765f / 766f); + mShaderProgram.setHostValue("density_offset", 0.5f / 1024f); + mShaderProgram.setHostValue("density_scale", 1023f / 1024f); + mShaderProgram.setHostValue("scale", mScale); + } + + @Override + protected void prepare(FilterContext context) { + int densityDim = 1024; + int histDim = 255 * 3 + 1; + long precision = (256l * 256l - 1l); + + int[] densityTable = new int[densityDim]; + for (int i = 0; i < densityDim; ++i) { + long temp = normal_cdf[i] * precision / histDim; + densityTable[i] = (int) temp; + } + + FrameFormat densityFormat = ImageFormat.create(densityDim, 1, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mDensityFrame = context.getFrameManager().newFrame(densityFormat); + mDensityFrame.setInts(densityTable); + } + + @Override + public void tearDown(FilterContext context) { + if (mDensityFrame != null) { + mDensityFrame.release(); + mDensityFrame = null; + } + + if (mHistFrame != null) { + mHistFrame.release(); + mHistFrame = null; + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mShaderProgram != null) { + mShaderProgram.setHostValue("scale", mScale); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mShaderProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + createHistogramFrame(context, mWidth, mHeight, input.getInts()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mHistFrame, mDensityFrame}; + mShaderProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void createHistogramFrame(FilterContext context, int width, int height, int[] data) { + int histDims = 255 * 3 + 1; + int[] histArray = new int[histDims]; + + float border_thickness_ratio = 0.05f; + int y_border_thickness = (int) (height * border_thickness_ratio); + int x_border_thickness = (int) (width * border_thickness_ratio); + int pixels = (width - 2 * x_border_thickness) * (height - 2 * y_border_thickness); + + float count = 0f; + for (int y = y_border_thickness; y < height - y_border_thickness; ++y) { + for (int x = x_border_thickness; x < width - x_border_thickness; ++x) { + int index = y * width + x; + int energy = (data[index] & 0xFF) + ((data[index] >> 8) & 0xFF) + + ((data[index] >> 16) & 0xFF); + histArray[energy] ++; + } + } + + for (int i = 1; i < histDims; i++) { + histArray[i] += histArray[i-1]; + } + + for (int i = 0; i < histDims; i++) { + long temp = (256 * 256 - 1l) * histArray[i] / pixels; + histArray[i] = (int) temp; + } + + FrameFormat shaderHistFormat = ImageFormat.create(histDims, 1, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mHistFrame != null) + mHistFrame.release(); + + mHistFrame = context.getFrameManager().newFrame(shaderHistFormat); + mHistFrame.setInts(histArray); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java new file mode 100644 index 0000000..d4c901f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java @@ -0,0 +1,153 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterpacks.imageproc.ImageCombineFilter; +import android.graphics.Bitmap; + +import android.util.Log; + +/** + * @hide + */ +public class BitmapOverlayFilter extends Filter { + + @GenerateFieldPort(name = "bitmap") + private Bitmap mBitmap; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private Frame mFrame; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mOverlayShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 original = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " gl_FragColor = vec4(original.rgb * (1.0 - mask.a) + mask.rgb, 1.0);\n" + + "}\n"; + + public BitmapOverlayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mOverlayShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FisheyeFilter does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void tearDown(FilterContext context) { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + createBitmapFrame(context); + } + + // Process + Frame[] inputs = {input, mFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void createBitmapFrame(FilterContext context) { + if (mBitmap != null) { + FrameFormat format = ImageFormat.create(mBitmap.getWidth(), + mBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + if (mFrame != null) { + mFrame.release(); + } + + mFrame = context.getFrameManager().newFrame(format); + mFrame.setBitmap(mBitmap); + + mBitmap.recycle(); + mBitmap = null; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java new file mode 100644 index 0000000..978fc94 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java @@ -0,0 +1,118 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; + +/** + * @hide + */ +public class BitmapSource extends Filter { + + @GenerateFieldPort(name = "target") + String mTargetString; + + @GenerateFieldPort(name = "bitmap") + private Bitmap mBitmap; + + @GenerateFieldPort(name = "recycleBitmap", hasDefault = true) + private boolean mRecycleBitmap = true; + + @GenerateFieldPort(name = "repeatFrame", hasDefault = true) + boolean mRepeatFrame = false; + + private int mTarget; + private Frame mImageFrame; + + public BitmapSource(String name) { + super(name); + } + + + @Override + public void setupPorts() { + // Setup output format + FrameFormat outputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED); + + // Add output port + addOutputPort("image", outputFormat); + } + + public void loadImage(FilterContext filterContext) { + // Create frame with bitmap + mTarget = FrameFormat.readTargetString(mTargetString); + FrameFormat outputFormat = ImageFormat.create(mBitmap.getWidth(), + mBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + mTarget); + mImageFrame = filterContext.getFrameManager().newFrame(outputFormat); + mImageFrame.setBitmap(mBitmap); + mImageFrame.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + + // Free up space used by bitmap + if (mRecycleBitmap) { + mBitmap.recycle(); + } + mBitmap = null; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // Clear image (to trigger reload) in case parameters have been changed + if (name.equals("bitmap") || name.equals("target")) { + if (mImageFrame != null) { + mImageFrame.release(); + mImageFrame = null; + } + } + } + + @Override + public void process(FilterContext context) { + if (mImageFrame == null) { + loadImage(context); + } + + pushOutput("image", mImageFrame); + + if (!mRepeatFrame) { + closeOutputPort("image"); + } + } + + @Override + public void tearDown(FilterContext env) { + if (mImageFrame != null) { + mImageFrame.release(); + mImageFrame = null; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java new file mode 100644 index 0000000..a1cec01 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java @@ -0,0 +1,176 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class BlackWhiteFilter extends Filter { + + @GenerateFieldPort(name = "black", hasDefault = true) + private float mBlack = 0f; + + @GenerateFieldPort(name = "white", hasDefault = true) + private float mWhite = 1f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame = null; + private Random mRandom; + + private final String mBlackWhiteShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float black;\n" + + "uniform float scale;\n" + + "uniform float stepsize;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp((color.rgb - black) * scale, 0.0, 1.0);\n" + + " vec3 temp = clamp((color.rgb + stepsize - black) * scale, 0.0, 1.0);\n" + + " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public BlackWhiteFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mBlackWhiteShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + updateParameters(); + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void updateParameters() { + float scale = (mBlack != mWhite) ? 1.0f / (mWhite - mBlack) : 2000f; + float stepsize = 1.0f / 255.0f; + + mProgram.setHostValue("black", mBlack); + mProgram.setHostValue("scale", scale); + mProgram.setHostValue("stepsize", stepsize); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java new file mode 100644 index 0000000..29bc8a3 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java @@ -0,0 +1,65 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +/** + * The filter linearly blends "left" and "right" frames. The blending weight is + * the multiplication of parameter "blend" and the alpha value in "right" frame. + * @hide + */ +public class BlendFilter extends ImageCombineFilter { + + private final String mBlendShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float blend;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 colorL = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 colorR = texture2D(tex_sampler_1, v_texcoord);\n" + + " float weight = colorR.a * blend;\n" + + " gl_FragColor = mix(colorL, colorR, weight);\n" + + "}\n"; + + public BlendFilter(String name) { + super(name, new String[] { "left", "right" }, "blended", "blend"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("TODO: Write native implementation for Blend!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mBlendShader); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java new file mode 100644 index 0000000..046e69d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java @@ -0,0 +1,59 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +/** + * @hide + */ +public class BrightnessFilter extends SimpleImageFilter { + + private static final String mBrightnessShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float brightness;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = brightness * color;\n" + + "}\n"; + + public BrightnessFilter(String name) { + super(name, "brightness"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "brightness"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mBrightnessShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java new file mode 100644 index 0000000..19da006 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java @@ -0,0 +1,128 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.util.Log; + +public class ColorTemperatureFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0.5f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mColorTemperatureShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 new_color = color.rgb;\n" + + " new_color.r = color.r + color.r * ( 1.0 - color.r) * scale;\n" + + " new_color.b = color.b - color.b * ( 1.0 - color.b) * scale;\n" + + " if (scale > 0.0) { \n" + + " color.g = color.g + color.g * ( 1.0 - color.g) * scale * 0.25;\n" + + " }\n" + + " float max_value = max(new_color.r, max(new_color.g, new_color.b));\n" + + " if (max_value > 1.0) { \n" + + " new_color /= max_value;\n" + + " } \n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public ColorTemperatureFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mColorTemperatureShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + mProgram.setHostValue("scale", 2.0f * mScale - 1.0f); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java new file mode 100644 index 0000000..70e987f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java @@ -0,0 +1,64 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +import java.util.Set; + +/** + * @hide + */ +public class ContrastFilter extends SimpleImageFilter { + + private static final String mContrastShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float contrast;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " color -= 0.5;\n" + + " color *= contrast;\n" + + " color += 0.5;\n" + + " gl_FragColor = color;\n" + // this will clamp + "}\n"; + + public ContrastFilter(String name) { + super(name, "contrast"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "contrast"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mContrastShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java new file mode 100644 index 0000000..5222d9c --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java @@ -0,0 +1,147 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.util.Log; + +/** + * @hide + */ +public class CropFilter extends Filter { + + private Program mProgram; + private FrameFormat mLastFormat = null; + + @GenerateFieldPort(name = "owidth") + private int mOutputWidth = -1; + + @GenerateFieldPort(name = "oheight") + private int mOutputHeight = -1; + + @GenerateFieldPort(name = "fillblack") + private boolean mFillBlack = false; + + public CropFilter(String name) { + super(name); + } + + private final String mFragShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const vec2 lo = vec2(0.0, 0.0);\n" + + " const vec2 hi = vec2(1.0, 1.0);\n" + + " const vec4 black = vec4(0.0, 0.0, 0.0, 1.0);\n" + + " bool out_of_bounds =\n" + + " any(lessThan(v_texcoord, lo)) ||\n" + + " any(greaterThan(v_texcoord, hi));\n" + + " if (out_of_bounds) {\n" + + " gl_FragColor = black;\n" + + " } else {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + " }\n" + + "}\n"; + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + // Make sure output size is set to unspecified, as we do not know what we will be resizing + // to. + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + outputFormat.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED); + return outputFormat; + } + + protected void createProgram(FilterContext context, FrameFormat format) { + // TODO: Add CPU version + if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return; + mLastFormat = format; + mProgram = null; + switch (format.getTarget()) { + case FrameFormat.TARGET_GPU: + if(mFillBlack) + mProgram = new ShaderProgram(context, mFragShader); + else + mProgram = ShaderProgram.createIdentity(context); + + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for crop filter " + this + "!"); + } + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame imageFrame = pullInput("image"); + Frame boxFrame = pullInput("box"); + + createProgram(env, imageFrame.getFormat()); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + + // Create output format + MutableFrameFormat outputFormat = imageFrame.getFormat().mutableCopy(); + outputFormat.setDimensions(mOutputWidth == -1 ? outputFormat.getWidth() : mOutputWidth, + mOutputHeight == -1 ? outputFormat.getHeight() : mOutputHeight); + + // Create output frame + Frame output = env.getFrameManager().newFrame(outputFormat); + + // Set the program parameters + if (mProgram instanceof ShaderProgram) { + ShaderProgram shaderProgram = (ShaderProgram)mProgram; + shaderProgram.setSourceRegion(box); + } + + mProgram.process(imageFrame, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java new file mode 100644 index 0000000..d423d06 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java @@ -0,0 +1,141 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.util.Log; + +/** + * @hide + */ +public class CropRectFilter extends Filter { + + @GenerateFieldPort(name = "xorigin") + private int mXorigin; + + @GenerateFieldPort(name = "yorigin") + private int mYorigin; + + @GenerateFieldPort(name = "width") + private int mOutputWidth; + + @GenerateFieldPort(name = "height") + private int mOutputHeight; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + public CropRectFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateSourceRect(mWidth, mHeight); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateSourceRect(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + void updateSourceRect(int width, int height) { + mWidth = width; + mHeight = height; + + /* + Log.e("CropFilter", mWidth + ", " + mHeight + ", " + + (float) mXorigin / mWidth + ", " + + (float) mYorigin / mHeight + ", " + + (float) mOutputWidth / mWidth + ", " + + (float) mOutputHeight / mHeight); + */ + + ((ShaderProgram) mProgram).setSourceRect((float) mXorigin / mWidth, + (float) mYorigin / mHeight, + (float) mOutputWidth / mWidth, + (float) mOutputHeight / mHeight); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java new file mode 100644 index 0000000..e0514f8 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java @@ -0,0 +1,129 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class CrossProcessFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mCrossProcessShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 ncolor = vec3(0.0, 0.0, 0.0);\n" + + " float value;\n" + + " if (color.r < 0.5) {\n" + + " value = color.r;\n" + + " } else {\n" + + " value = 1.0 - color.r;\n" + + " }\n" + + " float red = 4.0 * value * value * value;\n" + + " if (color.r < 0.5) {\n" + + " ncolor.r = red;\n" + + " } else {\n" + + " ncolor.r = 1.0 - red;\n" + + " }\n" + + " if (color.g < 0.5) {\n" + + " value = color.g;\n" + + " } else {\n" + + " value = 1.0 - color.g;\n" + + " }\n" + + " float green = 2.0 * value * value;\n" + + " if (color.g < 0.5) {\n" + + " ncolor.g = green;\n" + + " } else {\n" + + " ncolor.g = 1.0 - green;\n" + + " }\n" + + " ncolor.b = color.b * 0.5 + 0.25;\n" + + " gl_FragColor = vec4(ncolor.rgb, color.a);\n" + + "}\n"; + + public CrossProcessFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mCrossProcessShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter CrossProcess does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java new file mode 100644 index 0000000..3c7b846 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java @@ -0,0 +1,175 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class DocumentaryFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame; + private Random mRandom; + + private final String mDocumentaryShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float stepsize;\n" + + "uniform float inv_max_dist;\n" + + "uniform vec2 center;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + // black white + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp(2.0 * color.rgb, 0.0, 1.0);\n" + + " vec3 temp = clamp(2.0 * (color.rgb + stepsize), 0.0, 1.0);\n" + + " vec3 new_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + // grayscale + " float gray = dot(new_color, vec3(0.299, 0.587, 0.114));\n" + + " new_color = vec3(gray, gray, gray);\n" + + // vignette + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.83) * 20.0)) + 0.15;\n" + + " gl_FragColor = vec4(new_color * lumen, color.a);\n" + + "}\n"; + + public DocumentaryFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mDocumentaryShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + + initParameters(); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + if (mProgram != null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + mProgram.setHostValue("stepsize", 1.0f / 255.0f); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java new file mode 100644 index 0000000..3f1711e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java @@ -0,0 +1,92 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class DrawOverlayFilter extends Filter { + + private ShaderProgram mProgram; + + public DrawOverlayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + FrameFormat imageFormatMask = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + addMaskedInputPort("source", imageFormatMask); + addMaskedInputPort("overlay", imageFormatMask); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "source"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void prepare(FilterContext context) { + mProgram = ShaderProgram.createIdentity(context); + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame sourceFrame = pullInput("source"); + Frame overlayFrame = pullInput("overlay"); + Frame boxFrame = pullInput("box"); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + box = box.translated(1.0f, 1.0f).scaled(2.0f); + + mProgram.setTargetRegion(box); + + // Create output frame with copy of input + Frame output = env.getFrameManager().newFrame(sourceFrame.getFormat()); + output.setDataFromFrame(sourceFrame); + + // Draw onto output + mProgram.process(overlayFrame, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java new file mode 100644 index 0000000..83c9348 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java @@ -0,0 +1,132 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.geometry.Quad; +import android.filterfw.format.ImageFormat; +import android.filterfw.format.ObjectFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class DrawRectFilter extends Filter { + + @GenerateFieldPort(name = "colorRed", hasDefault = true) + private float mColorRed = 0.8f; + + @GenerateFieldPort(name = "colorGreen", hasDefault = true) + private float mColorGreen = 0.8f; + + @GenerateFieldPort(name = "colorBlue", hasDefault = true) + private float mColorBlue = 0.0f; + + private final String mVertexShader = + "attribute vec4 aPosition;\n" + + "void main() {\n" + + " gl_Position = aPosition;\n" + + "}\n"; + + private final String mFixedColorFragmentShader = + "precision mediump float;\n" + + "uniform vec4 color;\n" + + "void main() {\n" + + " gl_FragColor = color;\n" + + "}\n"; + + private ShaderProgram mProgram; + + + public DrawRectFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addMaskedInputPort("box", ObjectFormat.fromClass(Quad.class, FrameFormat.TARGET_SIMPLE)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void prepare(FilterContext context) { + mProgram = new ShaderProgram(context, mVertexShader, mFixedColorFragmentShader); + } + + @Override + public void process(FilterContext env) { + // Get input frame + Frame imageFrame = pullInput("image"); + Frame boxFrame = pullInput("box"); + + // Get the box + Quad box = (Quad)boxFrame.getObjectValue(); + box = box.scaled(2.0f).translated(-1.0f, -1.0f); + + // Create output frame with copy of input + GLFrame output = (GLFrame)env.getFrameManager().duplicateFrame(imageFrame); + + // Draw onto output + output.focus(); + renderBox(box); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void renderBox(Quad box) { + final int FLOAT_SIZE = 4; + + // Get current values + float[] color = {mColorRed, mColorGreen, mColorBlue, 1f}; + float[] vertexValues = { box.p0.x, box.p0.y, + box.p1.x, box.p1.y, + box.p3.x, box.p3.y, + box.p2.x, box.p2.y }; + + // Set the program variables + mProgram.setHostValue("color", color); + mProgram.setAttributeValues("aPosition", vertexValues, 2); + mProgram.setVertexCount(4); + + // Draw + mProgram.beginDrawing(); + GLES20.glLineWidth(1.0f); + GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 0, 4); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java new file mode 100644 index 0000000..d8c88ee --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java @@ -0,0 +1,125 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Color; + +public class DuotoneFilter extends Filter { + + @GenerateFieldPort(name = "first_color", hasDefault = true) + private int mFirstColor = 0xFFFF0000; + + @GenerateFieldPort(name = "second_color", hasDefault = true) + private int mSecondColor = 0xFFFFFF00; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mDuotoneShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 first;\n" + + "uniform vec3 second;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = (color.r + color.g + color.b) * 0.3333;\n" + + " vec3 new_color = (1.0 - energy) * first + energy * second;\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public DuotoneFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mDuotoneShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Duotone does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + updateParameters(); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float first[] = { Color.red(mFirstColor)/255f, + Color.green(mFirstColor)/255f, + Color.blue(mFirstColor)/255f }; + float second[] = { Color.red(mSecondColor)/255f, + Color.green(mSecondColor)/255f, + Color.blue(mSecondColor)/255f }; + + mProgram.setHostValue("first", first); + mProgram.setHostValue("second", second); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java new file mode 100644 index 0000000..fc917a1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java @@ -0,0 +1,140 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class FillLightFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + @GenerateFieldPort(name = "strength", hasDefault = true) + private float mBacklight = 0f; + + private Program mProgram; + + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mFillLightShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float mult;\n" + + "uniform float igamma;\n" + + "varying vec2 v_texcoord;\n" + + "void main()\n" + + "{\n" + + " const vec3 color_weights = vec3(0.25, 0.5, 0.25);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float lightmask = dot(color.rgb, color_weights);\n" + + " float backmask = (1.0 - lightmask);\n" + + " vec3 ones = vec3(1.0, 1.0, 1.0);\n" + + " vec3 diff = pow(mult * color.rgb, igamma * ones) - color.rgb;\n" + + " diff = min(diff, 1.0);\n" + + " vec3 new_color = min(color.rgb + diff * backmask, 1.0);\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + public FillLightFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mFillLightShader); + Log.e("FillLight", "tile size: " + mTileSize); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FillLight does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + private void updateParameters() { + float fade_gamma = 0.3f; + float amt = 1.0f - mBacklight; + float mult = 1.0f / (amt * 0.7f + 0.3f); + float faded = fade_gamma + (1.0f -fade_gamma) *mult; + float igamma = 1.0f / faded; + + mProgram.setHostValue("mult", mult); + mProgram.setHostValue("igamma", igamma); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java new file mode 100644 index 0000000..8d38f98 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java @@ -0,0 +1,178 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.Math; +import java.util.Set; + +/** + * @hide + */ +public class FisheyeFilter extends Filter { + private static final String TAG = "FisheyeFilter"; + + // This parameter has range between 0 and 1. It controls the effect of radial distortion. + // The larger the value, the more prominent the distortion effect becomes (a straight line + // becomes a curve). + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private static final String mFisheyeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec2 center;\n" + + "uniform float alpha;\n" + + "uniform float bound;\n" + + "uniform float radius2;\n" + + "uniform float factor;\n" + + "uniform float inv_height;\n" + + "uniform float inv_width;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const float m_pi_2 = 1.570963;\n" + + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float radian = m_pi_2 - atan(alpha * sqrt(radius2 - dist * dist), dist);\n" + + " float scale = radian * factor / dist;\n" + + " vec2 new_coord = gl_FragCoord.xy * scale + (1.0 - scale) * center;\n" + + " new_coord.x *= inv_width;\n" + + " new_coord.y *= inv_height;\n" + + " vec4 color = texture2D(tex_sampler_0, new_coord);\n" + + " gl_FragColor = color;\n" + + "}\n"; + + public FisheyeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mFisheyeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter FisheyeFilter does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateProgramParams(); + } + } + + private void updateFrameSize(int width, int height) { + float center[] = {0.5f * width, 0.5f * height}; + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_width", 1.0f / width); + mProgram.setHostValue("inv_height", 1.0f / height); + + mWidth = width; + mHeight = height; + + updateProgramParams(); + } + + private void updateProgramParams() { + final float pi = 3.14159265f; + + float alpha = mScale * 2.0f + 0.75f; + float bound2 = 0.25f * (mWidth * mWidth + mHeight * mHeight); + float bound = (float) Math.sqrt(bound2); + float radius = 1.15f * bound; + float radius2 = radius * radius; + float max_radian = 0.5f * pi - + (float) Math.atan(alpha / bound * (float) Math.sqrt(radius2 - bound2)); + float factor = bound / max_radian; + + mProgram.setHostValue("radius2",radius2); + mProgram.setHostValue("factor", factor); + mProgram.setHostValue("alpha", (float) (mScale * 2.0 + 0.75)); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java new file mode 100644 index 0000000..3d319ea --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java @@ -0,0 +1,112 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; + +/** + * The FixedRotationFilter rotates the input image clockwise, it only accepts + * 4 rotation angles: 0, 90, 180, 270 + * @hide + */ +public class FixedRotationFilter extends Filter { + + @GenerateFieldPort(name = "rotation", hasDefault = true) + private int mRotation = 0; + + private ShaderProgram mProgram = null; + + public FixedRotationFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("image"); + if (mRotation == 0) { + pushOutput("image", input); + return; + } + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + int width = inputFormat.getWidth(); + int height = inputFormat.getHeight(); + Point p1 = new Point(0.0f, 0.0f); + Point p2 = new Point(1.0f, 0.0f); + Point p3 = new Point(0.0f, 1.0f); + Point p4 = new Point(1.0f, 1.0f); + Quad sourceRegion; + switch (((int)Math.round(mRotation / 90f)) % 4) { + case 1: + sourceRegion = new Quad(p3,p1,p4,p2); + outputFormat.setDimensions(height, width); + break; + case 2: + sourceRegion = new Quad(p4,p3,p2,p1); + break; + case 3: + sourceRegion = new Quad(p2,p4,p1,p3); + outputFormat.setDimensions(height, width); + break; + case 0: + default: + sourceRegion = new Quad(p1,p2,p3,p4); + break; + } + // Create output frame + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Set the source region + mProgram.setSourceRegion(sourceRegion); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java new file mode 100644 index 0000000..f8b857b --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java @@ -0,0 +1,120 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +/** + * @hide + */ +public class FlipFilter extends Filter { + + @GenerateFieldPort(name = "vertical", hasDefault = true) + private boolean mVertical = false; + + @GenerateFieldPort(name = "horizontal", hasDefault = true) + private boolean mHorizontal = false; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + public FlipFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + updateParameters(); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float x_origin = (mHorizontal) ? 1.0f : 0.0f; + float y_origin = (mVertical) ? 1.0f : 0.0f; + + float width = (mHorizontal) ? -1.0f : 1.0f; + float height = (mVertical) ? -1.0f : 1.0f; + + ((ShaderProgram) mProgram).setSourceRect(x_origin, y_origin, width, height); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java new file mode 100644 index 0000000..168a9c6 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java @@ -0,0 +1,187 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; + +import java.util.Random; + +public class GrainFilter extends Filter { + + private static final int RAND_THRESHOLD = 128; + + @GenerateFieldPort(name = "strength", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame = null; + private Random mRandom; + + private final String mGrainShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float scale;\n" + + "uniform float stepX;\n" + + "uniform float stepY;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " float noise = texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, -stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(-stepX, stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, -stepY)).r * 0.224;\n" + + " noise += texture2D(tex_sampler_1, v_texcoord + vec2(stepX, stepY)).r * 0.224;\n" + + " noise += 0.4448;\n" + + " noise *= scale;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float energy = 0.33333 * color.r + 0.33333 * color.g + 0.33333 * color.b;\n" + + " float mask = (1.0 - sqrt(energy));\n" + + " float weight = 1.0 - 1.333 * mask * noise;\n" + + " gl_FragColor = vec4(color.rgb * weight, color.a);\n" + + "}\n"; + + public GrainFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mGrainShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void updateParameters() { + mProgram.setHostValue("scale", mScale); + } + + private void updateFrameSize(int width, int height) { + mWidth = width; + mHeight = height; + + if (mProgram != null) { + mProgram.setHostValue("stepX", 0.5f / mWidth); + mProgram.setHostValue("stepY", 0.5f / mHeight); + updateParameters(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + updateParameters(); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = (mRandom.nextInt(256) < RAND_THRESHOLD) ? + mRandom.nextInt(256) : 0; + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + } + + if (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java new file mode 100644 index 0000000..858489b --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java @@ -0,0 +1,139 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public abstract class ImageCombineFilter extends Filter { + + protected Program mProgram; + protected String[] mInputNames; + protected String mOutputName; + protected String mParameterName; + protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED; + + public ImageCombineFilter(String name, + String[] inputNames, + String outputName, + String parameterName) { + super(name); + mInputNames = inputNames; + mOutputName = outputName; + mParameterName = parameterName; + } + + @Override + public void setupPorts() { + if (mParameterName != null) { + try { + Field programField = ImageCombineFilter.class.getDeclaredField("mProgram"); + addProgramPort(mParameterName, mParameterName, programField, float.class, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Internal Error: mProgram field not found!"); + } + } + for (String inputName : mInputNames) { + addMaskedInputPort(inputName, ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + addOutputBasedOnInput(mOutputName, mInputNames[0]); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private void assertAllInputTargetsMatch() { + int target = getInputFormat(mInputNames[0]).getTarget(); + for (String inputName : mInputNames) { + if (target != getInputFormat(inputName).getTarget()) { + throw new RuntimeException("Type mismatch of input formats in filter " + this + + ". All input frames must have the same target!"); + } + } + } + + @Override + public void process(FilterContext context) { + // Pull input frames + int i = 0; + Frame[] inputs = new Frame[mInputNames.length]; + for (String inputName : mInputNames) { + inputs[i++] = pullInput(inputName); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputs[0].getFormat()); + + // Make sure we have a program + updateProgramWithTarget(inputs[0].getFormat().getTarget(), context); + + // Process + mProgram.process(inputs, output); + + // Push output + pushOutput(mOutputName, output); + + // Release pushed frame + output.release(); + } + + protected void updateProgramWithTarget(int target, FilterContext context) { + if (target != mCurrentTarget) { + switch (target) { + case FrameFormat.TARGET_NATIVE: + mProgram = getNativeProgram(context); + break; + + case FrameFormat.TARGET_GPU: + mProgram = getShaderProgram(context); + break; + + default: + mProgram = null; + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for image filter " + + this + "!"); + } + initProgramInputs(mProgram, context); + mCurrentTarget = target; + } + } + + protected abstract Program getNativeProgram(FilterContext context); + + protected abstract Program getShaderProgram(FilterContext context); +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java new file mode 100644 index 0000000..a5405cb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java @@ -0,0 +1,64 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; + +import android.util.Log; + +import java.io.OutputStream; +import java.io.IOException; + +/** + * @hide + */ +public class ImageEncoder extends Filter { + + @GenerateFieldPort(name = "stream") + private OutputStream mOutputStream; + + @GenerateFieldPort(name = "quality", hasDefault = true) + private int mQuality = 80; + + public ImageEncoder(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED)); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("image"); + Bitmap bitmap = input.getBitmap(); + bitmap.compress(CompressFormat.JPEG, mQuality, mOutputStream); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java new file mode 100644 index 0000000..b996eb8 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java @@ -0,0 +1,142 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class ImageSlicer extends Filter { + + @GenerateFieldPort(name = "xSlices") + private int mXSlices; + + @GenerateFieldPort(name = "ySlices") + private int mYSlices; + + @GenerateFieldPort(name = "padSize") + private int mPadSize; + + // The current slice index from 0 to xSlices * ySlices + private int mSliceIndex; + + private Frame mOriginalFrame; + + private Program mProgram; + + private int mInputWidth; + private int mInputHeight; + + private int mSliceWidth; + private int mSliceHeight; + + private int mOutputWidth; + private int mOutputHeight; + + public ImageSlicer(String name) { + super(name); + mSliceIndex = 0; + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private void calcOutputFormatForInput(Frame frame) { + + // calculate the output size based on input size, xSlices, and ySlices + mInputWidth = frame.getFormat().getWidth(); + mInputHeight = frame.getFormat().getHeight(); + + mSliceWidth = (mInputWidth + mXSlices - 1) / mXSlices; + mSliceHeight = (mInputHeight + mYSlices - 1)/ mYSlices; + + mOutputWidth = mSliceWidth + mPadSize * 2; + mOutputHeight = mSliceHeight + mPadSize * 2; + } + + + @Override + public void process(FilterContext context) { + + // Get input frame + if (mSliceIndex == 0) { + mOriginalFrame = pullInput("image"); + calcOutputFormatForInput(mOriginalFrame); + } + + FrameFormat inputFormat = mOriginalFrame.getFormat(); + MutableFrameFormat outputFormat = inputFormat.mutableCopy(); + outputFormat.setDimensions(mOutputWidth, mOutputHeight); + + // Create output frame + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Create the program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + + // Calculate the four corner of the source region + int xSliceIndex = mSliceIndex % mXSlices; + int ySliceIndex = mSliceIndex / mXSlices; + + // TODO(rslin) : not sure shifting by 0.5 is needed. + float x0 = (xSliceIndex * mSliceWidth - mPadSize) / ((float) mInputWidth); + float y0 = (ySliceIndex * mSliceHeight - mPadSize) / ((float) mInputHeight); + + ((ShaderProgram) mProgram).setSourceRect(x0, y0, + ((float) mOutputWidth) / mInputWidth, + ((float) mOutputHeight) / mInputHeight); + + // Process + mProgram.process(mOriginalFrame, output); + mSliceIndex++; + + if (mSliceIndex == mXSlices * mYSlices) { + mSliceIndex = 0; + mOriginalFrame.release(); + setWaitsOnInputPort("image", true); + } else { + // Retain the original frame so it can be used next time. + mOriginalFrame.retain(); + setWaitsOnInputPort("image", false); + } + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java new file mode 100644 index 0000000..20aba91 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java @@ -0,0 +1,144 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +public class ImageStitcher extends Filter { + + @GenerateFieldPort(name = "xSlices") + private int mXSlices; + + @GenerateFieldPort(name = "ySlices") + private int mYSlices; + + @GenerateFieldPort(name = "padSize") + private int mPadSize; + + private Program mProgram; + private Frame mOutputFrame; + + private int mInputWidth; + private int mInputHeight; + + private int mImageWidth; + private int mImageHeight; + + private int mSliceWidth; + private int mSliceHeight; + + private int mSliceIndex; + + public ImageStitcher(String name) { + super(name); + mSliceIndex = 0; + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + private FrameFormat calcOutputFormatForInput(FrameFormat format) { + MutableFrameFormat outputFormat = format.mutableCopy(); + + mInputWidth = format.getWidth(); + mInputHeight = format.getHeight(); + + mSliceWidth = mInputWidth - 2 * mPadSize; + mSliceHeight = mInputHeight - 2 * mPadSize; + + mImageWidth = mSliceWidth * mXSlices; + mImageHeight = mSliceHeight * mYSlices; + + outputFormat.setDimensions(mImageWidth, mImageHeight); + return outputFormat; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat format = input.getFormat(); + + // Create output frame + if (mSliceIndex == 0) { + mOutputFrame = context.getFrameManager().newFrame(calcOutputFormatForInput(format)); + } else { + if ((format.getWidth() != mInputWidth) || + (format.getHeight() != mInputHeight)) { + // CHECK input format here + throw new RuntimeException("Image size should not change."); + } + } + + // Create the program if not created already + if (mProgram == null) { + mProgram = ShaderProgram.createIdentity(context); + } + + // TODO(rslin) : not sure shifting by 0.5 is needed. + float x0 = ((float) mPadSize) / mInputWidth; + float y0 = ((float) mPadSize) / mInputHeight; + + int outputOffsetX = (mSliceIndex % mXSlices) * mSliceWidth; + int outputOffsetY = (mSliceIndex / mXSlices) * mSliceHeight; + + float outputWidth = (float) Math.min(mSliceWidth, mImageWidth - outputOffsetX); + float outputHeight = (float) Math.min(mSliceHeight, mImageHeight - outputOffsetY); + + // We need to set the source rect as well because the input are padded images. + ((ShaderProgram) mProgram).setSourceRect(x0, y0, + outputWidth / mInputWidth, + outputHeight / mInputHeight); + + ((ShaderProgram) mProgram).setTargetRect(((float) outputOffsetX)/ mImageWidth, + ((float) outputOffsetY) / mImageHeight, + outputWidth / mImageWidth, + outputHeight / mImageHeight); + + // Process this tile + mProgram.process(input, mOutputFrame); + mSliceIndex++; + + // Push output + if (mSliceIndex == mXSlices * mYSlices) { + pushOutput("image", mOutputFrame); + mOutputFrame.release(); + mSliceIndex = 0; + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java new file mode 100644 index 0000000..400fd5d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java @@ -0,0 +1,60 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; + +/** + * @hide + */ +public class Invert extends SimpleImageFilter { + + private static final String mInvertShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor.r = 1.0 - color.r;\n" + + " gl_FragColor.g = 1.0 - color.g;\n" + + " gl_FragColor.b = 1.0 - color.b;\n" + + " gl_FragColor.a = color.a;\n" + + "}\n"; + + public Invert(String name) { + super(name, null); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + return new NativeProgram("filterpack_imageproc", "invert"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + return new ShaderProgram(context, mInvertShader); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java new file mode 100644 index 0000000..452a833 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java @@ -0,0 +1,218 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Random; + +public class LomoishFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private Frame mNoiseFrame; + private Random mRandom; + + private final String mLomoishShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float stepsizeX;\n" + + "uniform float stepsizeY;\n" + + "uniform float stepsize;\n" + + "uniform vec2 center;\n" + + "uniform float inv_max_dist;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + // sharpen + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" + + " vec2 coord;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" + + " coord.y = v_texcoord.y - stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x - stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " vec3 s_color = vec3(color.rgb + 0.3 * nbr_color);\n" + + // cross process + " vec3 c_color = vec3(0.0, 0.0, 0.0);\n" + + " float value;\n" + + " if (s_color.r < 0.5) {\n" + + " value = s_color.r;\n" + + " } else {\n" + + " value = 1.0 - s_color.r;\n" + + " }\n" + + " float red = 4.0 * value * value * value;\n" + + " if (s_color.r < 0.5) {\n" + + " c_color.r = red;\n" + + " } else {\n" + + " c_color.r = 1.0 - red;\n" + + " }\n" + + " if (s_color.g < 0.5) {\n" + + " value = s_color.g;\n" + + " } else {\n" + + " value = 1.0 - s_color.g;\n" + + " }\n" + + " float green = 2.0 * value * value;\n" + + " if (s_color.g < 0.5) {\n" + + " c_color.g = green;\n" + + " } else {\n" + + " c_color.g = 1.0 - green;\n" + + " }\n" + + " c_color.b = s_color.b * 0.5 + 0.25;\n" + + // blackwhite + " float dither = texture2D(tex_sampler_1, v_texcoord).r;\n" + + " vec3 xform = clamp((c_color.rgb - 0.15) * 1.53846, 0.0, 1.0);\n" + + " vec3 temp = clamp((color.rgb + stepsize - 0.15) * 1.53846, 0.0, 1.0);\n" + + " vec3 bw_color = clamp(xform + (temp - xform) * (dither - 0.5), 0.0, 1.0);\n" + + // vignette + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = 0.85 / (1.0 + exp((dist * inv_max_dist - 0.73) * 20.0)) + 0.15;\n" + + " gl_FragColor = vec4(bw_color * lumen, color.a);\n" + + "}\n"; + + public LomoishFilter(String name) { + super(name); + + mRandom = new Random(); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void tearDown(FilterContext context) { + if (mNoiseFrame != null) { + mNoiseFrame.release(); + mNoiseFrame = null; + } + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mLomoishShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + if (mProgram !=null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + + mProgram.setHostValue("stepsize", 1.0f / 255.0f); + mProgram.setHostValue("stepsizeX", 1.0f / mWidth); + mProgram.setHostValue("stepsizeY", 1.0f / mHeight); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + int[] buffer = new int[mWidth * mHeight]; + for (int i = 0; i < mWidth * mHeight; ++i) { + buffer[i] = mRandom.nextInt(255); + } + FrameFormat format = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + if (mNoiseFrame != null) { + mNoiseFrame.release(); + } + mNoiseFrame = context.getFrameManager().newFrame(format); + mNoiseFrame.setInts(buffer); + + initParameters(); + } + + if (mNoiseFrame != null && (mNoiseFrame.getFormat().getWidth() != mWidth || + mNoiseFrame.getFormat().getHeight() != mHeight)) { + throw new RuntimeException("Random map and imput image size mismatch!"); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + Frame[] inputs = {input, mNoiseFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java new file mode 100644 index 0000000..440d6a6 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java @@ -0,0 +1,103 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + + +public class NegativeFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mNegativeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = vec4(1.0 - color.rgb, color.a);\n" + + "}\n"; + + public NegativeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mNegativeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java new file mode 100644 index 0000000..bc2e553 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java @@ -0,0 +1,106 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class PosterizeFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mPosterizeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 pcolor;\n" + + " pcolor.r = (color.r >= 0.5) ? 0.75 : 0.25;\n" + + " pcolor.g = (color.g >= 0.5) ? 0.75 : 0.25;\n" + + " pcolor.b = (color.b >= 0.5) ? 0.75 : 0.25;\n" + + " gl_FragColor = vec4(pcolor, color.a);\n" + + "}\n"; + + public PosterizeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mPosterizeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java new file mode 100644 index 0000000..5632a5e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java @@ -0,0 +1,211 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PointF; +import android.util.Log; + +/** + * @hide + */ +public class RedEyeFilter extends Filter { + + private static final float RADIUS_RATIO = 0.06f; + private static final float MIN_RADIUS = 10.0f; + private static final float DEFAULT_RED_INTENSITY = 1.30f; + + @GenerateFieldPort(name = "centers") + private float[] mCenters; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Frame mRedEyeFrame; + private Bitmap mRedEyeBitmap; + + private final Canvas mCanvas = new Canvas(); + private final Paint mPaint = new Paint(); + + private float mRadius; + + private int mWidth = 0; + private int mHeight = 0; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mRedEyeShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float intensity;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " gl_FragColor = vec4(mask.a, mask.a, mask.a, 1.0) * intensity + color * (1.0 - intensity);\n" + + " if (mask.a > 0.0) {\n" + + " gl_FragColor.r = 0.0;\n" + + " float green_blue = color.g + color.b;\n" + + " float red_intensity = color.r / green_blue;\n" + + " if (red_intensity > intensity) {\n" + + " color.r = 0.5 * green_blue;\n" + + " }\n" + + " }\n" + + " gl_FragColor = color;\n" + + "}\n"; + + public RedEyeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mRedEyeShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter RedEye does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void tearDown(FilterContext context) { + if (mRedEyeBitmap != null) { + mRedEyeBitmap.recycle(); + mRedEyeBitmap = null; + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + + createRedEyeBitmap(); + } + + createRedEyeFrame(context); + + // Process + Frame[] inputs = {input, mRedEyeFrame}; + mProgram.process(inputs, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + + // Release unused frame + mRedEyeFrame.release(); + mRedEyeFrame = null; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateProgramParams(); + } + } + + private void createRedEyeBitmap() { + if (mRedEyeBitmap != null) { + mRedEyeBitmap.recycle(); + } + + int bitmapWidth = mWidth / 2; + int bitmapHeight = mHeight / 2; + + mRedEyeBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); + mCanvas.setBitmap(mRedEyeBitmap); + mPaint.setColor(Color.WHITE); + mRadius = Math.max(MIN_RADIUS, RADIUS_RATIO * Math.min(bitmapWidth, bitmapHeight)); + + updateProgramParams(); + } + + private void createRedEyeFrame(FilterContext context) { + FrameFormat format = ImageFormat.create(mRedEyeBitmap.getWidth() , + mRedEyeBitmap.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mRedEyeFrame = context.getFrameManager().newFrame(format); + mRedEyeFrame.setBitmap(mRedEyeBitmap); + } + + private void updateProgramParams() { + mProgram.setHostValue("intensity", DEFAULT_RED_INTENSITY); + + if ( mCenters.length % 2 == 1) { + throw new RuntimeException("The size of center array must be even."); + } + + if (mRedEyeBitmap != null) { + for (int i = 0; i < mCenters.length; i += 2) { + mCanvas.drawCircle(mCenters[i] * mRedEyeBitmap.getWidth(), + mCenters[i + 1] * mRedEyeBitmap.getHeight(), + mRadius, mPaint); + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java new file mode 100644 index 0000000..411e061 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java @@ -0,0 +1,124 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.opengl.GLES20; + +/** + * @hide + */ +public class ResizeFilter extends Filter { + + @GenerateFieldPort(name = "owidth") + private int mOWidth; + @GenerateFieldPort(name = "oheight") + private int mOHeight; + @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true) + private boolean mKeepAspectRatio = false; + @GenerateFieldPort(name = "generateMipMap", hasDefault = true) + private boolean mGenerateMipMap = false; + + private Program mProgram; + private FrameFormat mLastFormat = null; + + private MutableFrameFormat mOutputFormat; + private int mInputChannels; + + public ResizeFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + protected void createProgram(FilterContext context, FrameFormat format) { + if (mLastFormat != null && mLastFormat.getTarget() == format.getTarget()) return; + mLastFormat = format; + switch (format.getTarget()) { + case FrameFormat.TARGET_NATIVE: + throw new RuntimeException("Native ResizeFilter not implemented yet!"); + + + case FrameFormat.TARGET_GPU: + ShaderProgram prog = ShaderProgram.createIdentity(context); + mProgram = prog; + break; + + default: + throw new RuntimeException("ResizeFilter could not create suitable program!"); + } + } + @Override + public void process(FilterContext env) { + // Get input frame + Frame input = pullInput("image"); + createProgram(env, input.getFormat()); + + // Create output frame + MutableFrameFormat outputFormat = input.getFormat().mutableCopy(); + if (mKeepAspectRatio) { + FrameFormat inputFormat = input.getFormat(); + mOHeight = mOWidth * inputFormat.getHeight() / inputFormat.getWidth(); + } + outputFormat.setDimensions(mOWidth, mOHeight); + Frame output = env.getFrameManager().newFrame(outputFormat); + + // Process + if (mGenerateMipMap) { + GLFrame mipmapped = (GLFrame)env.getFrameManager().newFrame(input.getFormat()); + mipmapped.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + mipmapped.setDataFromFrame(input); + mipmapped.generateMipMap(); + mProgram.process(mipmapped, output); + mipmapped.release(); + } else { + mProgram.process(input, output); + } + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java new file mode 100644 index 0000000..3da7939 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java @@ -0,0 +1,156 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; +import android.util.Log; + +/** + * @hide + */ +public class RotateFilter extends Filter { + + @GenerateFieldPort(name = "angle") + private int mAngle; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private int mOutputWidth; + private int mOutputHeight; + + public RotateFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + shaderProgram.setClearsOutput(true); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + mOutputWidth = mWidth; + mOutputHeight = mHeight; + + updateParameters(); + } + + // Create output frame + FrameFormat outputFormat = ImageFormat.create(mOutputWidth, mOutputHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + + Frame output = context.getFrameManager().newFrame(outputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float sinTheta; + float cosTheta; + + if (mAngle % 90 == 0) { + if (mAngle % 180 == 0) { + sinTheta = 0f; + cosTheta = (mAngle % 360 == 0) ? 1f:-1f; + } else { + cosTheta = 0f; + sinTheta = ((mAngle + 90) % 360 == 0) ? -1f:1f; + + mOutputWidth = mHeight; + mOutputHeight = mWidth; + } + } else { + throw new RuntimeException("degree has to be multiply of 90."); + } + + Point x0 = new Point(0.5f * (-cosTheta + sinTheta + 1f), + 0.5f * (-sinTheta - cosTheta + 1f)); + Point x1 = new Point(0.5f * (cosTheta + sinTheta + 1f), + 0.5f * (sinTheta - cosTheta + 1f)); + Point x2 = new Point(0.5f * (-cosTheta - sinTheta + 1f), + 0.5f * (-sinTheta + cosTheta + 1f)); + Point x3 = new Point(0.5f * (cosTheta - sinTheta + 1f), + 0.5f * (sinTheta + cosTheta + 1f)); + Quad quad = new Quad(x0, x1, x2, x3); + ((ShaderProgram) mProgram).setTargetRegion(quad); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java new file mode 100644 index 0000000..b83af39 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java @@ -0,0 +1,170 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class SaturateFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mBenProgram; + private Program mHerfProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mBenSaturateShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "uniform float shift;\n" + + "uniform vec3 weights;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float kv = dot(color.rgb, weights) + shift;\n" + + " vec3 new_color = scale * color.rgb + (1.0 - scale) * kv;\n" + + " gl_FragColor = vec4(new_color, color.a);\n" + + "}\n"; + + private final String mHerfSaturateShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 weights;\n" + + "uniform vec3 exponents;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float de = dot(color.rgb, weights);\n" + + " float inv_de = 1.0 / de;\n" + + " vec3 new_color = de * pow(color.rgb * inv_de, exponents);\n" + + " float max_color = max(max(max(new_color.r, new_color.g), new_color.b), 1.0);\n" + + " gl_FragColor = vec4(new_color / max_color, color.a);\n" + + "}\n"; + + + public SaturateFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mBenSaturateShader); + shaderProgram.setMaximumTileSize(mTileSize); + mBenProgram = shaderProgram; + + shaderProgram = new ShaderProgram(context, mHerfSaturateShader); + shaderProgram.setMaximumTileSize(mTileSize); + mHerfProgram = shaderProgram; + + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mBenProgram != null && mHerfProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mBenProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + if (mScale > 0.0f) { + mHerfProgram.process(input, output); + } else { + mBenProgram.process(input, output); + } + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float shift = 1.0f / 255.0f; + float weights[] = { 2f/8f, 5f/8f, 1f/8f}; + + mBenProgram.setHostValue("weights", weights); + mBenProgram.setHostValue("shift", shift); + + mHerfProgram.setHostValue("weights", weights); + + updateParameters(); + } + + private void updateParameters() { + + if (mScale > 0.0f) { + float exponents[] = new float[3]; + + exponents[0] = (0.9f * mScale) + 1.0f; + exponents[1] = (2.1f * mScale) + 1.0f; + exponents[2] = (2.7f * mScale) + 1.0f; + + mHerfProgram.setHostValue("exponents", exponents); + } else { + mBenProgram.setHostValue("scale", 1.0f + mScale); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java new file mode 100644 index 0000000..7a83fdf --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java @@ -0,0 +1,111 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class SepiaFilter extends Filter { + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mSepiaShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform mat3 matrix;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec3 new_color = min(matrix * color.rgb, 1.0);\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public SepiaFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mSepiaShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float weights[] = { 805.0f / 2048.0f, 715.0f / 2048.0f, 557.0f / 2048.0f, + 1575.0f / 2048.0f, 1405.0f / 2048.0f, 1097.0f / 2048.0f, + 387.0f / 2048.0f, 344.0f / 2048.0f, 268.0f / 2048.0f }; + mProgram.setHostValue("matrix", weights); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java new file mode 100644 index 0000000..256b769 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java @@ -0,0 +1,153 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.util.Set; + +public class SharpenFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mSharpenShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float scale;\n" + + "uniform float stepsizeX;\n" + + "uniform float stepsizeY;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec3 nbr_color = vec3(0.0, 0.0, 0.0);\n" + + " vec2 coord;\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " coord.x = v_texcoord.x - 0.5 * stepsizeX;\n" + + " coord.y = v_texcoord.y - stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x - stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y - 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " coord.x = v_texcoord.x + stepsizeX;\n" + + " coord.y = v_texcoord.y + 0.5 * stepsizeY;\n" + + " nbr_color += texture2D(tex_sampler_0, coord).rgb - color.rgb;\n" + + " gl_FragColor = vec4(color.rgb - 2.0 * scale * nbr_color, color.a);\n" + + "}\n"; + + public SharpenFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mSharpenShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + updateFrameSize(inputFormat.getWidth(), inputFormat.getHeight()); + } + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateFrameSize(int width, int height) { + mWidth = width; + mHeight = height; + + if (mProgram != null) { + mProgram.setHostValue("stepsizeX", 1.0f / mWidth); + mProgram.setHostValue("stepsizeY", 1.0f / mHeight); + updateParameters(); + } + } + + private void updateParameters() { + mProgram.setHostValue("scale", mScale); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java new file mode 100644 index 0000000..f4fc271 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java @@ -0,0 +1,116 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import java.lang.reflect.Field; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public abstract class SimpleImageFilter extends Filter { + + protected int mCurrentTarget = FrameFormat.TARGET_UNSPECIFIED; + protected Program mProgram; + protected String mParameterName; + + public SimpleImageFilter(String name, String parameterName) { + super(name); + mParameterName = parameterName; + } + + @Override + public void setupPorts() { + if (mParameterName != null) { + try { + Field programField = SimpleImageFilter.class.getDeclaredField("mProgram"); + addProgramPort(mParameterName, mParameterName, programField, float.class, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException("Internal Error: mProgram field not found!"); + } + } + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Create program if not created already + updateProgramWithTarget(inputFormat.getTarget(), context); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + protected void updateProgramWithTarget(int target, FilterContext context) { + if (target != mCurrentTarget) { + switch (target) { + case FrameFormat.TARGET_NATIVE: + mProgram = getNativeProgram(context); + break; + + case FrameFormat.TARGET_GPU: + mProgram = getShaderProgram(context); + break; + + default: + mProgram = null; + break; + } + if (mProgram == null) { + throw new RuntimeException("Could not create a program for image filter " + this + "!"); + } + initProgramInputs(mProgram, context); + mCurrentTarget = target; + } + } + + protected abstract Program getNativeProgram(FilterContext context); + + protected abstract Program getShaderProgram(FilterContext context); +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java new file mode 100644 index 0000000..c9f097d --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java @@ -0,0 +1,154 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; +import android.util.Log; + +/** + * @hide + */ +public class StraightenFilter extends Filter { + + @GenerateFieldPort(name = "angle", hasDefault = true) + private float mAngle = 0f; + + @GenerateFieldPort(name = "maxAngle", hasDefault = true) + private float mMaxAngle = 45f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f; + + public StraightenFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = ShaderProgram.createIdentity(context); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Create output frame + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + updateParameters(); + } + + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void updateParameters() { + float cosTheta = (float) Math.cos(mAngle * DEGREE_TO_RADIAN); + float sinTheta = (float) Math.sin(mAngle * DEGREE_TO_RADIAN); + + if (mMaxAngle <= 0) + throw new RuntimeException("Max angle is out of range (0-180)."); + mMaxAngle = (mMaxAngle > 90) ? 90 : mMaxAngle; + + Point p0 = new Point(-cosTheta * mWidth + sinTheta * mHeight, + -sinTheta * mWidth - cosTheta * mHeight); + + Point p1 = new Point(cosTheta * mWidth + sinTheta * mHeight, + sinTheta * mWidth - cosTheta * mHeight); + + Point p2 = new Point(-cosTheta * mWidth - sinTheta * mHeight, + -sinTheta * mWidth + cosTheta * mHeight); + + Point p3 = new Point(cosTheta * mWidth - sinTheta * mHeight, + sinTheta * mWidth + cosTheta * mHeight); + + float maxWidth = (float) Math.max(Math.abs(p0.x), Math.abs(p1.x)); + float maxHeight = (float) Math.max(Math.abs(p0.y), Math.abs(p1.y)); + + float scale = 0.5f * Math.min( mWidth / maxWidth, + mHeight / maxHeight); + + p0.set(scale * p0.x / mWidth + 0.5f, scale * p0.y / mHeight + 0.5f); + p1.set(scale * p1.x / mWidth + 0.5f, scale * p1.y / mHeight + 0.5f); + p2.set(scale * p2.x / mWidth + 0.5f, scale * p2.y / mHeight + 0.5f); + p3.set(scale * p3.x / mWidth + 0.5f, scale * p3.y / mHeight + 0.5f); + + Quad quad = new Quad(p0, p1, p2, p3); + ((ShaderProgram) mProgram).setSourceRegion(quad); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java new file mode 100644 index 0000000..0da54a5 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java @@ -0,0 +1,133 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.Color; + +public class TintFilter extends Filter { + + @GenerateFieldPort(name = "tint", hasDefault = true) + private int mTint = 0xFF0000FF; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final String mTintShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform vec3 tint;\n" + + "uniform vec3 color_ratio;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float avg_color = dot(color_ratio, color.rgb);\n" + + " vec3 new_color = min(0.8 * avg_color + 0.2 * tint, 1.0);\n" + + " gl_FragColor = vec4(new_color.rgb, color.a);\n" + + "}\n"; + + public TintFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mTintShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + + private void initParameters() { + float color_ratio[] = {0.21f, 0.71f, 0.07f}; + mProgram.setHostValue("color_ratio", color_ratio); + + updateParameters(); + } + + private void updateParameters() { + float tint_color[] = {Color.red(mTint) / 255f, + Color.green(mTint) / 255f, + Color.blue(mTint) / 255f }; + + mProgram.setHostValue("tint", tint_color); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java new file mode 100644 index 0000000..00e7bf4 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java @@ -0,0 +1,90 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class ToGrayFilter extends SimpleImageFilter { + + @GenerateFieldPort(name = "invertSource", hasDefault = true) + private boolean mInvertSource = false; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private MutableFrameFormat mOutputFormat; + + private static final String mColorToGray4Shader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " float y = dot(color, vec4(0.299, 0.587, 0.114, 0));\n" + + " gl_FragColor = vec4(y, y, y, color.a);\n" + + "}\n"; + + public ToGrayFilter(String name) { + super(name, null); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + protected Program getNativeProgram(FilterContext context) { + throw new RuntimeException("Native toGray not implemented yet!"); + } + + @Override + protected Program getShaderProgram(FilterContext context) { + int inputChannels = getInputFormat("image").getBytesPerSample(); + if (inputChannels != 4) { + throw new RuntimeException("Unsupported GL input channels: " + + inputChannels + "! Channels must be 4!"); + } + ShaderProgram program = new ShaderProgram(context, mColorToGray4Shader); + program.setMaximumTileSize(mTileSize); + if (mInvertSource) + program.setSourceRect(0.0f, 1.0f, 1.0f, -1.0f); + return program; + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java new file mode 100644 index 0000000..bc4a65e --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java @@ -0,0 +1,143 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +import java.lang.Math; +/** + * @hide + */ +public class ToPackedGrayFilter extends Filter { + + @GenerateFieldPort(name = "owidth", hasDefault = true) + private int mOWidth = FrameFormat.SIZE_UNSPECIFIED; + @GenerateFieldPort(name = "oheight", hasDefault = true) + private int mOHeight = FrameFormat.SIZE_UNSPECIFIED; + @GenerateFieldPort(name = "keepAspectRatio", hasDefault = true) + private boolean mKeepAspectRatio = false; + + private Program mProgram; + + private final String mColorToPackedGrayShader = + "precision mediump float;\n" + + "const vec4 coeff_y = vec4(0.299, 0.587, 0.114, 0);\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float pix_stride;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " for (int i = 0; i < 4; ++i) {\n" + + " vec4 p = texture2D(tex_sampler_0,\n" + + " v_texcoord + vec2(pix_stride * float(i), 0.0));\n" + + " gl_FragColor[i] = dot(p, coeff_y);\n" + + " }\n" + + "}\n"; + + public ToPackedGrayFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return convertInputFormat(inputFormat); + } + + private void checkOutputDimensions(int outputWidth, int outputHeight) { + if (outputWidth <= 0 || outputHeight <= 0) { + throw new RuntimeException("Invalid output dimensions: " + + outputWidth + " " + outputHeight); + } + } + + private FrameFormat convertInputFormat(FrameFormat inputFormat) { + int ow = mOWidth; + int oh = mOHeight; + int w = inputFormat.getWidth(); + int h = inputFormat.getHeight(); + if (mOWidth == FrameFormat.SIZE_UNSPECIFIED) { + ow = w; + } + if (mOHeight == FrameFormat.SIZE_UNSPECIFIED) { + oh = h; + } + if (mKeepAspectRatio) { + // if keep aspect ratio, use the bigger dimension to determine the + // final output size + if (w > h) { + ow = Math.max(ow, oh); + oh = ow * h / w; + } else { + oh = Math.max(ow, oh); + ow = oh * w / h; + } + } + ow = (ow > 0 && ow < 4) ? 4 : (ow / 4) * 4; // ensure width is multiple of 4 + return ImageFormat.create(ow, oh, + ImageFormat.COLORSPACE_GRAY, + FrameFormat.TARGET_NATIVE); + } + + @Override + public void prepare(FilterContext context) { + mProgram = new ShaderProgram(context, mColorToPackedGrayShader); + } + + @Override + public void process(FilterContext context) { + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + FrameFormat outputFormat = convertInputFormat(inputFormat); + int ow = outputFormat.getWidth(); + int oh = outputFormat.getHeight(); + checkOutputDimensions(ow, oh); + mProgram.setHostValue("pix_stride", 1.0f / ow); + + // Do the RGBA to luminance conversion. + MutableFrameFormat tempFrameFormat = inputFormat.mutableCopy(); + tempFrameFormat.setDimensions(ow / 4, oh); + Frame temp = context.getFrameManager().newFrame(tempFrameFormat); + mProgram.process(input, temp); + + // Read frame from GPU to CPU. + Frame output = context.getFrameManager().newFrame(outputFormat); + output.setDataFromFrame(temp); + temp.release(); + + // Push output and yield ownership. + pushOutput("image", output); + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java new file mode 100644 index 0000000..ab4814f --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java @@ -0,0 +1,103 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +/** + * @hide + */ +public class ToRGBAFilter extends Filter { + + private int mInputBPP; + private Program mProgram; + private FrameFormat mLastFormat = null; + + public ToRGBAFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_NATIVE); + mask.setDimensionCount(2); + addMaskedInputPort("image", mask); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return getConvertedFormat(inputFormat); + } + + public FrameFormat getConvertedFormat(FrameFormat format) { + MutableFrameFormat result = format.mutableCopy(); + result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGBA); + result.setBytesPerSample(4); + return result; + } + + public void createProgram(FilterContext context, FrameFormat format) { + mInputBPP = format.getBytesPerSample(); + if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return; + mLastFormat = format; + switch (mInputBPP) { + case 1: + mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgba"); + break; + case 3: + mProgram = new NativeProgram("filterpack_imageproc", "rgb_to_rgba"); + break; + default: + throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!"); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + createProgram(context, input.getFormat()); + + // Create output frame + Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat())); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java new file mode 100644 index 0000000..9258502 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java @@ -0,0 +1,103 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.util.Log; + +/** + * @hide + */ +public class ToRGBFilter extends Filter { + + private int mInputBPP; + private Program mProgram; + private FrameFormat mLastFormat = null; + + public ToRGBFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + MutableFrameFormat mask = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_NATIVE); + mask.setDimensionCount(2); + addMaskedInputPort("image", mask); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return getConvertedFormat(inputFormat); + } + + public FrameFormat getConvertedFormat(FrameFormat format) { + MutableFrameFormat result = format.mutableCopy(); + result.setMetaValue(ImageFormat.COLORSPACE_KEY, ImageFormat.COLORSPACE_RGB); + result.setBytesPerSample(3); + return result; + } + + public void createProgram(FilterContext context, FrameFormat format) { + mInputBPP = format.getBytesPerSample(); + if (mLastFormat != null && mLastFormat.getBytesPerSample() == mInputBPP) return; + mLastFormat = format; + switch (mInputBPP) { + case 1: + mProgram = new NativeProgram("filterpack_imageproc", "gray_to_rgb"); + break; + case 4: + mProgram = new NativeProgram("filterpack_imageproc", "rgba_to_rgb"); + break; + default: + throw new RuntimeException("Unsupported BytesPerPixel: " + mInputBPP + "!"); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + createProgram(context, input.getFormat()); + + // Create output frame + Frame output = context.getFrameManager().newFrame(getConvertedFormat(input.getFormat())); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java new file mode 100644 index 0000000..2d78fff --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java @@ -0,0 +1,153 @@ +/* + * 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 android.filterpacks.imageproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +public class VignetteFilter extends Filter { + + @GenerateFieldPort(name = "scale", hasDefault = true) + private float mScale = 0f; + + @GenerateFieldPort(name = "tile_size", hasDefault = true) + private int mTileSize = 640; + + private Program mProgram; + + private int mWidth = 0; + private int mHeight = 0; + private int mTarget = FrameFormat.TARGET_UNSPECIFIED; + + private final float mSlope = 20.0f; + private final float mShade = 0.85f; + + private final String mVignetteShader = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform float range;\n" + + "uniform float inv_max_dist;\n" + + "uniform float shade;\n" + + "uniform vec2 center;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " const float slope = 20.0;\n" + + " float dist = distance(gl_FragCoord.xy, center);\n" + + " float lumen = shade / (1.0 + exp((dist * inv_max_dist - range) * slope)) + (1.0 - shade);\n" + + " vec4 color = texture2D(tex_sampler_0, v_texcoord);\n" + + " gl_FragColor = vec4(color.rgb * lumen, color.a);\n" + + "}\n"; + + public VignetteFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("image", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + addOutputBasedOnInput("image", "image"); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + public void initProgram(FilterContext context, int target) { + switch (target) { + case FrameFormat.TARGET_GPU: + ShaderProgram shaderProgram = new ShaderProgram(context, mVignetteShader); + shaderProgram.setMaximumTileSize(mTileSize); + mProgram = shaderProgram; + break; + + default: + throw new RuntimeException("Filter Sharpen does not support frames of " + + "target " + target + "!"); + } + mTarget = target; + } + + private void initParameters() { + if (mProgram != null) { + float centerX = (float) (0.5 * mWidth); + float centerY = (float) (0.5 * mHeight); + float center[] = {centerX, centerY}; + float max_dist = (float) Math.sqrt(centerX * centerX + centerY * centerY); + + mProgram.setHostValue("center", center); + mProgram.setHostValue("inv_max_dist", 1.0f / max_dist); + mProgram.setHostValue("shade", mShade); + + updateParameters(); + } + } + + private void updateParameters() { + // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3 + // which means no vignette at all because the luminousity difference is + // less than 1/256 and will cause nothing. + mProgram.setHostValue("range", 1.30f - (float) Math.sqrt(mScale) * 0.7f); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mProgram != null) { + updateParameters(); + } + } + + @Override + public void process(FilterContext context) { + // Get input frame + Frame input = pullInput("image"); + FrameFormat inputFormat = input.getFormat(); + + // Create program if not created already + if (mProgram == null || inputFormat.getTarget() != mTarget) { + initProgram(context, inputFormat.getTarget()); + } + + // Check if the frame size has changed + if (inputFormat.getWidth() != mWidth || inputFormat.getHeight() != mHeight) { + mWidth = inputFormat.getWidth(); + mHeight = inputFormat.getHeight(); + initParameters(); + } + + // Create output frame + Frame output = context.getFrameManager().newFrame(inputFormat); + + // Process + mProgram.process(input, output); + + // Push output + pushOutput("image", output); + + // Release pushed frame + output.release(); + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java new file mode 100644 index 0000000..1cf48b0 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.imageproc; diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java new file mode 100644 index 0000000..7e2b4cf --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java @@ -0,0 +1,66 @@ +/* + * 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 android.filterpacks.numeric; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.format.ObjectFormat; + +import java.lang.Math; + +/** + * @hide + */ +public class SinWaveFilter extends Filter { + + @GenerateFieldPort(name = "stepSize", hasDefault = true) + private float mStepSize = 0.05f; + + private float mValue = 0.0f; + + private FrameFormat mOutputFormat; + + public SinWaveFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(Float.class, FrameFormat.TARGET_SIMPLE); + addOutputPort("value", mOutputFormat); + } + + @Override + public void open(FilterContext env) { + mValue = 0.0f; + } + + @Override + public void process(FilterContext env) { + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(((float)Math.sin(mValue) + 1.0f) / 2.0f); + pushOutput("value", output); + mValue += mStepSize; + output.release(); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java new file mode 100644 index 0000000..55088eb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.numeric; diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java new file mode 100644 index 0000000..51f29f3 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java @@ -0,0 +1,61 @@ +/* + * 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 android.filterpacks.performance; + +/** + * @hide + */ +public class Throughput { + + private final int mTotalFrames; + private final int mPeriodFrames; + private final int mPeriodTime; + private final int mPixels; + + public Throughput(int totalFrames, int periodFrames, int periodTime, int pixels) { + mTotalFrames = totalFrames; + mPeriodFrames = periodFrames; + mPeriodTime = periodTime; + mPixels = pixels; + } + + public int getTotalFrameCount() { + return mTotalFrames; + } + + public int getPeriodFrameCount() { + return mPeriodFrames; + } + + public int getPeriodTime() { + return mPeriodTime; + } + + public float getFramesPerSecond() { + return mPeriodFrames / (float)mPeriodTime; + } + + public float getNanosPerPixel() { + double frameTimeInNanos = (mPeriodTime / (double)mPeriodFrames) * 1000000.0; + return (float)(frameTimeInNanos / mPixels); + } + + public String toString() { + return getFramesPerSecond() + " FPS"; + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java new file mode 100644 index 0000000..ac837ed --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java @@ -0,0 +1,103 @@ +/* + * 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 android.filterpacks.performance; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.format.ObjectFormat; +import android.os.SystemClock; + +/** + * @hide + */ +public class ThroughputFilter extends Filter { + + @GenerateFieldPort(name = "period", hasDefault = true) + private int mPeriod = 5; + + private long mLastTime = 0; + + private int mTotalFrameCount = 0; + private int mPeriodFrameCount = 0; + + private FrameFormat mOutputFormat; + + public ThroughputFilter(String name) { + super(name); + } + + @Override + public void setupPorts() { + // Add input ports + addInputPort("frame"); + + // Add output ports + mOutputFormat = ObjectFormat.fromClass(Throughput.class, FrameFormat.TARGET_SIMPLE); + addOutputBasedOnInput("frame", "frame"); + addOutputPort("throughput", mOutputFormat); + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return inputFormat; + } + + @Override + public void open(FilterContext env) { + mTotalFrameCount = 0; + mPeriodFrameCount = 0; + mLastTime = 0; + } + + @Override + public void process(FilterContext context) { + // Pass through input frame + Frame input = pullInput("frame"); + pushOutput("frame", input); + + // Update stats + ++mTotalFrameCount; + ++mPeriodFrameCount; + + // Check clock + if (mLastTime == 0) { + mLastTime = SystemClock.elapsedRealtime(); + } + long curTime = SystemClock.elapsedRealtime(); + + // Output throughput info if time period is up + if ((curTime - mLastTime) >= (mPeriod * 1000)) { + FrameFormat inputFormat = input.getFormat(); + int pixelCount = inputFormat.getWidth() * inputFormat.getHeight(); + Throughput throughput = new Throughput(mTotalFrameCount, + mPeriodFrameCount, + mPeriod, + pixelCount); + Frame throughputFrame = context.getFrameManager().newFrame(mOutputFormat); + throughputFrame.setObjectValue(throughput); + pushOutput("throughput", throughputFrame); + mLastTime = curTime; + mPeriodFrameCount = 0; + } + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java new file mode 100644 index 0000000..8b77bbb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/performance/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.performance; diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java new file mode 100644 index 0000000..8c7cd69 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java @@ -0,0 +1,49 @@ +/* + * 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 android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.format.ObjectFormat; +import android.util.Log; + +/** + * @hide + */ +public class StringLogger extends Filter { + + public StringLogger(String name) { + super(name); + } + + @Override + public void setupPorts() { + addMaskedInputPort("string", ObjectFormat.fromClass(Object.class, + FrameFormat.TARGET_SIMPLE)); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("string"); + String inputString = input.getObjectValue().toString(); + Log.i("StringLogger", inputString); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java new file mode 100644 index 0000000..cc33b89 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/StringSource.java @@ -0,0 +1,59 @@ +/* + * 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 android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class StringSource extends Filter { + + @GenerateFieldPort(name = "stringValue") + private String mString; + + private FrameFormat mOutputFormat; + + public StringSource(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE); + addOutputPort("string", mOutputFormat); + } + + @Override + public void process(FilterContext env) { + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(mString); + output.setTimestamp(Frame.TIMESTAMP_UNKNOWN); + pushOutput("string", output); + closeOutputPort("string"); + } + + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java new file mode 100644 index 0000000..0cf3477 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java @@ -0,0 +1,55 @@ +/* + * 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 android.filterpacks.text; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.format.ObjectFormat; + +/** + * @hide + */ +public class ToUpperCase extends Filter { + + private FrameFormat mOutputFormat; + + public ToUpperCase(String name) { + super(name); + } + + @Override + public void setupPorts() { + mOutputFormat = ObjectFormat.fromClass(String.class, FrameFormat.TARGET_SIMPLE); + addMaskedInputPort("mixedcase", mOutputFormat); + addOutputPort("uppercase", mOutputFormat); + } + + @Override + public void process(FilterContext env) { + Frame input = pullInput("mixedcase"); + String inputString = (String)input.getObjectValue(); + + Frame output = env.getFrameManager().newFrame(mOutputFormat); + output.setObjectValue(inputString.toUpperCase()); + + pushOutput("uppercase", output); + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/text/package-info.java b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java new file mode 100644 index 0000000..371d3c1 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/text/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.text; diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java new file mode 100644 index 0000000..a5c1ccb --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java @@ -0,0 +1,275 @@ +/* + * 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 android.filterpacks.ui; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterSurfaceView; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceRenderFilter extends Filter implements SurfaceHolder.Callback { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + + /** Required. Sets the destination filter surface view for this + * node. + */ + @GenerateFinalPort(name = "surfaceView") + private FilterSurfaceView mSurfaceView; + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + private boolean mIsBound = false; + + private ShaderProgram mProgram; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private int mScreenWidth; + private int mScreenHeight; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceRenderFilter"; + + public SurfaceRenderFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a SurfaceView + if (mSurfaceView == null) { + throw new RuntimeException("NULL SurfaceView passed to SurfaceRenderFilter"); + } + + // Add input port + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearsOutput(true); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + updateRenderMode(); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = ImageFormat.create(mSurfaceView.getWidth(), + mSurfaceView.getHeight(), + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + } + + @Override + public void open(FilterContext context) { + // Bind surface view to us. This will emit a surfaceCreated and surfaceChanged call that + // will update our screen width and height. + mSurfaceView.unbind(); + mSurfaceView.bindToListener(this, context.getGLEnvironment()); + } + + @Override + public void process(FilterContext context) { + // Make sure we are bound to a surface before rendering + if (!mIsBound) { + Log.w("SurfaceRenderFilter", + this + ": Ignoring frame as there is no surface to render to!"); + return; + } + + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = mSurfaceView.getGLEnv(); + if (glEnv != context.getGLEnvironment()) { + throw new RuntimeException("Surface created under different GLEnvironment!"); + } + + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat()); + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceView.getSurfaceId()); + + // Process + mProgram.process(gpuFrame, mScreen); + + // And swap buffers + glEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + updateTargetRect(); + } + + @Override + public void close(FilterContext context) { + mSurfaceView.unbind(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + @Override + public synchronized void surfaceCreated(SurfaceHolder holder) { + mIsBound = true; + } + + @Override + public synchronized void surfaceChanged(SurfaceHolder holder, + int format, + int width, + int height) { + // If the screen is null, we do not care about surface changes (yet). Once we have a + // screen object, we need to keep track of these changes. + if (mScreen != null) { + mScreenWidth = width; + mScreenHeight = height; + mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight); + updateTargetRect(); + } + } + + @Override + public synchronized void surfaceDestroyed(SurfaceHolder holder) { + mIsBound = false; + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } else { + // Screen is taller than the camera, scale down Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } + break; + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java new file mode 100644 index 0000000..308d168 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java @@ -0,0 +1,257 @@ +/* + * 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 android.filterpacks.ui; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceTargetFilter extends Filter { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + + /** Required. Sets the destination surface for this node. This assumes that + * higher-level code is ensuring that the surface is valid, and properly + * updates Surface parameters if they change. + */ + @GenerateFinalPort(name = "surface") + private Surface mSurface; + + /** Required. Width of the output surface */ + @GenerateFieldPort(name = "owidth") + private int mScreenWidth; + + /** Required. Height of the output surface */ + @GenerateFieldPort(name = "oheight") + private int mScreenHeight; + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + private ShaderProgram mProgram; + private GLEnvironment mGlEnv; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private int mSurfaceId = -1; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceRenderFilter"; + + public SurfaceTargetFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a Surface + if (mSurface == null) { + throw new RuntimeException("NULL Surface passed to SurfaceTargetFilter"); + } + + // Add input port + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + mGlEnv = context.getGLEnvironment(); + + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearsOutput(true); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + MutableFrameFormat screenFormat = ImageFormat.create(mScreenWidth, + mScreenHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + + // Set up cropping + updateRenderMode(); + } + + @Override + public void open(FilterContext context) { + registerSurface(); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceRenderFilter", "Got input format: " + input.getFormat()); + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + mGlEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(gpuFrame, mScreen); + + // And swap buffers + mGlEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + mScreen.setViewport(0, 0, mScreenWidth, mScreenHeight); + updateTargetRect(); + } + + @Override + public void close(FilterContext context) { + unregisterSurface(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } else { + // Screen is taller than the camera, scale down Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mProgram.setTargetRect(0.0f, 0.5f - 0.5f * relativeAspectRatio, + 1.0f, relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mProgram.setTargetRect(0.5f - 0.5f / relativeAspectRatio, 0.0f, + 1.0f / relativeAspectRatio, 1.0f); + } + break; + } + } + } + + private void registerSurface() { + mSurfaceId = mGlEnv.registerSurface(mSurface); + if (mSurfaceId < 0) { + throw new RuntimeException("Could not register Surface: " + mSurface); + } + } + + private void unregisterSurface() { + if (mSurfaceId > 0) { + mGlEnv.unregisterSurfaceId(mSurfaceId); + } + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java new file mode 100644 index 0000000..9ed7d51 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/ui/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.ui; diff --git a/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java new file mode 100644 index 0000000..52c9fda --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java @@ -0,0 +1,976 @@ +/* + * 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 android.filterpacks.videoproc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.Frame; +import android.filterfw.core.GLFrame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.opengl.GLES20; +import android.os.SystemClock; +import android.util.Log; + +import java.lang.ArrayIndexOutOfBoundsException; +import java.lang.Math; +import java.util.Arrays; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class BackDropperFilter extends Filter { + /** User-visible parameters */ + + private final int BACKGROUND_STRETCH = 0; + private final int BACKGROUND_FIT = 1; + private final int BACKGROUND_FILL_CROP = 2; + + @GenerateFieldPort(name = "backgroundFitMode", hasDefault = true) + private int mBackgroundFitMode = BACKGROUND_FILL_CROP; + @GenerateFieldPort(name = "learningDuration", hasDefault = true) + private int mLearningDuration = DEFAULT_LEARNING_DURATION; + @GenerateFieldPort(name = "learningVerifyDuration", hasDefault = true) + private int mLearningVerifyDuration = DEFAULT_LEARNING_VERIFY_DURATION; + @GenerateFieldPort(name = "acceptStddev", hasDefault = true) + private float mAcceptStddev = DEFAULT_ACCEPT_STDDEV; + @GenerateFieldPort(name = "hierLrgScale", hasDefault = true) + private float mHierarchyLrgScale = DEFAULT_HIER_LRG_SCALE; + @GenerateFieldPort(name = "hierMidScale", hasDefault = true) + private float mHierarchyMidScale = DEFAULT_HIER_MID_SCALE; + @GenerateFieldPort(name = "hierSmlScale", hasDefault = true) + private float mHierarchySmlScale = DEFAULT_HIER_SML_SCALE; + + // Dimensions of foreground / background mask. Optimum value should take into account only + // image contents, NOT dimensions of input video stream. + @GenerateFieldPort(name = "maskWidthExp", hasDefault = true) + private int mMaskWidthExp = DEFAULT_MASK_WIDTH_EXPONENT; + @GenerateFieldPort(name = "maskHeightExp", hasDefault = true) + private int mMaskHeightExp = DEFAULT_MASK_HEIGHT_EXPONENT; + + // Levels at which to compute foreground / background decision. Think of them as are deltas + // SUBTRACTED from maskWidthExp and maskHeightExp. + @GenerateFieldPort(name = "hierLrgExp", hasDefault = true) + private int mHierarchyLrgExp = DEFAULT_HIER_LRG_EXPONENT; + @GenerateFieldPort(name = "hierMidExp", hasDefault = true) + private int mHierarchyMidExp = DEFAULT_HIER_MID_EXPONENT; + @GenerateFieldPort(name = "hierSmlExp", hasDefault = true) + private int mHierarchySmlExp = DEFAULT_HIER_SML_EXPONENT; + + @GenerateFieldPort(name = "lumScale", hasDefault = true) + private float mLumScale = DEFAULT_Y_SCALE_FACTOR; + @GenerateFieldPort(name = "chromaScale", hasDefault = true) + private float mChromaScale = DEFAULT_UV_SCALE_FACTOR; + @GenerateFieldPort(name = "maskBg", hasDefault = true) + private float mMaskBg = DEFAULT_MASK_BLEND_BG; + @GenerateFieldPort(name = "maskFg", hasDefault = true) + private float mMaskFg = DEFAULT_MASK_BLEND_FG; + @GenerateFieldPort(name = "exposureChange", hasDefault = true) + private float mExposureChange = DEFAULT_EXPOSURE_CHANGE; + @GenerateFieldPort(name = "whitebalanceredChange", hasDefault = true) + private float mWhiteBalanceRedChange = DEFAULT_WHITE_BALANCE_RED_CHANGE; + @GenerateFieldPort(name = "whitebalanceblueChange", hasDefault = true) + private float mWhiteBalanceBlueChange = DEFAULT_WHITE_BALANCE_BLUE_CHANGE; + @GenerateFieldPort(name = "autowbToggle", hasDefault = true) + private int mAutoWBToggle = DEFAULT_WHITE_BALANCE_TOGGLE; + + // TODO: These are not updatable: + @GenerateFieldPort(name = "learningAdaptRate", hasDefault = true) + private float mAdaptRateLearning = DEFAULT_LEARNING_ADAPT_RATE; + @GenerateFieldPort(name = "adaptRateBg", hasDefault = true) + private float mAdaptRateBg = DEFAULT_ADAPT_RATE_BG; + @GenerateFieldPort(name = "adaptRateFg", hasDefault = true) + private float mAdaptRateFg = DEFAULT_ADAPT_RATE_FG; + @GenerateFieldPort(name = "maskVerifyRate", hasDefault = true) + private float mVerifyRate = DEFAULT_MASK_VERIFY_RATE; + @GenerateFieldPort(name = "learningDoneListener", hasDefault = true) + private LearningDoneListener mLearningDoneListener = null; + + @GenerateFieldPort(name = "useTheForce", hasDefault = true) + private boolean mUseTheForce = false; + + @GenerateFinalPort(name = "provideDebugOutputs", hasDefault = true) + private boolean mProvideDebugOutputs = false; + + // Whether to mirror the background or not. For ex, the Camera app + // would mirror the preview for the front camera + @GenerateFieldPort(name = "mirrorBg", hasDefault = true) + private boolean mMirrorBg = false; + + // The orientation of the display. This will change the flipping + // coordinates, if we were to mirror the background + @GenerateFieldPort(name = "orientation", hasDefault = true) + private int mOrientation = 0; + + /** Default algorithm parameter values, for non-shader use */ + + // Frame count for learning bg model + private static final int DEFAULT_LEARNING_DURATION = 40; + // Frame count for learning verification + private static final int DEFAULT_LEARNING_VERIFY_DURATION = 10; + // Maximum distance (in standard deviations) for considering a pixel as background + private static final float DEFAULT_ACCEPT_STDDEV = 0.85f; + // Variance threshold scale factor for large scale of hierarchy + private static final float DEFAULT_HIER_LRG_SCALE = 0.7f; + // Variance threshold scale factor for medium scale of hierarchy + private static final float DEFAULT_HIER_MID_SCALE = 0.6f; + // Variance threshold scale factor for small scale of hierarchy + private static final float DEFAULT_HIER_SML_SCALE = 0.5f; + // Width of foreground / background mask. + private static final int DEFAULT_MASK_WIDTH_EXPONENT = 8; + // Height of foreground / background mask. + private static final int DEFAULT_MASK_HEIGHT_EXPONENT = 8; + // Area over which to average for large scale (length in pixels = 2^HIERARCHY_*_EXPONENT) + private static final int DEFAULT_HIER_LRG_EXPONENT = 3; + // Area over which to average for medium scale + private static final int DEFAULT_HIER_MID_EXPONENT = 2; + // Area over which to average for small scale + private static final int DEFAULT_HIER_SML_EXPONENT = 0; + // Scale factor for luminance channel in distance calculations (larger = more significant) + private static final float DEFAULT_Y_SCALE_FACTOR = 0.40f; + // Scale factor for chroma channels in distance calculations + private static final float DEFAULT_UV_SCALE_FACTOR = 1.35f; + // Mask value to start blending away from background + private static final float DEFAULT_MASK_BLEND_BG = 0.65f; + // Mask value to start blending away from foreground + private static final float DEFAULT_MASK_BLEND_FG = 0.95f; + // Exposure stop number to change the brightness of foreground + private static final float DEFAULT_EXPOSURE_CHANGE = 1.0f; + // White balance change in Red channel for foreground + private static final float DEFAULT_WHITE_BALANCE_RED_CHANGE = 0.0f; + // White balance change in Blue channel for foreground + private static final float DEFAULT_WHITE_BALANCE_BLUE_CHANGE = 0.0f; + // Variable to control automatic white balance effect + // 0.f -> Auto WB is off; 1.f-> Auto WB is on + private static final int DEFAULT_WHITE_BALANCE_TOGGLE = 0; + + // Default rate at which to learn bg model during learning period + private static final float DEFAULT_LEARNING_ADAPT_RATE = 0.2f; + // Default rate at which to learn bg model from new background pixels + private static final float DEFAULT_ADAPT_RATE_BG = 0.0f; + // Default rate at which to learn bg model from new foreground pixels + private static final float DEFAULT_ADAPT_RATE_FG = 0.0f; + // Default rate at which to verify whether background is stable + private static final float DEFAULT_MASK_VERIFY_RATE = 0.25f; + // Default rate at which to verify whether background is stable + private static final int DEFAULT_LEARNING_DONE_THRESHOLD = 20; + + // Default 3x3 matrix, column major, for fitting background 1:1 + private static final float[] DEFAULT_BG_FIT_TRANSFORM = new float[] { + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f + }; + + /** Default algorithm parameter values, for shader use */ + + // Area over which to blur binary mask values (length in pixels = 2^MASK_SMOOTH_EXPONENT) + private static final String MASK_SMOOTH_EXPONENT = "2.0"; + // Scale value for mapping variance distance to fit nicely to 0-1, 8-bit + private static final String DISTANCE_STORAGE_SCALE = "0.6"; + // Scale value for mapping variance to fit nicely to 0-1, 8-bit + private static final String VARIANCE_STORAGE_SCALE = "5.0"; + // Default scale of auto white balance parameters + private static final String DEFAULT_AUTO_WB_SCALE = "0.25"; + // Minimum variance (0-255 scale) + private static final String MIN_VARIANCE = "3.0"; + // Column-major array for 4x4 matrix converting RGB to YCbCr, JPEG definition (no pedestal) + private static final String RGB_TO_YUV_MATRIX = "0.299, -0.168736, 0.5, 0.000, " + + "0.587, -0.331264, -0.418688, 0.000, " + + "0.114, 0.5, -0.081312, 0.000, " + + "0.000, 0.5, 0.5, 1.000 "; + /** Stream names */ + + private static final String[] mInputNames = {"video", + "background"}; + + private static final String[] mOutputNames = {"video"}; + + private static final String[] mDebugOutputNames = {"debug1", + "debug2"}; + + /** Other private variables */ + + private FrameFormat mOutputFormat; + private MutableFrameFormat mMemoryFormat; + private MutableFrameFormat mMaskFormat; + private MutableFrameFormat mAverageFormat; + + private final boolean mLogVerbose; + private static final String TAG = "BackDropperFilter"; + + /** Shader source code */ + + // Shared uniforms and utility functions + private static String mSharedUtilShader = + "precision mediump float;\n" + + "uniform float fg_adapt_rate;\n" + + "uniform float bg_adapt_rate;\n" + + "const mat4 coeff_yuv = mat4(" + RGB_TO_YUV_MATRIX + ");\n" + + "const float dist_scale = " + DISTANCE_STORAGE_SCALE + ";\n" + + "const float inv_dist_scale = 1. / dist_scale;\n" + + "const float var_scale=" + VARIANCE_STORAGE_SCALE + ";\n" + + "const float inv_var_scale = 1. / var_scale;\n" + + "const float min_variance = inv_var_scale *" + MIN_VARIANCE + "/ 256.;\n" + + "const float auto_wb_scale = " + DEFAULT_AUTO_WB_SCALE + ";\n" + + "\n" + + // Variance distance in luminance between current pixel and background model + "float gauss_dist_y(float y, float mean, float variance) {\n" + + " float dist = (y - mean) * (y - mean) / variance;\n" + + " return dist;\n" + + "}\n" + + // Sum of variance distances in chroma between current pixel and background + // model + "float gauss_dist_uv(vec2 uv, vec2 mean, vec2 variance) {\n" + + " vec2 dist = (uv - mean) * (uv - mean) / variance;\n" + + " return dist.r + dist.g;\n" + + "}\n" + + // Select learning rate for pixel based on smoothed decision mask alpha + "float local_adapt_rate(float alpha) {\n" + + " return mix(bg_adapt_rate, fg_adapt_rate, alpha);\n" + + "}\n" + + "\n"; + + // Distance calculation shader. Calculates a distance metric between the foreground and the + // current background model, in both luminance and in chroma (yuv space). Distance is + // measured in variances from the mean background value. For chroma, the distance is the sum + // of the two individual color channel distances. The distances are output on the b and alpha + // channels, r and g are for debug information. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Background variance mask. + // subsample_level: Level on foreground frame's mip-map. + private static final String mBgDistanceShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + + "\n" + + " float dist_y = gauss_dist_y(fg.r, mean.r, variance.r);\n" + + " float dist_uv = gauss_dist_uv(fg.gb, mean.gb, variance.gb);\n" + + " gl_FragColor = vec4(0.5*fg.rg, dist_scale*dist_y, dist_scale*dist_uv);\n" + + "}\n"; + + // Foreground/background mask decision shader. Decides whether a frame is in the foreground or + // the background using a hierarchical threshold on the distance. Binary foreground/background + // mask is placed in the alpha channel. The RGB channels contain debug information. + private static final String mBgMaskShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform float accept_variance;\n" + + "uniform vec2 yuv_weights;\n" + + "uniform float scale_lrg;\n" + + "uniform float scale_mid;\n" + + "uniform float scale_sml;\n" + + "uniform float exp_lrg;\n" + + "uniform float exp_mid;\n" + + "uniform float exp_sml;\n" + + "varying vec2 v_texcoord;\n" + + // Decide whether pixel is foreground or background based on Y and UV + // distance and maximum acceptable variance. + // yuv_weights.x is smaller than yuv_weights.y to discount the influence of shadow + "bool is_fg(vec2 dist_yc, float accept_variance) {\n" + + " return ( dot(yuv_weights, dist_yc) >= accept_variance );\n" + + "}\n" + + "void main() {\n" + + " vec4 dist_lrg_sc = texture2D(tex_sampler_0, v_texcoord, exp_lrg);\n" + + " vec4 dist_mid_sc = texture2D(tex_sampler_0, v_texcoord, exp_mid);\n" + + " vec4 dist_sml_sc = texture2D(tex_sampler_0, v_texcoord, exp_sml);\n" + + " vec2 dist_lrg = inv_dist_scale * dist_lrg_sc.ba;\n" + + " vec2 dist_mid = inv_dist_scale * dist_mid_sc.ba;\n" + + " vec2 dist_sml = inv_dist_scale * dist_sml_sc.ba;\n" + + " vec2 norm_dist = 0.75 * dist_sml / accept_variance;\n" + // For debug viz + " bool is_fg_lrg = is_fg(dist_lrg, accept_variance * scale_lrg);\n" + + " bool is_fg_mid = is_fg_lrg || is_fg(dist_mid, accept_variance * scale_mid);\n" + + " float is_fg_sml =\n" + + " float(is_fg_mid || is_fg(dist_sml, accept_variance * scale_sml));\n" + + " float alpha = 0.5 * is_fg_sml + 0.3 * float(is_fg_mid) + 0.2 * float(is_fg_lrg);\n" + + " gl_FragColor = vec4(alpha, norm_dist, is_fg_sml);\n" + + "}\n"; + + // Automatic White Balance parameter decision shader + // Use the Gray World assumption that in a white balance corrected image, the average of R, G, B + // channel will be a common gray value. + // To match the white balance of foreground and background, the average of R, G, B channel of + // two videos should match. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Mip-map for background (playback) video frame. + // pyramid_depth: Depth of input frames' mip-maps. + private static final String mAutomaticWhiteBalance = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float pyramid_depth;\n" + + "uniform bool autowb_toggle;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 mean_video = texture2D(tex_sampler_0, v_texcoord, pyramid_depth);\n"+ + " vec4 mean_bg = texture2D(tex_sampler_1, v_texcoord, pyramid_depth);\n" + + // If Auto WB is toggled off, the return texture will be a unicolor texture of value 1 + // If Auto WB is toggled on, the return texture will be a unicolor texture with + // adjustment parameters for R and B channels stored in the corresponding channel + " float green_normalizer = mean_video.g / mean_bg.g;\n"+ + " vec4 adjusted_value = vec4(mean_bg.r / mean_video.r * green_normalizer, 1., \n" + + " mean_bg.b / mean_video.b * green_normalizer, 1.) * auto_wb_scale; \n" + + " gl_FragColor = autowb_toggle ? adjusted_value : vec4(auto_wb_scale);\n" + + "}\n"; + + + // Background subtraction shader. Uses a mipmap of the binary mask map to blend smoothly between + // foreground and background + // Inputs: + // tex_sampler_0: Foreground (live) video frame. + // tex_sampler_1: Background (playback) video frame. + // tex_sampler_2: Foreground/background mask. + // tex_sampler_3: Auto white-balance factors. + private static final String mBgSubtractShader = + "uniform mat3 bg_fit_transform;\n" + + "uniform float mask_blend_bg;\n" + + "uniform float mask_blend_fg;\n" + + "uniform float exposure_change;\n" + + "uniform float whitebalancered_change;\n" + + "uniform float whitebalanceblue_change;\n" + + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform sampler2D tex_sampler_3;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec2 bg_texcoord = (bg_fit_transform * vec3(v_texcoord, 1.)).xy;\n" + + " vec4 bg_rgb = texture2D(tex_sampler_1, bg_texcoord);\n" + + // The foreground texture is modified by multiplying both manual and auto white balance changes in R and B + // channel and multiplying exposure change in all R, G, B channels. + " vec4 wb_auto_scale = texture2D(tex_sampler_3, v_texcoord) * exposure_change / auto_wb_scale;\n" + + " vec4 wb_manual_scale = vec4(1. + whitebalancered_change, 1., 1. + whitebalanceblue_change, 1.);\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 fg_adjusted = fg_rgb * wb_manual_scale * wb_auto_scale;\n"+ + " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + " float alpha = smoothstep(mask_blend_bg, mask_blend_fg, mask.a);\n" + + " gl_FragColor = mix(bg_rgb, fg_adjusted, alpha);\n"; + + // May the Force... Makes the foreground object translucent blue, with a bright + // blue-white outline + private static final String mBgSubtractForceShader = + " vec4 ghost_rgb = (fg_adjusted * 0.7 + vec4(0.3,0.3,0.4,0.))*0.65 + \n" + + " 0.35*bg_rgb;\n" + + " float glow_start = 0.75 * mask_blend_bg; \n"+ + " float glow_max = mask_blend_bg; \n"+ + " gl_FragColor = mask.a < glow_start ? bg_rgb : \n" + + " mask.a < glow_max ? mix(bg_rgb, vec4(0.9,0.9,1.0,1.0), \n" + + " (mask.a - glow_start) / (glow_max - glow_start) ) : \n" + + " mask.a < mask_blend_fg ? mix(vec4(0.9,0.9,1.0,1.0), ghost_rgb, \n" + + " (mask.a - glow_max) / (mask_blend_fg - glow_max) ) : \n" + + " ghost_rgb;\n" + + "}\n"; + + // Background model mean update shader. Skews the current model mean toward the most recent pixel + // value for a pixel, weighted by the learning rate and by whether the pixel is classified as + // foreground or background. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Foreground/background mask. + // subsample_level: Level on foreground frame's mip-map. + private static final String mUpdateBgModelMeanShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_2, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + "\n" + + " float alpha = local_adapt_rate(mask.a);\n" + + " vec4 new_mean = mix(mean, fg, alpha);\n" + + " gl_FragColor = new_mean;\n" + + "}\n"; + + // Background model variance update shader. Skews the current model variance toward the most + // recent variance for the pixel, weighted by the learning rate and by whether the pixel is + // classified as foreground or background. + // Inputs: + // tex_sampler_0: Mip-map for foreground (live) video frame. + // tex_sampler_1: Background mean mask. + // tex_sampler_2: Background variance mask. + // tex_sampler_3: Foreground/background mask. + // subsample_level: Level on foreground frame's mip-map. + // TODO: to improve efficiency, use single mark for mean + variance, then merge this into + // mUpdateBgModelMeanShader. + private static final String mUpdateBgModelVarianceShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform sampler2D tex_sampler_2;\n" + + "uniform sampler2D tex_sampler_3;\n" + + "uniform float subsample_level;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 fg_rgb = texture2D(tex_sampler_0, v_texcoord, subsample_level);\n" + + " vec4 fg = coeff_yuv * vec4(fg_rgb.rgb, 1.);\n" + + " vec4 mean = texture2D(tex_sampler_1, v_texcoord);\n" + + " vec4 variance = inv_var_scale * texture2D(tex_sampler_2, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_3, v_texcoord, \n" + + " " + MASK_SMOOTH_EXPONENT + ");\n" + + "\n" + + " float alpha = local_adapt_rate(mask.a);\n" + + " vec4 cur_variance = (fg-mean)*(fg-mean);\n" + + " vec4 new_variance = mix(variance, cur_variance, alpha);\n" + + " new_variance = max(new_variance, vec4(min_variance));\n" + + " gl_FragColor = var_scale * new_variance;\n" + + "}\n"; + + // Background verification shader. Skews the current background verification mask towards the + // most recent frame, weighted by the learning rate. + private static final String mMaskVerifyShader = + "uniform sampler2D tex_sampler_0;\n" + + "uniform sampler2D tex_sampler_1;\n" + + "uniform float verify_rate;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " vec4 lastmask = texture2D(tex_sampler_0, v_texcoord);\n" + + " vec4 mask = texture2D(tex_sampler_1, v_texcoord);\n" + + " float newmask = mix(lastmask.a, mask.a, verify_rate);\n" + + " gl_FragColor = vec4(0., 0., 0., newmask);\n" + + "}\n"; + + /** Shader program objects */ + + private ShaderProgram mBgDistProgram; + private ShaderProgram mBgMaskProgram; + private ShaderProgram mBgSubtractProgram; + private ShaderProgram mBgUpdateMeanProgram; + private ShaderProgram mBgUpdateVarianceProgram; + private ShaderProgram mCopyOutProgram; + private ShaderProgram mAutomaticWhiteBalanceProgram; + private ShaderProgram mMaskVerifyProgram; + private ShaderProgram copyShaderProgram; + + /** Background model storage */ + + private boolean mPingPong; + private GLFrame mBgMean[]; + private GLFrame mBgVariance[]; + private GLFrame mMaskVerify[]; + private GLFrame mDistance; + private GLFrame mAutoWB; + private GLFrame mMask; + private GLFrame mVideoInput; + private GLFrame mBgInput; + private GLFrame mMaskAverage; + + /** Overall filter state */ + + private boolean isOpen; + private int mFrameCount; + private boolean mStartLearning; + private boolean mBackgroundFitModeChanged; + private float mRelativeAspect; + private int mPyramidDepth; + private int mSubsampleLevel; + + /** Learning listener object */ + + public interface LearningDoneListener { + public void onLearningDone(BackDropperFilter filter); + } + + /** Public Filter methods */ + + public BackDropperFilter(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Inputs. + // TODO: Target should be GPU, but relaxed for now. + FrameFormat imageFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_UNSPECIFIED); + for (String inputName : mInputNames) { + addMaskedInputPort(inputName, imageFormat); + } + // Normal outputs + for (String outputName : mOutputNames) { + addOutputBasedOnInput(outputName, "video"); + } + + // Debug outputs + if (mProvideDebugOutputs) { + for (String outputName : mDebugOutputNames) { + addOutputBasedOnInput(outputName, "video"); + } + } + } + + @Override + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + // Create memory format based on video input. + MutableFrameFormat format = inputFormat.mutableCopy(); + // Is this a debug output port? If so, leave dimensions unspecified. + if (!Arrays.asList(mOutputNames).contains(portName)) { + format.setDimensions(FrameFormat.SIZE_UNSPECIFIED, FrameFormat.SIZE_UNSPECIFIED); + } + return format; + } + + private boolean createMemoryFormat(FrameFormat inputFormat) { + // We can't resize because that would require re-learning. + if (mMemoryFormat != null) { + return false; + } + + if (inputFormat.getWidth() == FrameFormat.SIZE_UNSPECIFIED || + inputFormat.getHeight() == FrameFormat.SIZE_UNSPECIFIED) { + throw new RuntimeException("Attempting to process input frame with unknown size"); + } + + mMaskFormat = inputFormat.mutableCopy(); + int maskWidth = (int)Math.pow(2, mMaskWidthExp); + int maskHeight = (int)Math.pow(2, mMaskHeightExp); + mMaskFormat.setDimensions(maskWidth, maskHeight); + + mPyramidDepth = Math.max(mMaskWidthExp, mMaskHeightExp); + mMemoryFormat = mMaskFormat.mutableCopy(); + int widthExp = Math.max(mMaskWidthExp, pyramidLevel(inputFormat.getWidth())); + int heightExp = Math.max(mMaskHeightExp, pyramidLevel(inputFormat.getHeight())); + mPyramidDepth = Math.max(widthExp, heightExp); + int memWidth = Math.max(maskWidth, (int)Math.pow(2, widthExp)); + int memHeight = Math.max(maskHeight, (int)Math.pow(2, heightExp)); + mMemoryFormat.setDimensions(memWidth, memHeight); + mSubsampleLevel = mPyramidDepth - Math.max(mMaskWidthExp, mMaskHeightExp); + + if (mLogVerbose) { + Log.v(TAG, "Mask frames size " + maskWidth + " x " + maskHeight); + Log.v(TAG, "Pyramid levels " + widthExp + " x " + heightExp); + Log.v(TAG, "Memory frames size " + memWidth + " x " + memHeight); + } + + mAverageFormat = inputFormat.mutableCopy(); + mAverageFormat.setDimensions(1,1); + return true; + } + + public void prepare(FilterContext context){ + if (mLogVerbose) Log.v(TAG, "Preparing BackDropperFilter!"); + + mBgMean = new GLFrame[2]; + mBgVariance = new GLFrame[2]; + mMaskVerify = new GLFrame[2]; + copyShaderProgram = ShaderProgram.createIdentity(context); + } + + private void allocateFrames(FrameFormat inputFormat, FilterContext context) { + if (!createMemoryFormat(inputFormat)) { + return; // All set. + } + if (mLogVerbose) Log.v(TAG, "Allocating BackDropperFilter frames"); + + // Create initial background model values + int numBytes = mMaskFormat.getSize(); + byte[] initialBgMean = new byte[numBytes]; + byte[] initialBgVariance = new byte[numBytes]; + byte[] initialMaskVerify = new byte[numBytes]; + for (int i = 0; i < numBytes; i++) { + initialBgMean[i] = (byte)128; + initialBgVariance[i] = (byte)10; + initialMaskVerify[i] = (byte)0; + } + + // Get frames to store background model in + for (int i = 0; i < 2; i++) { + mBgMean[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mBgMean[i].setData(initialBgMean, 0, numBytes); + + mBgVariance[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mBgVariance[i].setData(initialBgVariance, 0, numBytes); + + mMaskVerify[i] = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mMaskVerify[i].setData(initialMaskVerify, 0, numBytes); + } + + // Get frames to store other textures in + if (mLogVerbose) Log.v(TAG, "Done allocating texture for Mean and Variance objects!"); + + mDistance = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mMask = (GLFrame)context.getFrameManager().newFrame(mMaskFormat); + mAutoWB = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); + mVideoInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); + mBgInput = (GLFrame)context.getFrameManager().newFrame(mMemoryFormat); + mMaskAverage = (GLFrame)context.getFrameManager().newFrame(mAverageFormat); + + // Create shader programs + mBgDistProgram = new ShaderProgram(context, mSharedUtilShader + mBgDistanceShader); + mBgDistProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mBgMaskProgram = new ShaderProgram(context, mSharedUtilShader + mBgMaskShader); + mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); + float[] yuvWeights = { mLumScale, mChromaScale }; + mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); + mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); + mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); + mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); + mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); + mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); + mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); + + if (mUseTheForce) { + mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + mBgSubtractForceShader); + } else { + mBgSubtractProgram = new ShaderProgram(context, mSharedUtilShader + mBgSubtractShader + "}\n"); + } + mBgSubtractProgram.setHostValue("bg_fit_transform", DEFAULT_BG_FIT_TRANSFORM); + mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); + mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); + mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); + mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); + mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); + + + mBgUpdateMeanProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelMeanShader); + mBgUpdateMeanProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mBgUpdateVarianceProgram = new ShaderProgram(context, mSharedUtilShader + mUpdateBgModelVarianceShader); + mBgUpdateVarianceProgram.setHostValue("subsample_level", (float)mSubsampleLevel); + + mCopyOutProgram = ShaderProgram.createIdentity(context); + + mAutomaticWhiteBalanceProgram = new ShaderProgram(context, mSharedUtilShader + mAutomaticWhiteBalance); + mAutomaticWhiteBalanceProgram.setHostValue("pyramid_depth", (float)mPyramidDepth); + mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); + + mMaskVerifyProgram = new ShaderProgram(context, mSharedUtilShader + mMaskVerifyShader); + mMaskVerifyProgram.setHostValue("verify_rate", mVerifyRate); + + if (mLogVerbose) Log.v(TAG, "Shader width set to " + mMemoryFormat.getWidth()); + + mRelativeAspect = 1.f; + + mFrameCount = 0; + mStartLearning = true; + } + + public void process(FilterContext context) { + // Grab inputs and ready intermediate frames and outputs. + Frame video = pullInput("video"); + Frame background = pullInput("background"); + allocateFrames(video.getFormat(), context); + + // Update learning rate after initial learning period + if (mStartLearning) { + if (mLogVerbose) Log.v(TAG, "Starting learning"); + mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); + mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); + mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateLearning); + mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateLearning); + mFrameCount = 0; + mStartLearning = false; + } + + // Select correct pingpong buffers + int inputIndex = mPingPong ? 0 : 1; + int outputIndex = mPingPong ? 1 : 0; + mPingPong = !mPingPong; + + // Check relative aspect ratios + updateBgScaling(video, background, mBackgroundFitModeChanged); + mBackgroundFitModeChanged = false; + + // Make copies for input frames to GLFrames + + copyShaderProgram.process(video, mVideoInput); + copyShaderProgram.process(background, mBgInput); + + mVideoInput.generateMipMap(); + mVideoInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + mBgInput.generateMipMap(); + mBgInput.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + // Process shaders + Frame[] distInputs = { mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex] }; + mBgDistProgram.process(distInputs, mDistance); + mDistance.generateMipMap(); + mDistance.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + mBgMaskProgram.process(mDistance, mMask); + mMask.generateMipMap(); + mMask.setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + Frame[] autoWBInputs = { mVideoInput, mBgInput }; + mAutomaticWhiteBalanceProgram.process(autoWBInputs, mAutoWB); + + if (mFrameCount <= mLearningDuration) { + // During learning + pushOutput("video", video); + + if (mFrameCount == mLearningDuration - mLearningVerifyDuration) { + copyShaderProgram.process(mMask, mMaskVerify[outputIndex]); + + mBgUpdateMeanProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); + mBgUpdateMeanProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); + mBgUpdateVarianceProgram.setHostValue("bg_adapt_rate", mAdaptRateBg); + mBgUpdateVarianceProgram.setHostValue("fg_adapt_rate", mAdaptRateFg); + + + } else if (mFrameCount > mLearningDuration - mLearningVerifyDuration) { + // In the learning verification stage, compute background masks and a weighted average + // with weights grow exponentially with time + Frame[] maskVerifyInputs = {mMaskVerify[inputIndex], mMask}; + mMaskVerifyProgram.process(maskVerifyInputs, mMaskVerify[outputIndex]); + mMaskVerify[outputIndex].generateMipMap(); + mMaskVerify[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + } + + if (mFrameCount == mLearningDuration) { + // In the last verification frame, verify if the verification mask is almost blank + // If not, restart learning + copyShaderProgram.process(mMaskVerify[outputIndex], mMaskAverage); + ByteBuffer mMaskAverageByteBuffer = mMaskAverage.getData(); + byte[] mask_average = mMaskAverageByteBuffer.array(); + int bi = (int)(mask_average[3] & 0xFF); + if (mLogVerbose) Log.v(TAG, String.format("Mask_average is %d", bi)); + + if (bi >= DEFAULT_LEARNING_DONE_THRESHOLD) { + mStartLearning = true; // Restart learning + } else { + if (mLogVerbose) Log.v(TAG, "Learning done"); + if (mLearningDoneListener != null) { + mLearningDoneListener.onLearningDone(this); + } + } + } + } else { + Frame output = context.getFrameManager().newFrame(video.getFormat()); + Frame[] subtractInputs = { video, background, mMask, mAutoWB }; + mBgSubtractProgram.process(subtractInputs, output); + pushOutput("video", output); + output.release(); + } + + // Compute mean and variance of the background + if (mFrameCount < mLearningDuration - mLearningVerifyDuration || + mAdaptRateBg > 0.0 || mAdaptRateFg > 0.0) { + Frame[] meanUpdateInputs = { mVideoInput, mBgMean[inputIndex], mMask }; + mBgUpdateMeanProgram.process(meanUpdateInputs, mBgMean[outputIndex]); + mBgMean[outputIndex].generateMipMap(); + mBgMean[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + + Frame[] varianceUpdateInputs = { + mVideoInput, mBgMean[inputIndex], mBgVariance[inputIndex], mMask + }; + mBgUpdateVarianceProgram.process(varianceUpdateInputs, mBgVariance[outputIndex]); + mBgVariance[outputIndex].generateMipMap(); + mBgVariance[outputIndex].setTextureParameter(GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR_MIPMAP_NEAREST); + } + + // Provide debug output to two smaller viewers + if (mProvideDebugOutputs) { + Frame dbg1 = context.getFrameManager().newFrame(video.getFormat()); + mCopyOutProgram.process(video, dbg1); + pushOutput("debug1", dbg1); + dbg1.release(); + + Frame dbg2 = context.getFrameManager().newFrame(mMemoryFormat); + mCopyOutProgram.process(mMask, dbg2); + pushOutput("debug2", dbg2); + dbg2.release(); + } + + mFrameCount++; + + if (mLogVerbose) { + if (mFrameCount % 30 == 0) { + if (startTime == -1) { + context.getGLEnvironment().activate(); + GLES20.glFinish(); + startTime = SystemClock.elapsedRealtime(); + } else { + context.getGLEnvironment().activate(); + GLES20.glFinish(); + long endTime = SystemClock.elapsedRealtime(); + Log.v(TAG, "Avg. frame duration: " + String.format("%.2f",(endTime-startTime)/30.) + + " ms. Avg. fps: " + String.format("%.2f", 1000./((endTime-startTime)/30.)) ); + startTime = endTime; + } + } + } + } + + private long startTime = -1; + + public void close(FilterContext context) { + if (mMemoryFormat == null) { + return; + } + + if (mLogVerbose) Log.v(TAG, "Filter Closing!"); + for (int i = 0; i < 2; i++) { + mBgMean[i].release(); + mBgVariance[i].release(); + mMaskVerify[i].release(); + } + mDistance.release(); + mMask.release(); + mAutoWB.release(); + mVideoInput.release(); + mBgInput.release(); + mMaskAverage.release(); + + mMemoryFormat = null; + } + + // Relearn background model + synchronized public void relearn() { + // Let the processing thread know about learning restart + mStartLearning = true; + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + // TODO: Many of these can be made ProgramPorts! + if (name.equals("backgroundFitMode")) { + mBackgroundFitModeChanged = true; + } else if (name.equals("acceptStddev")) { + mBgMaskProgram.setHostValue("accept_variance", mAcceptStddev * mAcceptStddev); + } else if (name.equals("hierLrgScale")) { + mBgMaskProgram.setHostValue("scale_lrg", mHierarchyLrgScale); + } else if (name.equals("hierMidScale")) { + mBgMaskProgram.setHostValue("scale_mid", mHierarchyMidScale); + } else if (name.equals("hierSmlScale")) { + mBgMaskProgram.setHostValue("scale_sml", mHierarchySmlScale); + } else if (name.equals("hierLrgExp")) { + mBgMaskProgram.setHostValue("exp_lrg", (float)(mSubsampleLevel + mHierarchyLrgExp)); + } else if (name.equals("hierMidExp")) { + mBgMaskProgram.setHostValue("exp_mid", (float)(mSubsampleLevel + mHierarchyMidExp)); + } else if (name.equals("hierSmlExp")) { + mBgMaskProgram.setHostValue("exp_sml", (float)(mSubsampleLevel + mHierarchySmlExp)); + } else if (name.equals("lumScale") || name.equals("chromaScale")) { + float[] yuvWeights = { mLumScale, mChromaScale }; + mBgMaskProgram.setHostValue("yuv_weights", yuvWeights ); + } else if (name.equals("maskBg")) { + mBgSubtractProgram.setHostValue("mask_blend_bg", mMaskBg); + } else if (name.equals("maskFg")) { + mBgSubtractProgram.setHostValue("mask_blend_fg", mMaskFg); + } else if (name.equals("exposureChange")) { + mBgSubtractProgram.setHostValue("exposure_change", mExposureChange); + } else if (name.equals("whitebalanceredChange")) { + mBgSubtractProgram.setHostValue("whitebalancered_change", mWhiteBalanceRedChange); + } else if (name.equals("whitebalanceblueChange")) { + mBgSubtractProgram.setHostValue("whitebalanceblue_change", mWhiteBalanceBlueChange); + } else if (name.equals("autowbToggle")){ + mAutomaticWhiteBalanceProgram.setHostValue("autowb_toggle", mAutoWBToggle); + } + } + + private void updateBgScaling(Frame video, Frame background, boolean fitModeChanged) { + float foregroundAspect = (float)video.getFormat().getWidth() / video.getFormat().getHeight(); + float backgroundAspect = (float)background.getFormat().getWidth() / background.getFormat().getHeight(); + float currentRelativeAspect = foregroundAspect/backgroundAspect; + if (currentRelativeAspect != mRelativeAspect || fitModeChanged) { + mRelativeAspect = currentRelativeAspect; + float xMin = 0.f, xWidth = 1.f, yMin = 0.f, yWidth = 1.f; + switch (mBackgroundFitMode) { + case BACKGROUND_STRETCH: + // Just map 1:1 + break; + case BACKGROUND_FIT: + if (mRelativeAspect > 1.0f) { + // Foreground is wider than background, scale down + // background in X + xMin = 0.5f - 0.5f * mRelativeAspect; + xWidth = 1.f * mRelativeAspect; + } else { + // Foreground is taller than background, scale down + // background in Y + yMin = 0.5f - 0.5f / mRelativeAspect; + yWidth = 1 / mRelativeAspect; + } + break; + case BACKGROUND_FILL_CROP: + if (mRelativeAspect > 1.0f) { + // Foreground is wider than background, crop + // background in Y + yMin = 0.5f - 0.5f / mRelativeAspect; + yWidth = 1.f / mRelativeAspect; + } else { + // Foreground is taller than background, crop + // background in X + xMin = 0.5f - 0.5f * mRelativeAspect; + xWidth = mRelativeAspect; + } + break; + } + // If mirroring is required (for ex. the camera mirrors the preview + // in the front camera) + // TODO: Backdropper does not attempt to apply any other transformation + // than just flipping. However, in the current state, it's "x-axis" is always aligned + // with the Camera's width. Hence, we need to define the mirroring based on the camera + // orientation. In the future, a cleaner design would be to cast away all the rotation + // in a separate place. + if (mMirrorBg) { + if (mLogVerbose) Log.v(TAG, "Mirroring the background!"); + // Mirroring in portrait + if (mOrientation == 0 || mOrientation == 180) { + xWidth = -xWidth; + xMin = 1.0f - xMin; + } else { + // Mirroring in landscape + yWidth = -yWidth; + yMin = 1.0f - yMin; + } + } + if (mLogVerbose) Log.v(TAG, "bgTransform: xMin, yMin, xWidth, yWidth : " + + xMin + ", " + yMin + ", " + xWidth + ", " + yWidth + + ", mRelAspRatio = " + mRelativeAspect); + // The following matrix is the transpose of the actual matrix + float[] bgTransform = {xWidth, 0.f, 0.f, + 0.f, yWidth, 0.f, + xMin, yMin, 1.f}; + mBgSubtractProgram.setHostValue("bg_fit_transform", bgTransform); + } + } + + private int pyramidLevel(int size) { + return (int)Math.floor(Math.log10(size) / Math.log10(2)) - 1; + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java new file mode 100644 index 0000000..3657d8a --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java @@ -0,0 +1,469 @@ +/* + * 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 android.filterpacks.videosink; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; +import android.os.ConditionVariable; +import android.media.MediaRecorder; +import android.media.CamcorderProfile; +import android.filterfw.core.GLEnvironment; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** @hide */ +public class MediaEncoderFilter extends Filter { + + /** User-visible parameters */ + + /** Recording state. When set to false, recording will stop, or will not + * start if not yet running the graph. Instead, frames are simply ignored. + * When switched back to true, recording will restart. This allows a single + * graph to both provide preview and to record video. If this is false, + * recording settings can be updated while the graph is running. + */ + @GenerateFieldPort(name = "recording", hasDefault = true) + private boolean mRecording = true; + + /** Filename to save the output. */ + @GenerateFieldPort(name = "outputFile", hasDefault = true) + private String mOutputFile = new String("/sdcard/MediaEncoderOut.mp4"); + + /** File Descriptor to save the output. */ + @GenerateFieldPort(name = "outputFileDescriptor", hasDefault = true) + private FileDescriptor mFd = null; + + /** Input audio source. If not set, no audio will be recorded. + * Select from the values in MediaRecorder.AudioSource + */ + @GenerateFieldPort(name = "audioSource", hasDefault = true) + private int mAudioSource = NO_AUDIO_SOURCE; + + /** Media recorder info listener, which needs to implement + * MediaRecorder.OnInfoListener. Set this to receive notifications about + * recording events. + */ + @GenerateFieldPort(name = "infoListener", hasDefault = true) + private MediaRecorder.OnInfoListener mInfoListener = null; + + /** Media recorder error listener, which needs to implement + * MediaRecorder.OnErrorListener. Set this to receive notifications about + * recording errors. + */ + @GenerateFieldPort(name = "errorListener", hasDefault = true) + private MediaRecorder.OnErrorListener mErrorListener = null; + + /** Media recording done callback, which needs to implement OnRecordingDoneListener. + * Set this to finalize media upon completion of media recording. + */ + @GenerateFieldPort(name = "recordingDoneListener", hasDefault = true) + private OnRecordingDoneListener mRecordingDoneListener = null; + + /** Orientation hint. Used for indicating proper video playback orientation. + * Units are in degrees of clockwise rotation, valid values are (0, 90, 180, + * 270). + */ + @GenerateFieldPort(name = "orientationHint", hasDefault = true) + private int mOrientationHint = 0; + + /** Camcorder profile to use. Select from the profiles available in + * android.media.CamcorderProfile. If this field is set, it overrides + * settings to width, height, framerate, outputFormat, and videoEncoder. + */ + @GenerateFieldPort(name = "recordingProfile", hasDefault = true) + private CamcorderProfile mProfile = null; + + /** Frame width to be encoded, defaults to 320. + * Actual received frame size has to match this */ + @GenerateFieldPort(name = "width", hasDefault = true) + private int mWidth = 320; + + /** Frame height to to be encoded, defaults to 240. + * Actual received frame size has to match */ + @GenerateFieldPort(name = "height", hasDefault = true) + private int mHeight = 240; + + /** Stream framerate to encode the frames at. + * By default, frames are encoded at 30 FPS*/ + @GenerateFieldPort(name = "framerate", hasDefault = true) + private int mFps = 30; + + /** The output format to encode the frames in. + * Choose an output format from the options in + * android.media.MediaRecorder.OutputFormat */ + @GenerateFieldPort(name = "outputFormat", hasDefault = true) + private int mOutputFormat = MediaRecorder.OutputFormat.MPEG_4; + + /** The videoencoder to encode the frames with. + * Choose a videoencoder from the options in + * android.media.MediaRecorder.VideoEncoder */ + @GenerateFieldPort(name = "videoEncoder", hasDefault = true) + private int mVideoEncoder = MediaRecorder.VideoEncoder.H264; + + /** The input region to read from the frame. The corners of this quad are + * mapped to the output rectangle. The input frame ranges from (0,0)-(1,1), + * top-left to bottom-right. The corners of the quad are specified in the + * order bottom-left, bottom-right, top-left, top-right. + */ + @GenerateFieldPort(name = "inputRegion", hasDefault = true) + private Quad mSourceRegion; + + /** The maximum filesize (in bytes) of the recording session. + * By default, it will be 0 and will be passed on to the MediaRecorder. + * If the limit is zero or negative, MediaRecorder will disable the limit*/ + @GenerateFieldPort(name = "maxFileSize", hasDefault = true) + private long mMaxFileSize = 0; + + /** The maximum duration (in milliseconds) of the recording session. + * By default, it will be 0 and will be passed on to the MediaRecorder. + * If the limit is zero or negative, MediaRecorder will record indefinitely*/ + @GenerateFieldPort(name = "maxDurationMs", hasDefault = true) + private int mMaxDurationMs = 0; + + /** TimeLapse Interval between frames. + * By default, it will be 0. Whether the recording is timelapsed + * is inferred based on its value being greater than 0 */ + @GenerateFieldPort(name = "timelapseRecordingIntervalUs", hasDefault = true) + private long mTimeBetweenTimeLapseFrameCaptureUs = 0; + + // End of user visible parameters + + private static final int NO_AUDIO_SOURCE = -1; + + private int mSurfaceId; + private ShaderProgram mProgram; + private GLFrame mScreen; + + private boolean mRecordingActive = false; + private long mTimestampNs = 0; + private long mLastTimeLapseFrameRealTimestampNs = 0; + private int mNumFramesEncoded = 0; + // Used to indicate whether recording is timelapsed. + // Inferred based on (mTimeBetweenTimeLapseFrameCaptureUs > 0) + private boolean mCaptureTimeLapse = false; + + private boolean mLogVerbose; + private static final String TAG = "MediaEncoderFilter"; + + // Our hook to the encoder + private MediaRecorder mMediaRecorder; + + /** Callback to be called when media recording completes. */ + + public interface OnRecordingDoneListener { + public void onRecordingDone(); + } + + public MediaEncoderFilter(String name) { + super(name); + Point bl = new Point(0, 0); + Point br = new Point(1, 0); + Point tl = new Point(0, 1); + Point tr = new Point(1, 1); + mSourceRegion = new Quad(bl, br, tl, tr); + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port- will accept RGBA GLFrames + addMaskedInputPort("videoframe", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Port " + name + " has been updated"); + if (name.equals("recording")) return; + if (name.equals("inputRegion")) { + if (isOpen()) updateSourceRegion(); + return; + } + // TODO: Not sure if it is possible to update the maxFileSize + // when the recording is going on. For now, not doing that. + if (isOpen() && mRecordingActive) { + throw new RuntimeException("Cannot change recording parameters" + + " when the filter is recording!"); + } + } + + private void updateSourceRegion() { + // Flip source quad to map to OpenGL origin + Quad flippedRegion = new Quad(); + flippedRegion.p0 = mSourceRegion.p2; + flippedRegion.p1 = mSourceRegion.p3; + flippedRegion.p2 = mSourceRegion.p0; + flippedRegion.p3 = mSourceRegion.p1; + mProgram.setSourceRegion(flippedRegion); + } + + // update the MediaRecorderParams based on the variables. + // These have to be in certain order as per the MediaRecorder + // documentation + private void updateMediaRecorderParams() { + mCaptureTimeLapse = mTimeBetweenTimeLapseFrameCaptureUs > 0; + final int GRALLOC_BUFFER = 2; + mMediaRecorder.setVideoSource(GRALLOC_BUFFER); + if (!mCaptureTimeLapse && (mAudioSource != NO_AUDIO_SOURCE)) { + mMediaRecorder.setAudioSource(mAudioSource); + } + if (mProfile != null) { + mMediaRecorder.setProfile(mProfile); + mFps = mProfile.videoFrameRate; + } else { + mMediaRecorder.setOutputFormat(mOutputFormat); + mMediaRecorder.setVideoEncoder(mVideoEncoder); + mMediaRecorder.setVideoSize(mWidth, mHeight); + mMediaRecorder.setVideoFrameRate(mFps); + } + mMediaRecorder.setOrientationHint(mOrientationHint); + mMediaRecorder.setOnInfoListener(mInfoListener); + mMediaRecorder.setOnErrorListener(mErrorListener); + if (mFd != null) { + mMediaRecorder.setOutputFile(mFd); + } else { + mMediaRecorder.setOutputFile(mOutputFile); + } + try { + mMediaRecorder.setMaxFileSize(mMaxFileSize); + } catch (Exception e) { + // Following the logic in VideoCamera.java (in Camera app) + // We are going to ignore failure of setMaxFileSize here, as + // a) The composer selected may simply not support it, or + // b) The underlying media framework may not handle 64-bit range + // on the size restriction. + Log.w(TAG, "Setting maxFileSize on MediaRecorder unsuccessful! " + + e.getMessage()); + } + mMediaRecorder.setMaxDuration(mMaxDurationMs); + } + + @Override + public void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing"); + + mProgram = ShaderProgram.createIdentity(context); + + mRecordingActive = false; + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening"); + updateSourceRegion(); + if (mRecording) startRecording(context); + } + + private void startRecording(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting recording"); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = new MutableFrameFormat( + FrameFormat.TYPE_BYTE, FrameFormat.TARGET_GPU); + screenFormat.setBytesPerSample(4); + + int width, height; + if (mProfile != null) { + width = mProfile.videoFrameWidth; + height = mProfile.videoFrameHeight; + } else { + width = mWidth; + height = mHeight; + } + screenFormat.setDimensions(width, height); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame( + screenFormat, GLFrame.EXISTING_FBO_BINDING, 0); + + // Initialize the media recorder + + mMediaRecorder = new MediaRecorder(); + updateMediaRecorderParams(); + + try { + mMediaRecorder.prepare(); + } catch (IllegalStateException e) { + throw e; + } catch (IOException e) { + throw new RuntimeException("IOException in" + + "MediaRecorder.prepare()!", e); + } catch (Exception e) { + throw new RuntimeException("Unknown Exception in" + + "MediaRecorder.prepare()!", e); + } + // Make sure start() is called before trying to + // register the surface. The native window handle needed to create + // the surface is initiated in start() + mMediaRecorder.start(); + if (mLogVerbose) Log.v(TAG, "Open: registering surface from Mediarecorder"); + mSurfaceId = context.getGLEnvironment(). + registerSurfaceFromMediaRecorder(mMediaRecorder); + mNumFramesEncoded = 0; + mRecordingActive = true; + } + + public boolean skipFrameAndModifyTimestamp(long timestampNs) { + // first frame- encode. Don't skip + if (mNumFramesEncoded == 0) { + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = timestampNs; + if (mLogVerbose) Log.v(TAG, "timelapse: FIRST frame, last real t= " + + mLastTimeLapseFrameRealTimestampNs + + ", setting t = " + mTimestampNs ); + return false; + } + + // Workaround to bypass the first 2 input frames for skipping. + // The first 2 output frames from the encoder are: decoder specific info and + // the compressed video frame data for the first input video frame. + if (mNumFramesEncoded >= 2 && timestampNs < + (mLastTimeLapseFrameRealTimestampNs + 1000L * mTimeBetweenTimeLapseFrameCaptureUs)) { + // If 2 frames have been already encoded, + // Skip all frames from last encoded frame until + // sufficient time (mTimeBetweenTimeLapseFrameCaptureUs) has passed. + if (mLogVerbose) Log.v(TAG, "timelapse: skipping intermediate frame"); + return true; + } else { + // Desired frame has arrived after mTimeBetweenTimeLapseFrameCaptureUs time: + // - Reset mLastTimeLapseFrameRealTimestampNs to current time. + // - Artificially modify timestampNs to be one frame time (1/framerate) ahead + // of the last encoded frame's time stamp. + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, Timestamp t = " + timestampNs + + ", last real t= " + mLastTimeLapseFrameRealTimestampNs + + ", interval = " + mTimeBetweenTimeLapseFrameCaptureUs); + mLastTimeLapseFrameRealTimestampNs = timestampNs; + mTimestampNs = mTimestampNs + (1000000000L / (long)mFps); + if (mLogVerbose) Log.v(TAG, "timelapse: encoding frame, setting t = " + + mTimestampNs + ", delta t = " + (1000000000L / (long)mFps) + + ", fps = " + mFps ); + return false; + } + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = context.getGLEnvironment(); + // Get input frame + Frame input = pullInput("videoframe"); + + // Check if recording needs to start + if (!mRecordingActive && mRecording) { + startRecording(context); + } + // Check if recording needs to stop + if (mRecordingActive && !mRecording) { + stopRecording(context); + } + + if (!mRecordingActive) return; + + if (mCaptureTimeLapse) { + if (skipFrameAndModifyTimestamp(input.getTimestamp())) { + return; + } + } else { + mTimestampNs = input.getTimestamp(); + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(input, mScreen); + + // Set timestamp from input + glEnv.setSurfaceTimestamp(mTimestampNs); + // And swap buffers + glEnv.swapBuffers(); + mNumFramesEncoded++; + if (mLogVerbose) Log.v(TAG, "numFramesEncoded = " + mNumFramesEncoded); + } + + private void stopRecording(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Stopping recording"); + + mRecordingActive = false; + mNumFramesEncoded = 0; + GLEnvironment glEnv = context.getGLEnvironment(); + // The following call will switch the surface_id to 0 + // (thus, calling eglMakeCurrent on surface with id 0) and + // then call eglDestroy on the surface. Hence, this will + // call disconnect the SurfaceMediaSource, which is needed to + // be called before calling Stop on the mediarecorder + if (mLogVerbose) Log.v(TAG, String.format("Unregistering surface %d", mSurfaceId)); + glEnv.unregisterSurfaceId(mSurfaceId); + try { + mMediaRecorder.stop(); + } catch (RuntimeException e) { + throw new MediaRecorderStopException("MediaRecorder.stop() failed!", e); + } + mMediaRecorder.release(); + mMediaRecorder = null; + + mScreen.release(); + mScreen = null; + + // Use an EffectsRecorder callback to forward a media finalization + // call so that it creates the video thumbnail, and whatever else needs + // to be done to finalize media. + if (mRecordingDoneListener != null) { + mRecordingDoneListener.onRecordingDone(); + } + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing"); + if (mRecordingActive) stopRecording(context); + } + + @Override + public void tearDown(FilterContext context) { + // Release all the resources associated with the MediaRecorder + // and GLFrame members + if (mMediaRecorder != null) { + mMediaRecorder.release(); + } + if (mScreen != null) { + mScreen.release(); + } + + } + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java new file mode 100644 index 0000000..dbf9768 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java @@ -0,0 +1,44 @@ +/* + * 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 android.filterpacks.videosink; + +import java.lang.RuntimeException; +import android.util.Log; + +/** @hide **/ +public class MediaRecorderStopException extends RuntimeException { + + private static final String TAG = "MediaRecorderStopException"; + + public MediaRecorderStopException(String msg) { + super(msg); + } + + public MediaRecorderStopException() { + super(); + } + + public MediaRecorderStopException(String msg, Throwable t) { + super(msg, t); + } + + public MediaRecorderStopException(Throwable t) { + super(t); + } +} + diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java new file mode 100644 index 0000000..2c474ab --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java @@ -0,0 +1,358 @@ +/* + * 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 android.filterpacks.videosrc; + +import android.content.Context; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.os.ConditionVariable; +import android.opengl.Matrix; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** + * @hide + */ +public class CameraSource extends Filter { + + /** User-visible parameters */ + + /** Camera ID to use for input. Defaults to 0. */ + @GenerateFieldPort(name = "id", hasDefault = true) + private int mCameraId = 0; + + /** Frame width to request from camera. Actual size may not match requested. */ + @GenerateFieldPort(name = "width", hasDefault = true) + private int mWidth = 320; + + /** Frame height to request from camera. Actual size may not match requested. */ + @GenerateFieldPort(name = "height", hasDefault = true) + private int mHeight = 240; + + /** Stream framerate to request from camera. Actual frame rate may not match requested. */ + @GenerateFieldPort(name = "framerate", hasDefault = true) + private int mFps = 30; + + /** Whether the filter should always wait for a new frame from the camera + * before providing output. If set to false, the filter will keep + * outputting the last frame it received from the camera if multiple process + * calls are received before the next update from the Camera. Defaults to true. + */ + @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + private Camera mCamera; + private GLFrame mCameraFrame; + private SurfaceTexture mSurfaceTexture; + private ShaderProgram mFrameExtractor; + private MutableFrameFormat mOutputFormat; + + private float[] mCameraTransform; + private float[] mMappedCoords; + // These default source coordinates perform the necessary flip + // for converting from OpenGL origin to MFF/Bitmap origin. + private static final float[] mSourceCoords = { 0, 1, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 1, 0, 0, 1 }; + + private static final int NEWFRAME_TIMEOUT = 100; //ms + private static final int NEWFRAME_TIMEOUT_REPEAT = 10; + + private boolean mNewFrameAvailable; + + private Camera.Parameters mCameraParameters; + + private static final String mFrameShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + private final boolean mLogVerbose; + private static final String TAG = "CameraSource"; + + public CameraSource(String name) { + super(name); + mCameraTransform = new float[16]; + mMappedCoords = new float[16]; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + public void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing"); + // Compile shader TODO: Move to onGLEnvSomething? + mFrameExtractor = new ShaderProgram(context, mFrameShader); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening"); + // Open camera + mCamera = Camera.open(mCameraId); + + // Set parameters + getCameraParameters(); + mCamera.setParameters(mCameraParameters); + + // Create frame formats + createFormats(); + + // Bind it to our camera frame + mCameraFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + mSurfaceTexture = new SurfaceTexture(mCameraFrame.getTextureId()); + try { + mCamera.setPreviewTexture(mSurfaceTexture); + } catch (IOException e) { + throw new RuntimeException("Could not bind camera surface texture: " + + e.getMessage() + "!"); + } + + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener); + // Start the preview + mNewFrameAvailable = false; + mCamera.startPreview(); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + if (mWaitForNewFrame) { + int waitCount = 0; + while (!mNewFrameAvailable) { + if (waitCount == NEWFRAME_TIMEOUT_REPEAT) { + throw new RuntimeException("Timeout waiting for new frame"); + } + try { + this.wait(NEWFRAME_TIMEOUT); + } catch (InterruptedException e) { + if (mLogVerbose) Log.v(TAG, "Interrupted while waiting for new frame"); + } + } + mNewFrameAvailable = false; + if (mLogVerbose) Log.v(TAG, "Got new frame"); + } + + mSurfaceTexture.updateTexImage(); + + if (mLogVerbose) Log.v(TAG, "Using frame extractor in thread: " + Thread.currentThread()); + mSurfaceTexture.getTransformMatrix(mCameraTransform); + Matrix.multiplyMM(mMappedCoords, 0, + mCameraTransform, 0, + mSourceCoords, 0); + mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1], + mMappedCoords[4], mMappedCoords[5], + mMappedCoords[8], mMappedCoords[9], + mMappedCoords[12], mMappedCoords[13]); + + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mCameraFrame, output); + + long timestamp = mSurfaceTexture.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s"); + output.setTimestamp(timestamp); + + pushOutput("video", output); + + // Release pushed frame + output.release(); + + if (mLogVerbose) Log.v(TAG, "Done processing new frame"); + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing"); + + mCamera.release(); + mCamera = null; + mSurfaceTexture.release(); + mSurfaceTexture = null; + } + + @Override + public void tearDown(FilterContext context) { + if (mCameraFrame != null) { + mCameraFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (name.equals("framerate")) { + getCameraParameters(); + int closestRange[] = findClosestFpsRange(mFps, mCameraParameters); + mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + mCamera.setParameters(mCameraParameters); + } + } + + synchronized public Camera.Parameters getCameraParameters() { + boolean closeCamera = false; + if (mCameraParameters == null) { + if (mCamera == null) { + mCamera = Camera.open(mCameraId); + closeCamera = true; + } + mCameraParameters = mCamera.getParameters(); + + if (closeCamera) { + mCamera.release(); + mCamera = null; + } + } + + int closestSize[] = findClosestSize(mWidth, mHeight, mCameraParameters); + mWidth = closestSize[0]; + mHeight = closestSize[1]; + mCameraParameters.setPreviewSize(mWidth, mHeight); + + int closestRange[] = findClosestFpsRange(mFps, mCameraParameters); + + mCameraParameters.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + + return mCameraParameters; + } + + /** Update camera parameters. Image resolution cannot be changed. */ + synchronized public void setCameraParameters(Camera.Parameters params) { + params.setPreviewSize(mWidth, mHeight); + mCameraParameters = params; + if (isOpen()) { + mCamera.setParameters(mCameraParameters); + } + } + + private int[] findClosestSize(int width, int height, Camera.Parameters parameters) { + List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes(); + int closestWidth = -1; + int closestHeight = -1; + int smallestWidth = previewSizes.get(0).width; + int smallestHeight = previewSizes.get(0).height; + for (Camera.Size size : previewSizes) { + // Best match defined as not being larger in either dimension than + // the requested size, but as close as possible. The below isn't a + // stable selection (reording the size list can give different + // results), but since this is a fallback nicety, that's acceptable. + if ( size.width <= width && + size.height <= height && + size.width >= closestWidth && + size.height >= closestHeight) { + closestWidth = size.width; + closestHeight = size.height; + } + if ( size.width < smallestWidth && + size.height < smallestHeight) { + smallestWidth = size.width; + smallestHeight = size.height; + } + } + if (closestWidth == -1) { + // Requested size is smaller than any listed size; match with smallest possible + closestWidth = smallestWidth; + closestHeight = smallestHeight; + } + + if (mLogVerbose) { + Log.v(TAG, + "Requested resolution: (" + width + ", " + height + + "). Closest match: (" + closestWidth + ", " + + closestHeight + ")."); + } + int[] closestSize = {closestWidth, closestHeight}; + return closestSize; + } + + private int[] findClosestFpsRange(int fps, Camera.Parameters params) { + List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange(); + int[] closestRange = supportedFpsRanges.get(0); + for (int[] range : supportedFpsRanges) { + if (range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] < fps*1000 && + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] > fps*1000 && + range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] > + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] && + range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] < + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]) { + closestRange = range; + } + } + if (mLogVerbose) Log.v(TAG, "Requested fps: " + fps + + ".Closest frame rate range: [" + + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] / 1000. + + "," + + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX] / 1000. + + "]"); + + return closestRange; + } + + private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from camera"); + synchronized(CameraSource.this) { + mNewFrameAvailable = true; + CameraSource.this.notify(); + } + } + }; + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java new file mode 100644 index 0000000..9c40cec --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java @@ -0,0 +1,567 @@ +/* + * 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 android.filterpacks.videosrc; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.media.MediaPlayer; +import android.os.ConditionVariable; +import android.opengl.Matrix; +import android.view.Surface; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.lang.IllegalArgumentException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** + * @hide + */ +public class MediaSource extends Filter { + + /** User-visible parameters */ + + /** The source URL for the media source. Can be an http: link to a remote + * resource, or a file: link to a local media file + */ + @GenerateFieldPort(name = "sourceUrl", hasDefault = true) + private String mSourceUrl = ""; + + /** An open asset file descriptor to a local media source. Default is null */ + @GenerateFieldPort(name = "sourceAsset", hasDefault = true) + private AssetFileDescriptor mSourceAsset = null; + + /** Whether the media source is a URL or an asset file descriptor. Defaults + * to false. + */ + @GenerateFieldPort(name = "sourceIsUrl", hasDefault = true) + private boolean mSelectedIsUrl = false; + + /** Whether the filter will always wait for a new video frame, or whether it + * will output an old frame again if a new frame isn't available. Defaults + * to true. + */ + @GenerateFinalPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + /** Whether the media source should loop automatically or not. Defaults to + * true. + */ + @GenerateFieldPort(name = "loop", hasDefault = true) + private boolean mLooping = true; + + /** Volume control. Currently sound is piped directly to the speakers, so + * this defaults to mute. + */ + @GenerateFieldPort(name = "volume", hasDefault = true) + private float mVolume = 0.f; + + /** Orientation. This controls the output orientation of the video. Valid + * values are 0, 90, 180, 270 + */ + @GenerateFieldPort(name = "orientation", hasDefault = true) + private int mOrientation = 0; + + private MediaPlayer mMediaPlayer; + private GLFrame mMediaFrame; + private SurfaceTexture mSurfaceTexture; + private ShaderProgram mFrameExtractor; + private MutableFrameFormat mOutputFormat; + private int mWidth, mHeight; + + // Total timeouts will be PREP_TIMEOUT*PREP_TIMEOUT_REPEAT + private static final int PREP_TIMEOUT = 100; // ms + private static final int PREP_TIMEOUT_REPEAT = 100; + private static final int NEWFRAME_TIMEOUT = 100; //ms + private static final int NEWFRAME_TIMEOUT_REPEAT = 10; + + // This is an identity shader; not using the default identity + // shader because reading from a SurfaceTexture requires the + // GL_OES_EGL_image_external extension. + private final String mFrameShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + // The following transforms enable rotation of the decoded source. + // These are multiplied with the transform obtained from the + // SurfaceTexture to get the final transform to be set on the media source. + // Currently, given a device orientation, the MediaSource rotates in such a way + // that the source is displayed upright. A particular use case + // is "Background Replacement" feature in the Camera app + // where the MediaSource rotates the source to align with the camera feed and pass it + // on to the backdropper filter. The backdropper only does the blending + // and does not have to do any rotation + // (except for mirroring in case of front camera). + // TODO: Currently the rotations are spread over a bunch of stages in the + // pipeline. A cleaner design + // could be to cast away all the rotation in a separate filter or attach a transform + // to the frame so that MediaSource itself need not know about any rotation. + private static final float[] mSourceCoords_0 = { 1, 1, 0, 1, + 0, 1, 0, 1, + 1, 0, 0, 1, + 0, 0, 0, 1 }; + private static final float[] mSourceCoords_270 = { 0, 1, 0, 1, + 0, 0, 0, 1, + 1, 1, 0, 1, + 1, 0, 0, 1 }; + private static final float[] mSourceCoords_180 = { 0, 0, 0, 1, + 1, 0, 0, 1, + 0, 1, 0, 1, + 1, 1, 0, 1 }; + private static final float[] mSourceCoords_90 = { 1, 0, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 0, 1, 0, 1 }; + + private boolean mGotSize; + private boolean mPrepared; + private boolean mPlaying; + private boolean mNewFrameAvailable; + private boolean mOrientationUpdated; + private boolean mPaused; + private boolean mCompleted; + + private final boolean mLogVerbose; + private static final String TAG = "MediaSource"; + + public MediaSource(String name) { + super(name); + mNewFrameAvailable = false; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + protected void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing MediaSource"); + + mFrameExtractor = new ShaderProgram(context, mFrameShader); + // SurfaceTexture defines (0,0) to be bottom-left. The filter framework + // defines (0,0) as top-left, so do the flip here. + mFrameExtractor.setSourceRect(0, 1, 1, -1); + + createFormats(); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) { + Log.v(TAG, "Opening MediaSource"); + if (mSelectedIsUrl) { + Log.v(TAG, "Current URL is " + mSourceUrl); + } else { + Log.v(TAG, "Current source is Asset!"); + } + } + + mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame( + mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + + mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId()); + + if (!setupMediaPlayer(mSelectedIsUrl)) { + throw new RuntimeException("Error setting up MediaPlayer!"); + } + } + + @Override + public void process(FilterContext context) { + // Note: process is synchronized by its caller in the Filter base class + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + if (mMediaPlayer == null) { + // Something went wrong in initialization or parameter updates + throw new NullPointerException("Unexpected null media player!"); + } + + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } + + if (!mPlaying) { + int waitCount = 0; + if (mLogVerbose) Log.v(TAG, "Waiting for preparation to complete"); + while (!mGotSize || !mPrepared) { + try { + this.wait(PREP_TIMEOUT); + } catch (InterruptedException e) { + // ignoring + } + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } + waitCount++; + if (waitCount == PREP_TIMEOUT_REPEAT) { + mMediaPlayer.release(); + throw new RuntimeException("MediaPlayer timed out while preparing!"); + } + } + if (mLogVerbose) Log.v(TAG, "Starting playback"); + mMediaPlayer.start(); + } + + // Use last frame if paused, unless just starting playback, in which case + // we want at least one valid frame before pausing + if (!mPaused || !mPlaying) { + if (mWaitForNewFrame) { + if (mLogVerbose) Log.v(TAG, "Waiting for new frame"); + + int waitCount = 0; + while (!mNewFrameAvailable) { + if (waitCount == NEWFRAME_TIMEOUT_REPEAT) { + if (mCompleted) { + // Video playback is done, so close us down + closeOutputPort("video"); + return; + } else { + throw new RuntimeException("Timeout waiting for new frame!"); + } + } + try { + this.wait(NEWFRAME_TIMEOUT); + } catch (InterruptedException e) { + if (mLogVerbose) Log.v(TAG, "interrupted"); + // ignoring + } + waitCount++; + } + mNewFrameAvailable = false; + if (mLogVerbose) Log.v(TAG, "Got new frame"); + } + + mSurfaceTexture.updateTexImage(); + mOrientationUpdated = true; + } + if (mOrientationUpdated) { + float[] surfaceTransform = new float[16]; + mSurfaceTexture.getTransformMatrix(surfaceTransform); + + float[] sourceCoords = new float[16]; + switch (mOrientation) { + default: + case 0: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_0, 0); + break; + case 90: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_90, 0); + break; + case 180: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_180, 0); + break; + case 270: + Matrix.multiplyMM(sourceCoords, 0, + surfaceTransform, 0, + mSourceCoords_270, 0); + break; + } + if (mLogVerbose) { + Log.v(TAG, "OrientationHint = " + mOrientation); + String temp = String.format("SetSourceRegion: %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f", + sourceCoords[4], sourceCoords[5],sourceCoords[0], sourceCoords[1], + sourceCoords[12], sourceCoords[13],sourceCoords[8], sourceCoords[9]); + Log.v(TAG, temp); + } + mFrameExtractor.setSourceRegion(sourceCoords[4], sourceCoords[5], + sourceCoords[0], sourceCoords[1], + sourceCoords[12], sourceCoords[13], + sourceCoords[8], sourceCoords[9]); + mOrientationUpdated = false; + } + + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mMediaFrame, output); + + long timestamp = mSurfaceTexture.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Timestamp: " + (timestamp / 1000000000.0) + " s"); + output.setTimestamp(timestamp); + + pushOutput("video", output); + output.release(); + + mPlaying = true; + } + + @Override + public void close(FilterContext context) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + } + mPrepared = false; + mGotSize = false; + mPlaying = false; + mPaused = false; + mCompleted = false; + mNewFrameAvailable = false; + + mMediaPlayer.release(); + mMediaPlayer = null; + mSurfaceTexture.release(); + mSurfaceTexture = null; + if (mLogVerbose) Log.v(TAG, "MediaSource closed"); + } + + @Override + public void tearDown(FilterContext context) { + if (mMediaFrame != null) { + mMediaFrame.release(); + } + } + + // When updating the port values of the filter, users can update sourceIsUrl to switch + // between using URL objects or Assets. + // If updating only sourceUrl/sourceAsset, MediaPlayer gets reset if the current player + // uses Url objects/Asset. + // Otherwise the new sourceUrl/sourceAsset is stored and will be used when users switch + // sourceIsUrl next time. + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Parameter update"); + if (name.equals("sourceUrl")) { + if (isOpen()) { + if (mLogVerbose) Log.v(TAG, "Opening new source URL"); + if (mSelectedIsUrl) { + setupMediaPlayer(mSelectedIsUrl); + } + } + } else if (name.equals("sourceAsset") ) { + if (isOpen()) { + if (mLogVerbose) Log.v(TAG, "Opening new source FD"); + if (!mSelectedIsUrl) { + setupMediaPlayer(mSelectedIsUrl); + } + } + } else if (name.equals("loop")) { + if (isOpen()) { + mMediaPlayer.setLooping(mLooping); + } + } else if (name.equals("sourceIsUrl")) { + if (isOpen()){ + if (mSelectedIsUrl){ + if (mLogVerbose) Log.v(TAG, "Opening new source URL"); + } else { + if (mLogVerbose) Log.v(TAG, "Opening new source Asset"); + } + setupMediaPlayer(mSelectedIsUrl); + } + } else if (name.equals("volume")) { + if (isOpen()) { + mMediaPlayer.setVolume(mVolume, mVolume); + } + } else if (name.equals("orientation") && mGotSize) { + if (mOrientation == 0 || mOrientation == 180) { + mOutputFormat.setDimensions(mWidth, mHeight); + } else { + mOutputFormat.setDimensions(mHeight, mWidth); + } + mOrientationUpdated = true; + } + } + + synchronized public void pauseVideo(boolean pauseState) { + if (isOpen()) { + if (pauseState && !mPaused) { + mMediaPlayer.pause(); + } else if (!pauseState && mPaused) { + mMediaPlayer.start(); + } + } + mPaused = pauseState; + } + + /** Creates a media player, sets it up, and calls prepare */ + synchronized private boolean setupMediaPlayer(boolean useUrl) { + mPrepared = false; + mGotSize = false; + mPlaying = false; + mPaused = false; + mCompleted = false; + mNewFrameAvailable = false; + + if (mLogVerbose) Log.v(TAG, "Setting up playback."); + + if (mMediaPlayer != null) { + // Clean up existing media players + if (mLogVerbose) Log.v(TAG, "Resetting existing MediaPlayer."); + mMediaPlayer.reset(); + } else { + // Create new media player + if (mLogVerbose) Log.v(TAG, "Creating new MediaPlayer."); + mMediaPlayer = new MediaPlayer(); + } + + if (mMediaPlayer == null) { + throw new RuntimeException("Unable to create a MediaPlayer!"); + } + + // Set up data sources, etc + try { + if (useUrl) { + if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to URI " + mSourceUrl); + mMediaPlayer.setDataSource(mSourceUrl); + } else { + if (mLogVerbose) Log.v(TAG, "Setting MediaPlayer source to asset " + mSourceAsset); + mMediaPlayer.setDataSource(mSourceAsset.getFileDescriptor(), mSourceAsset.getStartOffset(), mSourceAsset.getLength()); + } + } catch(IOException e) { + mMediaPlayer.release(); + mMediaPlayer = null; + if (useUrl) { + throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e); + } else { + throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e); + } + } catch(IllegalArgumentException e) { + mMediaPlayer.release(); + mMediaPlayer = null; + if (useUrl) { + throw new RuntimeException(String.format("Unable to set MediaPlayer to URL %s!", mSourceUrl), e); + } else { + throw new RuntimeException(String.format("Unable to set MediaPlayer to asset %s!", mSourceAsset), e); + } + } + + mMediaPlayer.setLooping(mLooping); + mMediaPlayer.setVolume(mVolume, mVolume); + + // Bind it to our media frame + Surface surface = new Surface(mSurfaceTexture); + mMediaPlayer.setSurface(surface); + surface.release(); + + // Connect Media Player to callbacks + + mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); + mMediaPlayer.setOnPreparedListener(onPreparedListener); + mMediaPlayer.setOnCompletionListener(onCompletionListener); + + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onMediaFrameAvailableListener); + + if (mLogVerbose) Log.v(TAG, "Preparing MediaPlayer."); + mMediaPlayer.prepareAsync(); + + return true; + } + + private MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = + new MediaPlayer.OnVideoSizeChangedListener() { + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer sent dimensions: " + width + " x " + height); + if (!mGotSize) { + if (mOrientation == 0 || mOrientation == 180) { + mOutputFormat.setDimensions(width, height); + } else { + mOutputFormat.setDimensions(height, width); + } + mWidth = width; + mHeight = height; + } else { + if (mOutputFormat.getWidth() != width || + mOutputFormat.getHeight() != height) { + Log.e(TAG, "Multiple video size change events received!"); + } + } + synchronized(MediaSource.this) { + mGotSize = true; + MediaSource.this.notify(); + } + } + }; + + private MediaPlayer.OnPreparedListener onPreparedListener = + new MediaPlayer.OnPreparedListener() { + public void onPrepared(MediaPlayer mp) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer is prepared"); + synchronized(MediaSource.this) { + mPrepared = true; + MediaSource.this.notify(); + } + } + }; + + private MediaPlayer.OnCompletionListener onCompletionListener = + new MediaPlayer.OnCompletionListener() { + public void onCompletion(MediaPlayer mp) { + if (mLogVerbose) Log.v(TAG, "MediaPlayer has completed playback"); + synchronized(MediaSource.this) { + mCompleted = true; + } + } + }; + + private SurfaceTexture.OnFrameAvailableListener onMediaFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from media player"); + synchronized(MediaSource.this) { + if (mLogVerbose) Log.v(TAG, "New frame: notify"); + mNewFrameAvailable = true; + MediaSource.this.notify(); + if (mLogVerbose) Log.v(TAG, "New frame: notify done"); + } + } + }; + +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java new file mode 100644 index 0000000..37fa242 --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java @@ -0,0 +1,265 @@ +/* + * 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 android.filterpacks.videosrc; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; +import android.graphics.SurfaceTexture; +import android.media.MediaPlayer; +import android.os.ConditionVariable; +import android.opengl.Matrix; + +import java.io.IOException; +import java.io.FileDescriptor; +import java.lang.IllegalArgumentException; +import java.util.List; +import java.util.Set; + +import android.util.Log; + +/** <p>A filter that converts textures from a SurfaceTexture object into frames for + * processing in the filter framework.</p> + * + * <p>To use, connect up the sourceListener callback, and then when executing + * the graph, use the SurfaceTexture object passed to the callback to feed + * frames into the filter graph. For example, pass the SurfaceTexture into + * {#link + * android.hardware.Camera.setPreviewTexture(android.graphics.SurfaceTexture)}. + * This filter is intended for applications that need for flexibility than the + * CameraSource and MediaSource provide. Note that the application needs to + * provide width and height information for the SurfaceTextureSource, which it + * should obtain from wherever the SurfaceTexture data is coming from to avoid + * unnecessary resampling.</p> + * + * @hide + */ +public class SurfaceTextureSource extends Filter { + + /** User-visible parameters */ + + /** The callback interface for the sourceListener parameter */ + public interface SurfaceTextureSourceListener { + public void onSurfaceTextureSourceReady(SurfaceTexture source); + } + /** A callback to send the internal SurfaceTexture object to, once it is + * created. This callback will be called when the the filter graph is + * preparing to execute, but before any processing has actually taken + * place. The SurfaceTexture object passed to this callback is the only way + * to feed this filter. When the filter graph is shutting down, this + * callback will be called again with null as the source. + * + * This callback may be called from an arbitrary thread, so it should not + * assume it is running in the UI thread in particular. + */ + @GenerateFinalPort(name = "sourceListener") + private SurfaceTextureSourceListener mSourceListener; + + /** The width of the output image frame. If the texture width for the + * SurfaceTexture source is known, use it here to minimize resampling. */ + @GenerateFieldPort(name = "width") + private int mWidth; + + /** The height of the output image frame. If the texture height for the + * SurfaceTexture source is known, use it here to minimize resampling. */ + @GenerateFieldPort(name = "height") + private int mHeight; + + /** Whether the filter will always wait for a new frame from its + * SurfaceTexture, or whether it will output an old frame again if a new + * frame isn't available. The filter will always wait for the first frame, + * to avoid outputting a blank frame. Defaults to true. + */ + @GenerateFieldPort(name = "waitForNewFrame", hasDefault = true) + private boolean mWaitForNewFrame = true; + + /** Maximum timeout before signaling error when waiting for a new frame. Set + * this to zero to disable the timeout and wait indefinitely. In milliseconds. + */ + @GenerateFieldPort(name = "waitTimeout", hasDefault = true) + private int mWaitTimeout = 1000; + + /** Whether a timeout is an exception-causing failure, or just causes the + * filter to close. + */ + @GenerateFieldPort(name = "closeOnTimeout", hasDefault = true) + private boolean mCloseOnTimeout = false; + + // Variables for input->output conversion + private GLFrame mMediaFrame; + private ShaderProgram mFrameExtractor; + private SurfaceTexture mSurfaceTexture; + private MutableFrameFormat mOutputFormat; + private ConditionVariable mNewFrameAvailable; + private boolean mFirstFrame; + + private float[] mFrameTransform; + private float[] mMappedCoords; + // These default source coordinates perform the necessary flip + // for converting from MFF/Bitmap origin to OpenGL origin. + private static final float[] mSourceCoords = { 0, 1, 0, 1, + 1, 1, 0, 1, + 0, 0, 0, 1, + 1, 0, 0, 1 }; + // Shader for output + private final String mRenderShader = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "uniform samplerExternalOES tex_sampler_0;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + + "}\n"; + + // Variables for logging + + private static final String TAG = "SurfaceTextureSource"; + private static final boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + public SurfaceTextureSource(String name) { + super(name); + mNewFrameAvailable = new ConditionVariable(); + mFrameTransform = new float[16]; + mMappedCoords = new float[16]; + } + + @Override + public void setupPorts() { + // Add input port + addOutputPort("video", ImageFormat.create(ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU)); + } + + private void createFormats() { + mOutputFormat = ImageFormat.create(mWidth, mHeight, + ImageFormat.COLORSPACE_RGBA, + FrameFormat.TARGET_GPU); + } + + @Override + protected void prepare(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Preparing SurfaceTextureSource"); + + createFormats(); + + // Prepare input + mMediaFrame = (GLFrame)context.getFrameManager().newBoundFrame(mOutputFormat, + GLFrame.EXTERNAL_TEXTURE, + 0); + + // Prepare output + mFrameExtractor = new ShaderProgram(context, mRenderShader); + } + + @Override + public void open(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Opening SurfaceTextureSource"); + // Create SurfaceTexture anew each time - it can use substantial memory. + mSurfaceTexture = new SurfaceTexture(mMediaFrame.getTextureId()); + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onFrameAvailableListener); + // Connect SurfaceTexture to source + mSourceListener.onSurfaceTextureSourceReady(mSurfaceTexture); + mFirstFrame = true; + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Processing new frame"); + + // First, get new frame if available + if (mWaitForNewFrame || mFirstFrame) { + boolean gotNewFrame; + if (mWaitTimeout != 0) { + gotNewFrame = mNewFrameAvailable.block(mWaitTimeout); + if (!gotNewFrame) { + if (!mCloseOnTimeout) { + throw new RuntimeException("Timeout waiting for new frame"); + } else { + if (mLogVerbose) Log.v(TAG, "Timeout waiting for a new frame. Closing."); + closeOutputPort("video"); + return; + } + } + } else { + mNewFrameAvailable.block(); + } + mNewFrameAvailable.close(); + mFirstFrame = false; + } + + mSurfaceTexture.updateTexImage(); + + mSurfaceTexture.getTransformMatrix(mFrameTransform); + Matrix.multiplyMM(mMappedCoords, 0, + mFrameTransform, 0, + mSourceCoords, 0); + mFrameExtractor.setSourceRegion(mMappedCoords[0], mMappedCoords[1], + mMappedCoords[4], mMappedCoords[5], + mMappedCoords[8], mMappedCoords[9], + mMappedCoords[12], mMappedCoords[13]); + // Next, render to output + Frame output = context.getFrameManager().newFrame(mOutputFormat); + mFrameExtractor.process(mMediaFrame, output); + + output.setTimestamp(mSurfaceTexture.getTimestamp()); + + pushOutput("video", output); + output.release(); + } + + @Override + public void close(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "SurfaceTextureSource closed"); + mSourceListener.onSurfaceTextureSourceReady(null); + mSurfaceTexture.release(); + mSurfaceTexture = null; + } + + @Override + public void tearDown(FilterContext context) { + if (mMediaFrame != null) { + mMediaFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + if (name.equals("width") || name.equals("height") ) { + mOutputFormat.setDimensions(mWidth, mHeight); + } + } + + private SurfaceTexture.OnFrameAvailableListener onFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + if (mLogVerbose) Log.v(TAG, "New frame from SurfaceTexture"); + mNewFrameAvailable.open(); + } + }; +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java new file mode 100644 index 0000000..436caab --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java @@ -0,0 +1,282 @@ +/* + * 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 android.filterpacks.videosrc; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterSurfaceView; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.GenerateFieldPort; +import android.filterfw.core.GenerateFinalPort; +import android.filterfw.core.GLEnvironment; +import android.filterfw.core.GLFrame; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeProgram; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.Program; +import android.filterfw.core.ShaderProgram; +import android.filterfw.format.ImageFormat; + +import android.filterfw.geometry.Quad; +import android.filterfw.geometry.Point; + +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import android.graphics.Rect; +import android.graphics.SurfaceTexture; + +import android.util.Log; + +/** + * @hide + */ +public class SurfaceTextureTarget extends Filter { + + private final int RENDERMODE_STRETCH = 0; + private final int RENDERMODE_FIT = 1; + private final int RENDERMODE_FILL_CROP = 2; + private final int RENDERMODE_CUSTOMIZE = 3; + + /** Required. Sets the destination surfaceTexture. + */ + @GenerateFinalPort(name = "surfaceTexture") + private SurfaceTexture mSurfaceTexture; + + /** Required. Sets the width of the output surfaceTexture images */ + @GenerateFinalPort(name = "width") + private int mScreenWidth; + + /** Required. Sets the height of the output surfaceTexture images */ + @GenerateFinalPort(name = "height") + private int mScreenHeight; + + + /** Optional. Control how the incoming frames are rendered onto the + * output. Default is FIT. + * RENDERMODE_STRETCH: Just fill the output surfaceView. + * RENDERMODE_FIT: Keep aspect ratio and fit without cropping. May + * have black bars. + * RENDERMODE_FILL_CROP: Keep aspect ratio and fit without black + * bars. May crop. + */ + @GenerateFieldPort(name = "renderMode", hasDefault = true) + private String mRenderModeString; + + @GenerateFieldPort(name = "sourceQuad", hasDefault = true) + private Quad mSourceQuad = new Quad(new Point(0.0f, 1.0f), + new Point(1.0f, 1.0f), + new Point(0.0f, 0.0f), + new Point(1.0f, 0.0f)); + + @GenerateFieldPort(name = "targetQuad", hasDefault = true) + private Quad mTargetQuad = new Quad(new Point(0.0f, 0.0f), + new Point(1.0f, 0.0f), + new Point(0.0f, 1.0f), + new Point(1.0f, 1.0f)); + + private int mSurfaceId; + + private ShaderProgram mProgram; + private GLFrame mScreen; + private int mRenderMode = RENDERMODE_FIT; + private float mAspectRatio = 1.f; + + private boolean mLogVerbose; + private static final String TAG = "SurfaceTextureTarget"; + + public SurfaceTextureTarget(String name) { + super(name); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void setupPorts() { + // Make sure we have a SurfaceView + if (mSurfaceTexture == null) { + throw new RuntimeException("Null SurfaceTexture passed to SurfaceTextureTarget"); + } + + // Add input port - will accept anything that's 4-channel. + addMaskedInputPort("frame", ImageFormat.create(ImageFormat.COLORSPACE_RGBA)); + } + + public void updateRenderMode() { + if (mRenderModeString != null) { + if (mRenderModeString.equals("stretch")) { + mRenderMode = RENDERMODE_STRETCH; + } else if (mRenderModeString.equals("fit")) { + mRenderMode = RENDERMODE_FIT; + } else if (mRenderModeString.equals("fill_crop")) { + mRenderMode = RENDERMODE_FILL_CROP; + } else if (mRenderModeString.equals("customize")) { + mRenderMode = RENDERMODE_CUSTOMIZE; + } else { + throw new RuntimeException("Unknown render mode '" + mRenderModeString + "'!"); + } + } + updateTargetRect(); + } + + @Override + public void prepare(FilterContext context) { + // Create identity shader to render, and make sure to render upside-down, as textures + // are stored internally bottom-to-top. + mProgram = ShaderProgram.createIdentity(context); + mProgram.setSourceRect(0, 1, 1, -1); + mProgram.setClearColor(0.0f, 0.0f, 0.0f); + + updateRenderMode(); + + // Create a frame representing the screen + MutableFrameFormat screenFormat = new MutableFrameFormat(FrameFormat.TYPE_BYTE, + FrameFormat.TARGET_GPU); + screenFormat.setBytesPerSample(4); + screenFormat.setDimensions(mScreenWidth, mScreenHeight); + mScreen = (GLFrame)context.getFrameManager().newBoundFrame(screenFormat, + GLFrame.EXISTING_FBO_BINDING, + 0); + } + + @Override + public void open(FilterContext context) { + // Set up SurfaceTexture internals + mSurfaceId = context.getGLEnvironment().registerSurfaceTexture(mSurfaceTexture, mScreenWidth, mScreenHeight); + } + + @Override + public void process(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Starting frame processing"); + + GLEnvironment glEnv = context.getGLEnvironment(); + + // Get input frame + Frame input = pullInput("frame"); + boolean createdFrame = false; + + float currentAspectRatio = (float)input.getFormat().getWidth() / input.getFormat().getHeight(); + if (currentAspectRatio != mAspectRatio) { + if (mLogVerbose) Log.v(TAG, "New aspect ratio: " + currentAspectRatio +", previously: " + mAspectRatio); + mAspectRatio = currentAspectRatio; + updateTargetRect(); + } + + // See if we need to copy to GPU + Frame gpuFrame = null; + if (mLogVerbose) Log.v("SurfaceTextureTarget", "Got input format: " + input.getFormat()); + + int target = input.getFormat().getTarget(); + if (target != FrameFormat.TARGET_GPU) { + gpuFrame = context.getFrameManager().duplicateFrameToTarget(input, + FrameFormat.TARGET_GPU); + createdFrame = true; + } else { + gpuFrame = input; + } + + // Activate our surface + glEnv.activateSurfaceWithId(mSurfaceId); + + // Process + mProgram.process(gpuFrame, mScreen); + + glEnv.setSurfaceTimestamp(input.getTimestamp()); + + // And swap buffers + glEnv.swapBuffers(); + + if (createdFrame) { + gpuFrame.release(); + } + } + + @Override + public void fieldPortValueUpdated(String name, FilterContext context) { + updateRenderMode(); + } + + @Override + public void tearDown(FilterContext context) { + if (mScreen != null) { + mScreen.release(); + } + } + + private void updateTargetRect() { + if (mScreenWidth > 0 && mScreenHeight > 0 && mProgram != null) { + float screenAspectRatio = (float)mScreenWidth / mScreenHeight; + float relativeAspectRatio = screenAspectRatio / mAspectRatio; + + if (relativeAspectRatio == 1.0f && mRenderMode != RENDERMODE_CUSTOMIZE) { + mProgram.setClearsOutput(false); + } else { + switch (mRenderMode) { + case RENDERMODE_STRETCH: + mProgram.setTargetRect(0, 0, 1, 1); + mTargetQuad.p0.set(0f, 0.0f); + mTargetQuad.p1.set(1f, 0.0f); + mTargetQuad.p2.set(0f, 1.0f); + mTargetQuad.p3.set(1f, 1.0f); + mProgram.setClearsOutput(false); + break; + case RENDERMODE_FIT: + if (relativeAspectRatio > 1.0f) { + // Screen is wider than the camera, scale down X + mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f); + mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f); + + } else { + // Screen is taller than the camera, scale down Y + mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio); + mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio); + } + mProgram.setClearsOutput(true); + break; + case RENDERMODE_FILL_CROP: + if (relativeAspectRatio > 1) { + // Screen is wider than the camera, crop in Y + mTargetQuad.p0.set(0.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p1.set(1.0f, 0.5f - 0.5f * relativeAspectRatio); + mTargetQuad.p2.set(0.0f, 0.5f + 0.5f * relativeAspectRatio); + mTargetQuad.p3.set(1.0f, 0.5f + 0.5f * relativeAspectRatio); + } else { + // Screen is taller than the camera, crop in X + mTargetQuad.p0.set(0.5f - 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p1.set(0.5f + 0.5f / relativeAspectRatio, 0.0f); + mTargetQuad.p2.set(0.5f - 0.5f / relativeAspectRatio, 1.0f); + mTargetQuad.p3.set(0.5f + 0.5f / relativeAspectRatio, 1.0f); + } + mProgram.setClearsOutput(true); + break; + case RENDERMODE_CUSTOMIZE: + ((ShaderProgram) mProgram).setSourceRegion(mSourceQuad); + break; + } + ((ShaderProgram) mProgram).setTargetRegion(mTargetQuad); + } + } + } +} diff --git a/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java new file mode 100644 index 0000000..d8fd0bd --- /dev/null +++ b/media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterpacks.videosrc; diff --git a/media/mca/filterpacks/native/base/geometry.cpp b/media/mca/filterpacks/native/base/geometry.cpp new file mode 100644 index 0000000..7812d50 --- /dev/null +++ b/media/mca/filterpacks/native/base/geometry.cpp @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#include <cutils/log.h> +#include <cmath> + +#include "geometry.h" + +namespace android { +namespace filterfw { + +float Point::Length() const { + return std::sqrt(x_ * x_ + y_ * y_); +} + +bool Point::ScaleTo(float new_length) { + float length = Length(); + if (length == 0.0f) { + return false; + } + x_ *= new_length / length; + y_ *= new_length / length; + return true; +} + +float Point::Distance(const Point& p0, const Point& p1) { + Point diff = p1 - p0; + return diff.Length(); +} + +Point Point::operator+(const Point& other) const { + Point out; + out.x_ = x_ + other.x_; + out.y_ = y_ + other.y_; + return out; +} + +Point Point::operator-(const Point& other) const { + Point out; + out.x_ = x_ - other.x_; + out.y_ = y_ - other.y_; + return out; +} + +Point Point::operator*(float factor) const { + Point out; + out.x_ = factor * x_; + out.y_ = factor * y_; + return out; +} + +void Point::Rotate90Clockwise() { + const float x = x_; + x_ = y_; + y_ = -x; +} + +bool Rect::ExpandToAspectRatio(float ratio) { + if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) { + return false; + } + + const float current_ratio = width / height; + if (current_ratio < ratio) { + const float dx = width * (ratio / current_ratio - 1.0f); + x -= dx / 2.0f; + width += dx; + } else { + const float dy = height * (current_ratio / ratio - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ExpandToMinLength(float length) { + if (width <= 0.0f || height <= 0.0f || length <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (length > current_length) { + const float dx = width * (length / current_length - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (length / current_length - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ScaleWithLengthLimit(float factor, float max_length) { + if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (current_length >= max_length) { + return true; + } + + float f = factor; + if (current_length * f > max_length) { + f *= max_length / (current_length * f); + } + + const float dx = width * (f - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (f - 1.0f); + y -= dy / 2.0f; + height += dy; + return true; +} + +const Point& Quad::point(int ix) const { + ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds"); + return points_[ix]; +} + +bool SlantedRect::FromCenterAxisAndLengths(const Point& center, + const Point& vert_axis, + const Point& lengths) { + Point dy = vert_axis; + if (!dy.ScaleTo(lengths.y() / 2.0f)) { + ALOGE("Illegal axis: %f %f", vert_axis.x(), vert_axis.y()); + return false; + } + + Point dx = dy; + dx.Rotate90Clockwise(); + dx.ScaleTo(lengths.x() / 2.0f); + + points_[0] = center - dx - dy; + points_[1] = center + dx - dy; + points_[2] = center - dx + dy; + points_[3] = center + dx + dy; + + width_ = lengths.x(); + height_ = lengths.y(); + + return true; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterpacks/native/base/geometry.h b/media/mca/filterpacks/native/base/geometry.h new file mode 100644 index 0000000..40a9343 --- /dev/null +++ b/media/mca/filterpacks/native/base/geometry.h @@ -0,0 +1,111 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H + +#include <vector> + +namespace android { +namespace filterfw { + +// This is an initial implementation of some geometrical structures. This is +// likely to grow and become more sophisticated in the future. + +class Point { + public: + Point() : x_(0.0f), y_(0.0f) {} + Point(float x, float y) : x_(x), y_(y) {} + + float x() const { return x_; } + float y() const { return y_; } + + float Length() const; + bool ScaleTo(float new_length); + static float Distance(const Point& p0, const Point& p1); + + // Add more of these as needed: + Point operator+(const Point& other) const; + Point operator-(const Point& other) const; + Point operator*(float factor) const; + + void Rotate90Clockwise(); + + private: + float x_, y_; +}; + +class Quad { + public: + Quad() : points_(4) {} + virtual ~Quad() {} + + Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) + : points_(4) { + points_[0] = p0; + points_[1] = p1; + points_[2] = p2; + points_[3] = p3; + } + + const std::vector<Point>& points() const { return points_; } + const Point& point(int ix) const; + + protected: + std::vector<Point> points_; +}; + +class SlantedRect : public Quad { + public: + SlantedRect() : width_(0.0f), height_(0.0f) {} + virtual ~SlantedRect() {} + + bool FromCenterAxisAndLengths(const Point& center, + const Point& vert_axis, + const Point& lenghts); + + float width() const { return width_; } + float height() const { return height_; } + + private: + float width_; + float height_; +}; + +struct Rect { + float x, y, width, height; + + Rect() { + x = y = 0.0f; + width = height = 1.0f; + } + + Rect(float x, float y, float width, float height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + } + + bool ExpandToAspectRatio(float ratio); + bool ExpandToMinLength(float length); + bool ScaleWithLengthLimit(float factor, float max_length); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_GEOMETRY_H diff --git a/media/mca/filterpacks/native/base/time_util.cpp b/media/mca/filterpacks/native/base/time_util.cpp new file mode 100644 index 0000000..1a78a95 --- /dev/null +++ b/media/mca/filterpacks/native/base/time_util.cpp @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#include "time_util.h" +#include "utilities.h" + +#include <cutils/log.h> +#include <sys/time.h> +#include <map> + +namespace android { +namespace filterfw { + +uint64_t getTimeUs() { + static long basesec; + struct timeval tv; + uint64_t nowtime; + gettimeofday(&tv, 0); + if (basesec == 0) { + basesec = tv.tv_sec; + } + nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 + + (uint64_t)tv.tv_usec; + return nowtime; +} + +const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100; + +NamedStopWatch::NamedStopWatch(const std::string& name) + : mName(name), + mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames), + mStartUSec(0), + mNumCalls(0), + mTotalUSec(0) { +} + +void NamedStopWatch::Start() { + mStartUSec = getTimeUs(); +} + +void NamedStopWatch::Stop() { + if (!mStartUSec) { + return; + } + uint64_t stopUSec = getTimeUs(); + if (stopUSec > mStartUSec) { + ++mNumCalls; + mTotalUSec += stopUSec - mStartUSec; + if (mNumCalls % mLoggingPeriodInFrames == 0) { + const float mSec = TotalUSec() * 1.0E-3f / NumCalls(); + ALOGE("%s: %f ms", Name().c_str(), mSec); + } + } + mStartUSec = 0; +} + +namespace { +static NamedStopWatch* GetWatchForName(const std::string& watch_name) { + // TODO: this leaks the NamedStopWatch objects. Replace it with a + // singleton to avoid that and make it thread safe. + static std::map<std::string, NamedStopWatch*> watches; + NamedStopWatch* watch = FindPtrOrNull(watches, watch_name); + if (!watch) { + watch = new NamedStopWatch(watch_name); + watches[watch_name] = watch; + } + return watch; +}; +} // namespace + +ScopedTimer::ScopedTimer(const std::string& stop_watch_name) { + mWatch = GetWatchForName(stop_watch_name); + mWatch->Start(); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterpacks/native/base/time_util.h b/media/mca/filterpacks/native/base/time_util.h new file mode 100644 index 0000000..60d76c6 --- /dev/null +++ b/media/mca/filterpacks/native/base/time_util.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H + +#include <string> +#include <utils/RefBase.h> + +#define LOG_MFF_RUNNING_TIMES 0 + +namespace android { +namespace filterfw { + +uint64_t getTimeUs(); + +class NamedStopWatch : public RefBase { + public: + static const uint64_t kDefaultLoggingPeriodInFrames; + + explicit NamedStopWatch(const std::string& name); + void Start(); + void Stop(); + + void SetName(const std::string& name) { mName = name; } + void SetLoggingPeriodInFrames(uint64_t numFrames) { + mLoggingPeriodInFrames = numFrames; + } + + const std::string& Name() const { return mName; } + uint64_t NumCalls() const { return mNumCalls; } + uint64_t TotalUSec() const { return mTotalUSec; } + + private: + std::string mName; + uint64_t mLoggingPeriodInFrames; + uint64_t mStartUSec; + uint64_t mNumCalls; + uint64_t mTotalUSec; +}; + +class ScopedTimer { + public: + explicit ScopedTimer(const std::string& stop_watch_name); + explicit ScopedTimer(NamedStopWatch* watch) + : mWatch(watch) { mWatch->Start(); } + ~ScopedTimer() { mWatch->Stop(); } + + private: + NamedStopWatch* mWatch; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_TIME_UTIL_H diff --git a/media/mca/filterpacks/native/base/utilities.h b/media/mca/filterpacks/native/base/utilities.h new file mode 100644 index 0000000..302e177 --- /dev/null +++ b/media/mca/filterpacks/native/base/utilities.h @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H + +#include <set> +#include <utility> + +namespace android { +namespace filterfw { + +// Convenience Macro to make copy constructor and assignment operator private +// (thereby disallowing copying and assigning). +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +// Perform a lookup in a map or hash_map. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +template <class Collection> +const typename Collection::value_type::second_type* +FindOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return &it->second; +} + +// A simple class that gives checklist functionality: There are essemtially two +// operations defined on a CheckList: +// - Adding a new (unchecked) item. +// - Checking off an item. +// When checking off the last remaining item CheckItem() returns true. +template<typename T> +class CheckList { + public: + // Add a new unchecked item. Does nothing if item is already in checklist. + void AddItem(const T& item); + + // Check off an item in the checklist. Returns true if all items have been + // checked. + bool CheckItem(const T& item); + + // Clear the checklist. + void Clear() { + items_.clear(); + } + + private: + std::set<T> items_; +}; + +template<typename T> +void CheckList<T>::AddItem(const T& item) { + if (!ContainsKey(items_, item)) + items_.insert(item); +} + +template<typename T> +bool CheckList<T>::CheckItem(const T& item) { + typename std::set<T>::iterator iter = items_.find(item); + if (iter != items_.end()) + items_.erase(iter); + return items_.empty(); +} + +// Perform a lookup in a map or hash_map whose values are pointers. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +// This function does not distinguish between a missing key and a key mapped +// to a NULL value. +template <class Collection> +const typename Collection::value_type::second_type +FindPtrOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return it->second; +} + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template <typename Collection, typename Key> +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Insert a new key and value into a map or hash_map. +// If the key is not present in the map the key and value are +// inserted, otherwise nothing happens. True indicates that an insert +// took place, false indicates the key was already present. +template <class Collection, class Key, class Value> +bool InsertIfNotPresent(Collection * const collection, + const Key& key, const Value& value) { + std::pair<typename Collection::iterator, bool> ret = + collection->insert(typename Collection::value_type(key, value)); + return ret.second; +} + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_UTILITIES_H diff --git a/media/mca/filterpacks/native/base/vec_types.h b/media/mca/filterpacks/native/base/vec_types.h new file mode 100644 index 0000000..65967c9 --- /dev/null +++ b/media/mca/filterpacks/native/base/vec_types.h @@ -0,0 +1,177 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H +#define ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H + +namespace android { +namespace filterfw { + +template < class T, int dim> +class VecBase { + public: + T data[dim]; + VecBase() {} + VecBase<T,dim>& operator = (const VecBase<T, dim> &x) { + memcpy(data, x.data, sizeof(T)*dim); + return *this; + } + T & operator [] (int i) { + // out of boundary not checked + return data[i]; + } + const T & operator [] (int i) const { + // out of boundary not checked + return data[i]; + } + T Length() { + double sum = 0; + for (int i = 0; i < dim; ++i) + sum += static_cast<double> (data[i] * data[i]); + return static_cast<T>(sqrt(sum)); + } +}; + +template < class T, int dim> +class Vec : public VecBase<T,dim> { + public: + Vec() {} + Vec<T,dim>& operator = (const Vec<T, dim> &x) { + memcpy(this->data, x.data, sizeof(T)*dim); + return *this; + } +}; + +template <class T, int dim> +Vec<T, dim> operator + (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] + y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator - (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] - y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator * (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] * y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator / (const Vec<T,dim> &x, const Vec<T,dim> &y) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] / y.data[i]; + return out; +} + +template <class T, int dim> +T dot(const Vec<T,dim> &x, const Vec<T,dim> &y) { + T out = 0; + for (int i = 0; i < dim; i++) + out += x.data[i] * y.data[i]; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator * (const Vec<T,dim> &x, T scale) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] * scale; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator / (const Vec<T,dim> &x, T scale) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] / scale; + return out; +} + +template <class T, int dim> +Vec<T, dim> operator + (const Vec<T,dim> &x, T val) { + Vec<T, dim> out; + for (int i = 0; i < dim; i++) + out.data[i] = x.data[i] + val; + return out; +} + +// specialization for vec2, vec3, vec4 float +template<> +class Vec<float, 2> : public VecBase<float, 2> { +public: + Vec() {} + Vec(float x, float y) { + data[0] = x; + data[1] = y; + } + Vec<float, 2>& operator = (const Vec<float, 2> &x) { + memcpy(data, x.data, sizeof(float)*2); + return *this; + } +}; + +template<> +class Vec<float, 3> { +public: + float data[3]; + Vec() {} + Vec(float x, float y, float z) { + data[0] = x; + data[1] = y; + data[2] = z; + } + Vec<float, 3>& operator = (const Vec<float, 3> &x) { + memcpy(data, x.data, sizeof(float)*3); + return *this; + } +}; + +template<> +class Vec<float, 4> { +public: + float data[4]; + Vec() {} + Vec(float x, float y, float z, float w) { + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + } + Vec<float, 4>& operator = (const Vec<float, 4> &x) { + memcpy(data, x.data, sizeof(float)*4); + return *this; + } +}; + +typedef Vec<float,2> Vec2f; +typedef Vec<float,3> Vec3f; +typedef Vec<float,4> Vec4f; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_FILTERPACKS_BASE_VEC_TYPES_H diff --git a/media/mca/filterpacks/native/imageproc/brightness.c b/media/mca/filterpacks/native/imageproc/brightness.c new file mode 100644 index 0000000..f4addf1 --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/brightness.c @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#include <android/log.h> +#include <stdlib.h> + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__) + +typedef struct { + float brightness; +} BrightnessParameters; + +typedef union { + int value; + char rgba[4]; +} Pixel; + +void brightness_init(void** user_data) { + (*user_data) = malloc(sizeof(BrightnessParameters)); +} + +void brightness_teardown(void* user_data) { + free(user_data); +} + +void brightness_setvalue(const char* key, const char* value, void* user_data) { + if (strcmp(key, "brightness") == 0) + ((BrightnessParameters*)user_data)->brightness = atof(value); + else + LOGE("Unknown parameter: %s!", key); +} + +int brightness_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) { + LOGE("Brightness: Incorrect input count! Expected 1 but got %d!", input_count); + return 0; + } + + // Make sure sizes match up + if (input_sizes[0] != output_size) { + LOGE("Brightness: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size); + return 0; + } + + // Get the input and output pointers + const int* input_ptr = (int*)inputs[0]; + int* output_ptr = (int*)output; + const int* end_ptr = input_ptr + (output_size / 4); + if (!input_ptr || !output_ptr) { + LOGE("Brightness: No input or output pointer found!"); + return 0; + } + + // Get the parameters + BrightnessParameters* params = (BrightnessParameters*)user_data; + const float brightness = params->brightness; + + // Run the brightness adjustment + const int factor = (int)(brightness * 255.0f); + Pixel pixel; + while (input_ptr < end_ptr) { + pixel.value = *(input_ptr++); + + const short r = (pixel.rgba[0] * factor) / 255; + const short g = (pixel.rgba[1] * factor) / 255; + const short b = (pixel.rgba[2] * factor) / 255; + + *(output_ptr++) = (r > 255 ? 255 : r) + | ((g > 255 ? 255 : g) << 8) + | ((b > 255 ? 255 : b) << 16) + | (pixel.rgba[3] << 24); + } + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/contrast.c b/media/mca/filterpacks/native/imageproc/contrast.c new file mode 100644 index 0000000..ea8c8d2 --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/contrast.c @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include <android/log.h> +#include <stdlib.h> + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "MCA", __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "MCA", __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "MCA", __VA_ARGS__) + +typedef struct { + float contrast; +} ContrastParameters; + +void contrast_init(void** user_data) { + (*user_data) = malloc(sizeof(ContrastParameters)); +} + +void contrast_teardown(void* user_data) { + free(user_data); +} + +void contrast_setvalue(const char* key, const char* value, void* user_data) { + if (strcmp(key, "contrast") == 0) + ((ContrastParameters*)user_data)->contrast = atof(value); + else + LOGE("Unknown parameter: %s!", key); +} + +int contrast_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) { + LOGE("Contrast: Incorrect input count! Expected 1 but got %d!", input_count); + return 0; + } + + // Make sure sizes match up + if (input_sizes[0] != output_size) { + LOGE("Contrast: Input-output sizes do not match up. %d vs. %d!", input_sizes[0], output_size); + return 0; + } + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) { + LOGE("Contrast: No input or output pointer found!"); + return 0; + } + + // Get the parameters + ContrastParameters* params = (ContrastParameters*)user_data; + const float contrast = params->contrast; + + // Run the contrast adjustment + int i; + for (i = 0; i < output_size; ++i) { + float px = *(input_ptr++) / 255.0; + px -= 0.5; + px *= contrast; + px += 0.5; + *(output_ptr++) = (char)(px > 1.0 ? 255.0 : (px < 0.0 ? 0.0 : px * 255.0)); + } + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/invert.c b/media/mca/filterpacks/native/imageproc/invert.c new file mode 100644 index 0000000..5938aac --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/invert.c @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#include <android/log.h> + +int invert_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the inversion + int i; + for (i = 0; i < output_size; ++i) + *(output_ptr++) = 255 - *(input_ptr++); + + return 1; +} + diff --git a/media/mca/filterpacks/native/imageproc/to_rgba.c b/media/mca/filterpacks/native/imageproc/to_rgba.c new file mode 100644 index 0000000..bf4db2a --- /dev/null +++ b/media/mca/filterpacks/native/imageproc/to_rgba.c @@ -0,0 +1,145 @@ +/* + * 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. + */ + +#include <stdlib.h> + +int gray_to_rgb_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size/3) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0]; ++i) { + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr++); + } + + return 1; +} + +int rgba_to_rgb_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0]/4 != output_size/3) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0] / 4; ++i) { + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + ++input_ptr; + } + + return 1; +} + +int gray_to_rgba_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0] != output_size/4) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < input_sizes[0]; ++i) { + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = 255; + } + + return 1; +} + +int rgb_to_rgba_process(const char** inputs, + const int* input_sizes, + int input_count, + char* output, + int output_size, + void* user_data) { + // Make sure we have exactly one input + if (input_count != 1) + return 0; + + // Make sure sizes match up + if (input_sizes[0]/3 != output_size/4) + return 0; + + // Get the input and output pointers + const char* input_ptr = inputs[0]; + char* output_ptr = output; + if (!input_ptr || !output_ptr) + return 0; + + // Run the conversion + int i; + for (i = 0; i < output_size / 4; ++i) { + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = *(input_ptr++); + *(output_ptr++) = 255; + } + + return 1; +} + diff --git a/media/mca/samples/Android.mk b/media/mca/samples/Android.mk new file mode 100644 index 0000000..b1ce91e --- /dev/null +++ b/media/mca/samples/Android.mk @@ -0,0 +1,21 @@ +# 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. +# + +# +# Build all native libraries +# +include $(call all-subdir-makefiles) + + diff --git a/media/mca/samples/CameraEffectsRecordingSample/Android.mk b/media/mca/samples/CameraEffectsRecordingSample/Android.mk new file mode 100644 index 0000000..d3c4336 --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/Android.mk @@ -0,0 +1,34 @@ +# 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. +# + +# Build activity + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := CameraEffectsRecordingSample + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + +# ============================================================ + +# Also build all of the sub-targets under this one: the shared library. +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml b/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml new file mode 100644 index 0000000..a65129d --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. + * + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:versionCode="1" + android:versionName="1.0" package="android.media.filterfw.samples"> + <uses-sdk android:minSdkVersion="3" /> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-permission android:name="android.permission.RECORD_AUDIO" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-feature android:name="android.hardware.camera" /> + <uses-feature android:name="android.hardware.camera.autofocus" /> + <application android:label="@string/app_name" + android:debuggable="true"> + <activity android:name=".CameraEffectsRecordingSample" + android:label="@string/app_name"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java b/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java new file mode 100644 index 0000000..c0c3034 --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java @@ -0,0 +1,101 @@ +/* + * 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 android.media.filterfw.samples; + +import android.app.Activity; +import android.os.Bundle; +import android.os.Environment; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.SurfaceView; +import android.view.KeyEvent; +import android.widget.Button; +import android.filterfw.GraphEnvironment; +import android.filterfw.core.GraphRunner; +import android.filterpacks.videosink.MediaEncoderFilter; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.content.Intent; + +public class CameraEffectsRecordingSample extends Activity { + + private Button mRunButton; + private SurfaceView mCameraView; + + private GraphRunner mRunner; + private int mCameraId = 0; + private String mOutFileName = Environment.getExternalStorageDirectory().toString() + + "/CameraEffectsRecordingSample.mp4"; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + mRunButton = (Button) findViewById(R.id.runbutton); + mCameraView = (SurfaceView) findViewById(R.id.cameraview); + mRunButton.setOnClickListener(mRunButtonClick); + + Intent intent = getIntent(); + if (intent.hasExtra("OUTPUT_FILENAME")) { + mOutFileName = intent.getStringExtra("OUTPUT_FILENAME"); + } + // Set up the references and load the filter graph + createGraph(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_CAMERA: + mRunButton.performClick(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + private void createGraph() { + Bitmap sourceBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.android); + GraphEnvironment graphEnvironment = new GraphEnvironment(); + graphEnvironment.createGLEnvironment(); + graphEnvironment.addReferences("cameraView", mCameraView); + graphEnvironment.addReferences("cameraId", mCameraId); + graphEnvironment.addReferences("outputFileName", mOutFileName); + int graphId = graphEnvironment.loadGraph(this, R.raw.cameraeffectsrecordingsample); + mRunner = graphEnvironment.getRunner(graphId, GraphEnvironment.MODE_ASYNCHRONOUS); + } + + protected void onPause() { + super.onPause(); + if (mRunner.isRunning()) { + mRunner.stop(); + mRunButton.setText("Record"); + } + } + + private OnClickListener mRunButtonClick = new OnClickListener() { + @Override + public void onClick(View v) { + if (mRunner.isRunning()) { + mRunner.stop(); + mRunButton.setText("Record"); + } else { + mRunner.run(); + mRunButton.setText("Stop"); + } + } + }; +} diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg b/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg Binary files differnew file mode 100644 index 0000000..7c26a8d --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml b/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml new file mode 100644 index 0000000..0cc07ce --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. + * + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <Button + android:id="@+id/runbutton" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Record" /> + <android.filterfw.core.FilterSurfaceView + android:id="@+id/cameraview" + android:layout_height="fill_parent" + android:layout_width="fill_parent" + android:layout_weight="1" /> +</LinearLayout> diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph b/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph new file mode 100644 index 0000000..f3ed667 --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph @@ -0,0 +1,57 @@ +// 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. + +// A simple graph that displays the raw camera stream. + +@setting autoBranch = "synced"; + +// Imports --------------------------------------------------- +@import android.filterpacks.videosrc; +@import android.filterpacks.ui; +@import android.filterpacks.videosink; +@import android.filterpacks.base; + +// Externals ------------------------------------------------- +@external cameraView; +@external outputFileName; +@external cameraId; + +@set widthLoc = 320; +@set heightLoc = 240; + +// Filters --------------------------------------------------- +@filter CameraSource camera { + id = $cameraId; + width = $widthLoc; + height = $heightLoc; + waitForNewFrame = false; +} + +@filter MediaEncoderFilter mediaEncoder { + width = $widthLoc; + height = $heightLoc; + outputFile = $outputFileName; +} + +@filter SurfaceRenderFilter renderer { + surfaceView = $cameraView; +} + + +// Connections ----------------------------------------------- +// Camera->Preview, Camera->Encoder +// Render camera output on to a surface +@connect camera[video] => renderer[frame]; +// Also pass it to an encoder +@connect camera[video] => mediaEncoder[videoframe]; diff --git a/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml b/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml new file mode 100644 index 0000000..6491043 --- /dev/null +++ b/media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. + * + --> +<resources> + <string name="app_name">CameraEffectsRecordingSample</string> +</resources> diff --git a/media/mca/structgen.py b/media/mca/structgen.py new file mode 100644 index 0000000..437326c --- /dev/null +++ b/media/mca/structgen.py @@ -0,0 +1,367 @@ +#!/usr/bin/env python + +# +# 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. +# + +import os +import sys + +hFileTemplate = """/** + * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify! + **/ + +#ifndef %s +#define %s + +%s + +#endif // %s +""" + +jniFileTemplate = """/** + * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify! + **/ + +#include <stdint.h> +#include "native/%s.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include "jni.h" + +// Helper functions //////////////////////////////////////////////////////////////////////////////// +%s* Get%sAtIndex(JNIEnv* env, jobject buffer, int index) { + jclass base_class = (*env)->FindClass(env, "android/filterfw/core/NativeBuffer"); + jfieldID ptr_field = (*env)->GetFieldID(env, base_class, "mDataPointer", "J"); + uintptr_t data_ptr = (*env)->GetLongField(env, buffer, ptr_field); + %s* array = (%s*)data_ptr; + (*env)->DeleteLocalRef(env, base_class); + return &array[index]; +} + +// Declarations //////////////////////////////////////////////////////////////////////////////////// +JNIEXPORT jint JNICALL +Java_%s_getElementSize(JNIEnv* env, jobject thiz); + +%s + +#ifdef __cplusplus +} +#endif + +// Implementation ////////////////////////////////////////////////////////////////////////////////// +jint Java_%s_getElementSize(JNIEnv* env, jobject thiz) { + return sizeof(%s); +} + +%s +""" + +javaFileTemplate = """/** + * This file is auto-generated by platform/system/media/mca/structgen.py! Do NOT modify! + **/ + +package %s; + +import android.filterfw.core.NativeBuffer; + +%s +""" + + +def ToJavaName(cname, start_upper_at = 1): + lower = cname.split("_") + upper = [c.title() for c in lower] + return "".join(lower[:start_upper_at] + upper[start_upper_at:]) + +def ToJNIPackage(package, jclassname): + return "%s_%s" % (package.replace(".", "_"), jclassname) + +def ToMacroDefName(cname, pname): + return "%s_%s" % (pname.replace(".", "_").upper(), cname.upper()) + +class ParseError: + def __init__(self, lineno, message): + self.lineno = lineno + self.message = message + + def __str__(self): + return "On line %d: %s" % (self.lineno, self.message) + +class FieldType_BasePOD: + def __init__(self, name, structname, jclassname, package, javatype, ctype, jtype, defval): + self.name = name + self.structname = structname + self.jclassname = jclassname + self.package = package + self.javatype = javatype + self.ctype = ctype + self.jtype = jtype + self.defval = defval + + def cString(self): + return " %s %s;" % (self.ctype, self.name) + + def javaGetter(self): + return " public %s get%s(int index) {\n"\ + " assertReadable();\n"\ + " return nativeGet%s(index);\n"\ + " }" % (self.javatype, ToJavaName(self.name, 0), ToJavaName(self.name, 0)) + + def javaSetter(self): + return " public void set%s(int index, %s value) {\n"\ + " assertWritable();\n"\ + " nativeSet%s(index, value);\n"\ + " }" % (ToJavaName(self.name, 0), self.javatype, ToJavaName(self.name, 0)) + + def javaNativeGetter(self): + return " private native %s nativeGet%s(int index);"\ + % (self.javatype, ToJavaName(self.name, 0)) + + def javaNativeSetter(self): + return " private native boolean nativeSet%s(int index, %s value);"\ + % (ToJavaName(self.name, 0), self.javatype) + + def jniGetterDefString(self): + return "JNIEXPORT %s JNICALL\n" \ + "Java_%s_nativeGet%s(JNIEnv* env, jobject thiz, jint index);" \ + % (self.jtype, ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0)) + + def jniGetterImplString(self): + return \ + "%s Java_%s_nativeGet%s(JNIEnv* env, jobject thiz, jint index) {\n"\ + " %s* instance = Get%sAtIndex(env, thiz, index);\n"\ + " return instance ? instance->%s : %s;\n"\ + "}\n" % (self.jtype, ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0),\ + self.structname, self.structname, self.name, self.defval) + + def jniSetterDefString(self): + return "JNIEXPORT jboolean JNICALL\n" \ + "Java_%s_nativeSet%s(JNIEnv* env, jobject thiz, jint index, %s value);" \ + % (ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0), self.jtype) + + def jniSetterImplString(self): + return \ + "jboolean Java_%s_nativeSet%s(JNIEnv* env, jobject thiz, jint index, %s value) {\n"\ + " %s* instance = Get%sAtIndex(env, thiz, index);\n"\ + " if (instance) {\n"\ + " instance->%s = value;\n"\ + " return JNI_TRUE;\n"\ + " }\n"\ + " return JNI_FALSE;\n"\ + "}\n" % (ToJNIPackage(self.package, self.jclassname), ToJavaName(self.name, 0),\ + self.jtype, self.structname, self.structname, self.name) + +class FieldType_Float(FieldType_BasePOD): + def __init__(self, name, structname, jclassname, package): + FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "float", "float", "jfloat", "0.0") + +class FieldType_Int(FieldType_BasePOD): + def __init__(self, name, structname, jclassname, package): + FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "int", "int", "jint", "0") + +class FieldType_Long(FieldType_BasePOD): + def __init__(self, name, structname, jclassname, package): + FieldType_BasePOD.__init__(self, name, structname, jclassname, package, "long", "long long", "jlong", "0") + +class StructSpec: + + def parseTextFile(self, filepath): + # Init + self.name = None + self.package = None + self.fields = [] + self.structname = None + self.jclassname = None + self.libname = None + + # Open the file + txtfile = open(filepath) + + # Parse it line by line + lineno = 0 + for line in txtfile: + # Split line into components + linecomps = line.split() + if len(linecomps) == 0: + continue + + # Execute command + cmd = linecomps[0] + if cmd == "@name": + self.commandArgAssert(linecomps, 1, lineno) + self.name = linecomps[1] + if not self.structname: + self.structname = self.name + if not self.jclassname: + self.jclassname = self.name + elif cmd == "@package": + self.commandArgAssert(linecomps, 1, lineno) + self.package = linecomps[1] + elif cmd == "@libname": + self.commandArgAssert(linecomps, 1, lineno) + self.libname = linecomps[1] + elif cmd == "@structname": + self.commandArgAssert(linecomps, 1, lineno) + self.structname = linecomps[1] + elif cmd == "@javaclassname": + self.commandArgAssert(linecomps, 1, lineno) + self.jclassname = linecomps[1] + elif cmd == "@field": + self.commandArgAssert(linecomps, 2, lineno) + typestr = linecomps[1] + if typestr == "int": + fieldtype = FieldType_Int(linecomps[2], self.structname, self.jclassname, self.package) + elif typestr == "long": + fieldtype = FieldType_Long(linecomps[2], self.structname, self.jclassname, self.package) + elif typestr == "float": + fieldtype = FieldType_Float(linecomps[2], self.structname, self.jclassname, self.package) + else: + raise ParseError(lineno, "Unknown field type '%s'!" % typestr) + self.fields.append(fieldtype) + else: + raise ParseError(lineno, "Unknown command: '%s'!" % cmd) + + lineno = lineno + 1 + + # Make sure we have all required info + if not self.name: + raise ParseError(lineno, "Required field '@name' missing!") + elif not self.package: + raise ParseError(lineno, "Required field '@package' missing!") + elif not self.libname: + raise ParseError(lineno, "Required field '@libname' missing!") + + # Normalize values + if self.libname[:3] == "lib": + self.libname = self.libname[3:] + + def commandArgAssert(self, linecomps, expectedcount, lineno): + foundcount = len(linecomps) - 1 + if foundcount < expectedcount: + raise ParseError(lineno, "Not enough arguments specifed for command '%s'! Expected %d, " \ + "but got only %d!" % (linecomps[0], expectedcount, foundcount)) + elif foundcount > expectedcount + 1: + raise ParseError(lineno, "Too many arguments specifed for command '%s'! Expected %d, " \ + "but got %d!" % (linecomps[0], expectedcount, foundcount)) + + + def cStructString(self): + cfields = [f.cString() for f in self.fields] + return "typedef struct Struct%s {\n%s\n} %s;\n" % (self.structname,\ + "\n".join(cfields),\ + self.structname) + + def javaClassString(self): + jgetters = [f.javaGetter() for f in self.fields] + jsetters = [f.javaSetter() for f in self.fields] + jnativesetters = [f.javaNativeSetter() for f in self.fields] + jnativegetters = [f.javaNativeGetter() for f in self.fields] + return "public class %s extends NativeBuffer {\n\n"\ + " public %s() {\n"\ + " super();\n"\ + " }\n"\ + "\n"\ + " public %s(int count) {\n"\ + " super(count);\n"\ + " }\n"\ + "\n"\ + " public native int getElementSize();\n"\ + "\n"\ + "%s\n\n"\ + "%s\n\n"\ + "%s\n\n"\ + "%s\n\n"\ + " static {\n"\ + " System.loadLibrary(\"%s\");\n"\ + " }\n"\ + "\n"\ + "};\n" % (self.jclassname,\ + self.jclassname,\ + self.jclassname,\ + "\n\n".join(jgetters),\ + "\n\n".join(jsetters),\ + "\n\n".join(jnativegetters),\ + "\n\n".join(jnativesetters),\ + self.libname) + + def jniDeclString(self): + jnigetters = [f.jniGetterDefString() for f in self.fields] + jnisetters = [f.jniSetterDefString() for f in self.fields] + return "\n\n".join(jnigetters + jnisetters) + + def jniImplString(self): + jnigetters = [f.jniGetterImplString() for f in self.fields] + jnisetters = [f.jniSetterImplString() for f in self.fields] + return "\n\n".join(jnigetters + jnisetters) + + def hFileString(self): + defname = ToMacroDefName(self.structname, self.package) + return hFileTemplate % (defname, defname, self.cStructString(), defname) + + def javaFileString(self): + return javaFileTemplate % (self.package, self.javaClassString()) + + def jniFileString(self): + return jniFileTemplate % (self.structname.lower(),\ + self.structname,\ + self.structname,\ + self.structname,\ + self.structname,\ + ToJNIPackage(self.package, self.jclassname),\ + self.jniDeclString(),\ + ToJNIPackage(self.package, self.jclassname),\ + self.structname, + self.jniImplString()) + +def main(argv): + if len(argv) != 2: + print("Usage: %s <file.struct>" % argv[0]) + return -1 + + filepath = argv[1] + + structspec = StructSpec() + structspec.parseTextFile(filepath) + + hfilename = "%s.h" % structspec.structname.lower() + javafilename = "%s.java" % structspec.jclassname + jnifilename = "jni_%s.c" % structspec.structname.lower() + + javapackagepath = structspec.package.replace('.','/') + + rootdir = os.path.dirname(filepath) + hfilepath = "%s/../native/%s" % (rootdir, hfilename) + javafilepath = "%s/../java/%s/%s" % (rootdir, javapackagepath, javafilename) + jnifilepath = "%s/../jni/%s" % (rootdir, jnifilename) + + hfile = open(hfilepath, 'w') + hfile.write(structspec.hFileString()) + hfile.close() + + javafile = open(javafilepath, 'w') + javafile.write(structspec.javaFileString()) + javafile.close() + + jnifile = open(jnifilepath, 'w') + jnifile.write(structspec.jniFileString()) + jnifile.close() + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/media/mca/tests/Android.mk b/media/mca/tests/Android.mk new file mode 100644 index 0000000..2abd7f6 --- /dev/null +++ b/media/mca/tests/Android.mk @@ -0,0 +1,18 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +LOCAL_JAVA_LIBRARIES := android.test.runner + +# Include all test java files. +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := CameraEffectsTests + +LOCAL_INSTRUMENTATION_FOR := CameraEffectsRecordingSample + +include $(BUILD_PACKAGE) + + diff --git a/media/mca/tests/AndroidManifest.xml b/media/mca/tests/AndroidManifest.xml new file mode 100644 index 0000000..5133640 --- /dev/null +++ b/media/mca/tests/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2008 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.camera.mediaeffects.tests"> + + <uses-permission android:name="android.permission.INJECT_EVENTS" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.test.InstrumentationTestRunner" + android:targetPackage="android.media.filterfw.samples" + android:label="Tests for Camera Effects Recording."/> +</manifest> diff --git a/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java new file mode 100644 index 0000000..474b00f --- /dev/null +++ b/media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java @@ -0,0 +1,90 @@ +/* + * 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 android.camera.mediaeffects.tests.functional; + +import android.media.filterfw.samples.CameraEffectsRecordingSample; +import android.app.Activity; +import android.app.Instrumentation; +import android.content.Intent; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.view.KeyEvent; +import android.util.Log; +import android.content.Intent; +import android.os.Environment; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import java.io.File; + +public class EffectsVideoCapture extends ActivityInstrumentationTestCase2 + <CameraEffectsRecordingSample> { + private static final String TAG = "EffectsVideoCaptureTest"; + private static final long WAIT_FOR_PREVIEW = 4 * 1000; // 4 seconds + + public EffectsVideoCapture() { + super(CameraEffectsRecordingSample.class); + } + + private void captureVideos(String reportTag, Instrumentation inst) throws Exception{ + int total_num_of_videos = 1; + int video_duration = 4 * 1000; // 4 seconds + + Log.v(TAG, reportTag); + for (int i = 0; i < total_num_of_videos; i++) { + Thread.sleep(WAIT_FOR_PREVIEW); + // record a video + inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + Thread.sleep(video_duration); + inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA); + } + } + + @LargeTest + public void testBackEffectsVideoCapture() throws Exception { + Instrumentation inst = getInstrumentation(); + + Intent intent = new Intent(); + intent.setClass(getInstrumentation().getTargetContext(), + CameraEffectsRecordingSample.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("OUTPUT_FILENAME", Environment.getExternalStorageDirectory().toString() + + "/CameraEffectsRecordingTest.mp4"); + Activity act = inst.startActivitySync(intent); + captureVideos("Back Camera Video Capture\n", inst); + act.finish(); + + // Verification + File file = new File(Environment.getExternalStorageDirectory(), + "CameraEffectsRecordingTest.mp4"); + Uri uri = Uri.fromFile(file); + verify(getActivity(), uri); + } + + // Verify result code, result data, and the duration. + private void verify(CameraEffectsRecordingSample activity, Uri uri) throws Exception { + assertNotNull(uri); + // Verify the video file + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(activity, uri); + String duration = retriever.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION); + assertNotNull(duration); + int durationValue = Integer.parseInt(duration); + Log.v(TAG, "Video duration is " + durationValue); + assertTrue(durationValue > 0); + } +} |