summaryrefslogtreecommitdiffstats
path: root/media/mca
diff options
context:
space:
mode:
authorMarius Renn <renn@google.com>2012-03-27 10:44:45 -0700
committerMarius Renn <renn@google.com>2012-03-27 10:44:45 -0700
commit65953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9 (patch)
treebfb7ea73b90f23cd135b68b090a72ef8b947ee90 /media/mca
parent80e4ee46008d2817dc0496e0cf8c9470c6851755 (diff)
downloadframeworks_base-65953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9.zip
frameworks_base-65953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9.tar.gz
frameworks_base-65953da4636fbf5f0a24b8f5f2b5fa7d76ff13d9.tar.bz2
Multi-Project Commit: Move of filterfw out of system/media (2 of 7)
This is part of the multi-project commit to move the filter-framework from system/media/mca to frameworks/base/media/mca. Note that the filter-framework will soon be replaced with a refactored version currently under API review (also to go under frameworks/base). This move is done now to unblock the PDK efforts. Change-Id: I9f42be5a12a9e8157512be11f04e38e4548970be
Diffstat (limited to 'media/mca')
-rw-r--r--media/mca/Android.mk21
-rw-r--r--media/mca/effect/java/android/media/effect/Effect.java111
-rw-r--r--media/mca/effect/java/android/media/effect/EffectContext.java131
-rw-r--r--media/mca/effect/java/android/media/effect/EffectFactory.java517
-rw-r--r--media/mca/effect/java/android/media/effect/EffectUpdateListener.java36
-rw-r--r--media/mca/effect/java/android/media/effect/FilterEffect.java101
-rw-r--r--media/mca/effect/java/android/media/effect/FilterGraphEffect.java120
-rw-r--r--media/mca/effect/java/android/media/effect/SingleFilterEffect.java98
-rw-r--r--media/mca/effect/java/android/media/effect/SizeChangeEffect.java65
-rw-r--r--media/mca/effect/java/android/media/effect/effects/AutoFixEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java102
-rw-r--r--media/mca/effect/java/android/media/effect/effects/BitmapOverlayEffect.java32
-rw-r--r--media/mca/effect/java/android/media/effect/effects/BlackWhiteEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/BrightnessEffect.java32
-rw-r--r--media/mca/effect/java/android/media/effect/effects/ColorTemperatureEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/ContrastEffect.java32
-rw-r--r--media/mca/effect/java/android/media/effect/effects/CropEffect.java33
-rw-r--r--media/mca/effect/java/android/media/effect/effects/CrossProcessEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/DocumentaryEffect.java30
-rw-r--r--media/mca/effect/java/android/media/effect/effects/DuotoneEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/FillLightEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/FisheyeEffect.java32
-rw-r--r--media/mca/effect/java/android/media/effect/effects/FlipEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/GrainEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/GrayscaleEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/IdentityEffect.java58
-rw-r--r--media/mca/effect/java/android/media/effect/effects/LomoishEffect.java30
-rw-r--r--media/mca/effect/java/android/media/effect/effects/NegativeEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/PosterizeEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/RedEyeEffect.java32
-rw-r--r--media/mca/effect/java/android/media/effect/effects/RotateEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/SaturateEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/SepiaEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/SharpenEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/StraightenEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/TintEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/effects/VignetteEffect.java31
-rw-r--r--media/mca/effect/java/android/media/effect/package-info.java28
-rw-r--r--media/mca/effect/java/android/media/effect/package.html54
-rw-r--r--media/mca/filterfw/Android.mk53
-rw-r--r--media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java99
-rw-r--r--media/mca/filterfw/java/android/filterfw/GraphEnvironment.java197
-rw-r--r--media/mca/filterfw/java/android/filterfw/MffEnvironment.java106
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java247
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java155
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FieldPort.java111
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Filter.java709
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterContext.java126
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterFactory.java145
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterFunction.java139
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterGraph.java363
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterPort.java134
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java157
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FinalPort.java48
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Frame.java236
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FrameFormat.java439
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/FrameManager.java67
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java180
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GLFrame.java417
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java30
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java30
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java32
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java29
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/GraphRunner.java100
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/InputPort.java85
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java82
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java96
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java28
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java129
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/NativeFrame.java265
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/NativeProgram.java176
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java78
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/OutputPort.java122
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Program.java41
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/ProgramPort.java62
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java57
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/ProtocolException.java33
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java54
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java73
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/Scheduler.java47
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java287
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java301
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java161
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java107
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java45
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java101
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/StreamPort.java100
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/SyncRunner.java227
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/VertexFrame.java143
-rw-r--r--media/mca/filterfw/java/android/filterfw/core/package-info.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/format/ImageFormat.java92
-rw-r--r--media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java105
-rw-r--r--media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java79
-rw-r--r--media/mca/filterfw/java/android/filterfw/format/package-info.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/Point.java113
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/Quad.java94
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java95
-rw-r--r--media/mca/filterfw/java/android/filterfw/geometry/package-info.java4
-rw-r--r--media/mca/filterfw/java/android/filterfw/io/GraphIOException.java33
-rw-r--r--media/mca/filterfw/java/android/filterfw/io/GraphReader.java69
-rw-r--r--media/mca/filterfw/java/android/filterfw/io/PatternScanner.java123
-rw-r--r--media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java489
-rw-r--r--media/mca/filterfw/java/android/filterfw/io/package-info.java4
-rw-r--r--media/mca/filterfw/jni/Android.mk51
-rw-r--r--media/mca/filterfw/jni/jni_gl_environment.cpp377
-rw-r--r--media/mca/filterfw/jni/jni_gl_environment.h97
-rw-r--r--media/mca/filterfw/jni/jni_gl_frame.cpp322
-rw-r--r--media/mca/filterfw/jni/jni_gl_frame.h137
-rw-r--r--media/mca/filterfw/jni/jni_init.cpp43
-rw-r--r--media/mca/filterfw/jni/jni_native_buffer.cpp84
-rw-r--r--media/mca/filterfw/jni/jni_native_buffer.h55
-rw-r--r--media/mca/filterfw/jni/jni_native_frame.cpp293
-rw-r--r--media/mca/filterfw/jni/jni_native_frame.h94
-rw-r--r--media/mca/filterfw/jni/jni_native_program.cpp187
-rw-r--r--media/mca/filterfw/jni/jni_native_program.h100
-rw-r--r--media/mca/filterfw/jni/jni_shader_program.cpp326
-rw-r--r--media/mca/filterfw/jni/jni_shader_program.h150
-rw-r--r--media/mca/filterfw/jni/jni_util.cpp188
-rw-r--r--media/mca/filterfw/jni/jni_util.h283
-rw-r--r--media/mca/filterfw/jni/jni_vertex_frame.cpp90
-rw-r--r--media/mca/filterfw/jni/jni_vertex_frame.h54
-rw-r--r--media/mca/filterfw/native/Android.mk44
-rw-r--r--media/mca/filterfw/native/base/logging.h27
-rw-r--r--media/mca/filterfw/native/base/utilities.h160
-rw-r--r--media/mca/filterfw/native/core/geometry.cpp137
-rw-r--r--media/mca/filterfw/native/core/geometry.h94
-rw-r--r--media/mca/filterfw/native/core/gl_buffer_interface.h71
-rw-r--r--media/mca/filterfw/native/core/gl_env.cpp408
-rw-r--r--media/mca/filterfw/native/core/gl_env.h261
-rw-r--r--media/mca/filterfw/native/core/gl_frame.cpp467
-rw-r--r--media/mca/filterfw/native/core/gl_frame.h217
-rw-r--r--media/mca/filterfw/native/core/native_frame.cpp61
-rw-r--r--media/mca/filterfw/native/core/native_frame.h85
-rw-r--r--media/mca/filterfw/native/core/native_program.cpp162
-rw-r--r--media/mca/filterfw/native/core/native_program.h84
-rw-r--r--media/mca/filterfw/native/core/shader_program.cpp1122
-rw-r--r--media/mca/filterfw/native/core/shader_program.h553
-rw-r--r--media/mca/filterfw/native/core/statistics.cpp57
-rw-r--r--media/mca/filterfw/native/core/statistics.h71
-rw-r--r--media/mca/filterfw/native/core/time_util.cpp92
-rw-r--r--media/mca/filterfw/native/core/time_util.h69
-rw-r--r--media/mca/filterfw/native/core/value.cpp236
-rw-r--r--media/mca/filterfw/native/core/value.h80
-rw-r--r--media/mca/filterfw/native/core/vertex_frame.cpp84
-rw-r--r--media/mca/filterfw/native/core/vertex_frame.h77
-rw-r--r--media/mca/filterfw/native/libfilterfw.mk33
-rw-r--r--media/mca/filterpacks/Android.mk55
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/CallbackFilter.java103
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameBranch.java62
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameFetch.java64
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameSource.java63
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/FrameStore.java51
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/GLTextureSource.java106
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/GLTextureTarget.java64
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/InputStreamSource.java96
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/NullFilter.java44
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/ObjectSource.java93
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/OutputStreamTarget.java66
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/base/RetargetFilter.java76
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/AlphaBlendFilter.java66
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/AutoFixFilter.java309
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapOverlayFilter.java153
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BitmapSource.java118
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BlackWhiteFilter.java176
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BlendFilter.java65
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/BrightnessFilter.java59
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ColorTemperatureFilter.java128
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ContrastFilter.java64
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CropFilter.java147
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CropRectFilter.java141
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/CrossProcessFilter.java129
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DocumentaryFilter.java175
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DrawOverlayFilter.java92
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DrawRectFilter.java132
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/DuotoneFilter.java125
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FillLightFilter.java140
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FisheyeFilter.java178
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FixedRotationFilter.java112
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/FlipFilter.java120
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/GrainFilter.java187
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageCombineFilter.java139
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageEncoder.java64
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageSlicer.java142
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ImageStitcher.java144
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/Invert.java60
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/LomoishFilter.java218
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/NegativeFilter.java103
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/PosterizeFilter.java106
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/RedEyeFilter.java211
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ResizeFilter.java124
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/RotateFilter.java156
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SaturateFilter.java170
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SepiaFilter.java111
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SharpenFilter.java153
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/SimpleImageFilter.java116
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/StraightenFilter.java154
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/TintFilter.java133
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToGrayFilter.java90
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToPackedGrayFilter.java143
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBAFilter.java103
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/ToRGBFilter.java103
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/VignetteFilter.java153
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/imageproc/package-info.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/numeric/SinWaveFilter.java66
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/numeric/package-info.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/performance/Throughput.java61
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/performance/ThroughputFilter.java103
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/performance/package-info.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/text/StringLogger.java49
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/text/StringSource.java59
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/text/ToUpperCase.java55
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/text/package-info.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/ui/SurfaceRenderFilter.java275
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/ui/SurfaceTargetFilter.java257
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/ui/package-info.java4
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videoproc/BackDropperFilter.java976
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosink/MediaEncoderFilter.java469
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosink/MediaRecorderStopException.java44
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/CameraSource.java358
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/MediaSource.java567
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureSource.java265
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/SurfaceTextureTarget.java282
-rw-r--r--media/mca/filterpacks/java/android/filterpacks/videosrc/package-info.java4
-rw-r--r--media/mca/filterpacks/native/base/geometry.cpp160
-rw-r--r--media/mca/filterpacks/native/base/geometry.h111
-rw-r--r--media/mca/filterpacks/native/base/time_util.cpp90
-rw-r--r--media/mca/filterpacks/native/base/time_util.h69
-rw-r--r--media/mca/filterpacks/native/base/utilities.h160
-rw-r--r--media/mca/filterpacks/native/base/vec_types.h177
-rw-r--r--media/mca/filterpacks/native/imageproc/brightness.c97
-rw-r--r--media/mca/filterpacks/native/imageproc/contrast.c85
-rw-r--r--media/mca/filterpacks/native/imageproc/invert.c46
-rw-r--r--media/mca/filterpacks/native/imageproc/to_rgba.c145
-rw-r--r--media/mca/samples/Android.mk21
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/Android.mk34
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/AndroidManifest.xml37
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/java/android/media/filterfw/samples/CameraEffectsRecordingSample.java101
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpgbin0 -> 3710 bytes
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/res/layout/main.xml32
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/res/raw/cameraeffectsrecordingsample.graph57
-rw-r--r--media/mca/samples/CameraEffectsRecordingSample/res/values/strings.xml20
-rw-r--r--media/mca/structgen.py367
-rw-r--r--media/mca/tests/Android.mk18
-rw-r--r--media/mca/tests/AndroidManifest.xml29
-rw-r--r--media/mca/tests/src/android/camera/mediaeffects/tests/functional/EffectsVideoCapture.java90
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
new file mode 100644
index 0000000..7c26a8d
--- /dev/null
+++ b/media/mca/samples/CameraEffectsRecordingSample/res/drawable/android.jpg
Binary files differ
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);
+ }
+}