diff options
19 files changed, 1137 insertions, 187 deletions
diff --git a/jni/Android.mk b/jni/Android.mk index d653573..0802754 100755 --- a/jni/Android.mk +++ b/jni/Android.mk @@ -22,6 +22,7 @@ LOCAL_SRC_FILES := \ feature_mos/src/mosaic/Mosaic.cpp \ feature_mos/src/mosaic/Pyramid.cpp \ feature_mos/src/mosaic_renderer/WarpRenderer.cpp \ + feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp \ feature_mos/src/mosaic_renderer/FrameBuffer.cpp \ feature_stab/db_vlvm/db_feature_detection.cpp \ feature_stab/db_vlvm/db_feature_matching.cpp \ diff --git a/jni/feature_mos/src/mosaic/ImageUtils.cpp b/jni/feature_mos/src/mosaic/ImageUtils.cpp index 792b90d..2671dd4 100644 --- a/jni/feature_mos/src/mosaic/ImageUtils.cpp +++ b/jni/feature_mos/src/mosaic/ImageUtils.cpp @@ -25,6 +25,51 @@ #include "ImageUtils.h" +void ImageUtils::rgba2yvu(ImageType out, ImageType in, int width, int height) +{ + int r,g,b, a; + ImageType yimg = out; + ImageType vimg = yimg + width*height; + ImageType uimg = vimg + width*height; + ImageType image = in; + + for (int ii = 0; ii < height; ii++) { + for (int ij = 0; ij < width; ij++) { + r = (*image++); + g = (*image++); + b = (*image++); + a = (*image++); + + if (r < 0) r = 0; + if (r > 255) r = 255; + if (g < 0) g = 0; + if (g > 255) g = 255; + if (b < 0) b = 0; + if (b > 255) b = 255; + + int val = (int) (REDY * r + GREENY * g + BLUEY * b) / 1000 + 16; + if (val < 0) val = 0; + if (val > 255) val = 255; + *(yimg) = val; + + val = (int) (REDV * r - GREENV * g - BLUEV * b) / 1000 + 128; + if (val < 0) val = 0; + if (val > 255) val = 255; + *(vimg) = val; + + val = (int) (-REDU * r - GREENU * g + BLUEU * b) / 1000 + 128; + if (val < 0) val = 0; + if (val > 255) val = 255; + *(uimg) = val; + + yimg++; + uimg++; + vimg++; + } + } +} + + void ImageUtils::rgb2yvu(ImageType out, ImageType in, int width, int height) { int r,g,b; diff --git a/jni/feature_mos/src/mosaic/ImageUtils.h b/jni/feature_mos/src/mosaic/ImageUtils.h index 9a47cc1..8778238 100644 --- a/jni/feature_mos/src/mosaic/ImageUtils.h +++ b/jni/feature_mos/src/mosaic/ImageUtils.h @@ -61,6 +61,8 @@ public: */ static void rgb2yvu(ImageType out, ImageType in, int width, int height); + static void rgba2yvu(ImageType out, ImageType in, int width, int height); + /** * Convert image from YVU (non-interlaced) to BGR (interlaced) * diff --git a/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp b/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp new file mode 100755 index 0000000..fb124aa --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp @@ -0,0 +1,382 @@ +#include "SurfaceTextureRenderer.h" + +#include <GLES2/gl2ext.h> + +#include <android/log.h> +#define LOG_TAG "SurfaceTextureRenderer" +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + +static const char gVertexShader[] = +"uniform mat4 uSTMatrix;\n" +"uniform mat4 u_scalingtrans; \n" +"attribute vec4 aPosition;\n" +"attribute vec4 aTextureCoord;\n" +"varying vec2 vTextureCoord;\n" +"varying vec2 vTextureNormCoord;\n" +"void main() {\n" +" gl_Position = u_scalingtrans * aPosition;\n" +" vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +" vTextureNormCoord = aTextureCoord.xy;\n" +"}\n"; + +static const char gFragmentShader[] = +"#extension GL_OES_EGL_image_external : require\n" +"precision mediump float;\n" +"varying vec2 vTextureCoord;\n" +"varying vec2 vTextureNormCoord;\n" +"uniform samplerExternalOES sTexture;\n" +"void main() {\n" +" gl_FragColor = texture2D(sTexture, vTextureNormCoord);\n" +"}\n"; + +const GLfloat g_vVertices[] = { + -1.f, -1.f, 0.0f, 1.0f, // Position 0 + 0.0f, 1.0f, // TexCoord 0 + 1.f, -1.f, 0.0f, 1.0f, // Position 1 + 1.0f, 1.0f, // TexCoord 1 + -1.f, 1.f, 0.0f, 1.0f, // Position 2 + 0.0f, 0.0f, // TexCoord 2 + 1.f, 1.f, 0.0f, 1.0f, // Position 3 + 1.0f, 0.0f // TexCoord 3 +}; +GLushort g_iIndices2[] = { 0, 1, 2, 3 }; + +const int GL_TEXTURE_EXTERNAL_OES_ENUM = 0x8D65; + +const int VERTEX_STRIDE = 6 * sizeof(GLfloat); + +SurfaceTextureRenderer::SurfaceTextureRenderer() + : mGlProgram(0), + mInputTextureName(-1), + mInputTextureWidth(0), + mInputTextureHeight(0), + mSurfaceWidth(0), + mSurfaceHeight(0) +{ + memset(mSTMatrix, 0.0, 16*sizeof(float)); + mSTMatrix[0] = 1.0f; + mSTMatrix[5] = 1.0f; + mSTMatrix[10] = 1.0f; + mSTMatrix[15] = 1.0f; + + InitializeGLContext(); +} + +SurfaceTextureRenderer::~SurfaceTextureRenderer() { +} + +void SurfaceTextureRenderer::SetSTMatrix(float *stmat) +{ + memcpy(mSTMatrix, stmat, 16*sizeof(float)); +} + +GLuint SurfaceTextureRenderer::loadShader(GLenum shaderType, const char* pSource) { + GLuint shader = glCreateShader(shaderType); + if (shader) { + glShaderSource(shader, 1, &pSource, NULL); + glCompileShader(shader); + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLint infoLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); + if (infoLen) { + char* buf = (char*) malloc(infoLen); + if (buf) { + glGetShaderInfoLog(shader, infoLen, NULL, buf); + LOGE("Could not compile shader %d:\n%s\n", + shaderType, buf); + free(buf); + } + glDeleteShader(shader); + shader = 0; + } + } + } + return shader; +} + +GLuint SurfaceTextureRenderer::createProgram(const char* pVertexSource, const char* pFragmentSource) +{ + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource); + if (!vertexShader) + { + return 0; + } + LOGI("VertexShader Loaded!"); + + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource); + if (!pixelShader) + { + return 0; + } + LOGI("FragmentShader Loaded!"); + + GLuint program = glCreateProgram(); + if (program) + { + glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + + LOGI("Shaders Attached!"); + + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + + LOGI("Program Linked!"); + + if (linkStatus != GL_TRUE) + { + GLint bufLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength); + if (bufLength) + { + char* buf = (char*) malloc(bufLength); + if (buf) + { + glGetProgramInfoLog(program, bufLength, NULL, buf); + LOGE("Could not link program:\n%s\n", buf); + free(buf); + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; +} + +bool SurfaceTextureRenderer::InitializeGLProgram() +{ + bool succeeded = false; + do { + GLuint glProgram; + glProgram = createProgram(VertexShaderSource(), + FragmentShaderSource()); + if (!glProgram) { + break; + } + + glUseProgram(glProgram); + if (!checkGlError("glUseProgram")) break; + + maPositionHandle = glGetAttribLocation(glProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + maTextureHandle = glGetAttribLocation(glProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + muSTMatrixHandle = glGetUniformLocation(glProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + mScalingtransLoc = glGetUniformLocation(glProgram, "u_scalingtrans"); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + mGlProgram = glProgram; + succeeded = true; + } while (false); + + if (!succeeded && (mGlProgram != 0)) + { + glDeleteProgram(mGlProgram); + checkGlError("glDeleteProgram"); + mGlProgram = 0; + } + return succeeded; +} + +void SurfaceTextureRenderer::SetViewportMatrix(int w, int h, int W, int H) +{ + for(int i=0; i<16; i++) + { + mViewportMatrix[i] = 0.0f; + } + + mViewportMatrix[0] = float(w)/float(W); + mViewportMatrix[5] = float(h)/float(H); + mViewportMatrix[10] = 1.0f; + mViewportMatrix[12] = -1.0f + float(w)/float(W); + mViewportMatrix[13] = -1.0f + float(h)/float(H); + mViewportMatrix[15] = 1.0f; +} + +void SurfaceTextureRenderer::SetScalingMatrix(float xscale, float yscale) +{ + for(int i=0; i<16; i++) + { + mScalingMatrix[i] = 0.0f; + } + + mScalingMatrix[0] = xscale; + mScalingMatrix[5] = yscale; + mScalingMatrix[10] = 1.0f; + mScalingMatrix[15] = 1.0f; +} + +// Set this renderer to use the default frame-buffer (screen) and +// set the viewport size to be the given width and height (pixels). +bool SurfaceTextureRenderer::SetupGraphics(int width, int height) +{ + bool succeeded = false; + do { + if (mGlProgram == 0) + { + if (!InitializeGLProgram()) + { + break; + } + } + glUseProgram(mGlProgram); + if (!checkGlError("glUseProgram")) break; + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + mFrameBuffer = NULL; + mSurfaceWidth = width; + mSurfaceHeight = height; + + glViewport(0, 0, mSurfaceWidth, mSurfaceHeight); + if (!checkGlError("glViewport")) break; + succeeded = true; + } while (false); + + return succeeded; +} + + +// Set this renderer to use the specified FBO and +// set the viewport size to be the width and height of this FBO. +bool SurfaceTextureRenderer::SetupGraphics(FrameBuffer* buffer) +{ + bool succeeded = false; + do { + if (mGlProgram == 0) + { + if (!InitializeGLProgram()) + { + break; + } + } + glUseProgram(mGlProgram); + if (!checkGlError("glUseProgram")) break; + + glBindFramebuffer(GL_FRAMEBUFFER, buffer->GetFrameBufferName()); + + mFrameBuffer = buffer; + mSurfaceWidth = mFrameBuffer->GetWidth(); + mSurfaceHeight = mFrameBuffer->GetHeight(); + + glViewport(0, 0, mSurfaceWidth, mSurfaceHeight); + if (!checkGlError("glViewport")) break; + succeeded = true; + } while (false); + + return succeeded; +} + +bool SurfaceTextureRenderer::Clear(float r, float g, float b, float a) +{ + bool succeeded = false; + do { + bool rt = (mFrameBuffer == NULL)? + SetupGraphics(mSurfaceWidth, mSurfaceHeight) : + SetupGraphics(mFrameBuffer); + + if(!rt) + break; + + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); + + succeeded = true; + } while (false); + return succeeded; + +} + +bool SurfaceTextureRenderer::DrawTexture(GLfloat *affine) +{ + bool succeeded = false; + do { + bool rt = (mFrameBuffer == NULL)? + SetupGraphics(mSurfaceWidth, mSurfaceHeight) : + SetupGraphics(mFrameBuffer); + + if(!rt) + break; + + glDisable(GL_BLEND); + + glActiveTexture(GL_TEXTURE0); + if (!checkGlError("glActiveTexture")) break; + + const GLenum texture_type = InputTextureType(); + glBindTexture(texture_type, mInputTextureName); + if (!checkGlError("glBindTexture")) break; + + glUniformMatrix4fv(mScalingtransLoc, 1, GL_FALSE, mScalingMatrix); + + // Load the vertex position + glVertexAttribPointer(maPositionHandle, 4, GL_FLOAT, + GL_FALSE, VERTEX_STRIDE, g_vVertices); + glEnableVertexAttribArray(maPositionHandle); + // Load the texture coordinate + glVertexAttribPointer(maTextureHandle, 2, GL_FLOAT, + GL_FALSE, VERTEX_STRIDE, &g_vVertices[4]); + glEnableVertexAttribArray(maTextureHandle); + + // And, finally, execute the GL draw command. + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, g_iIndices2); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glFinish(); + succeeded = true; + } while (false); + return succeeded; +} + +void SurfaceTextureRenderer::InitializeGLContext() +{ + if(mFrameBuffer != NULL) + { + delete mFrameBuffer; + mFrameBuffer = NULL; + } + + mInputTextureName = -1; + mInputTextureType = GL_TEXTURE_EXTERNAL_OES_ENUM; + mGlProgram = 0; +} + +int SurfaceTextureRenderer::GetTextureName() +{ + return mInputTextureName; +} + +void SurfaceTextureRenderer::SetInputTextureName(GLuint textureName) +{ + mInputTextureName = textureName; +} + +void SurfaceTextureRenderer::SetInputTextureType(GLenum textureType) +{ + mInputTextureType = textureType; +} + +void SurfaceTextureRenderer::SetInputTextureDimensions(int width, int height) +{ + mInputTextureWidth = width; + mInputTextureHeight = height; +} + + +const char* SurfaceTextureRenderer::VertexShaderSource() const +{ + return gVertexShader; +} + +const char* SurfaceTextureRenderer::FragmentShaderSource() const +{ + return gFragmentShader; +} diff --git a/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h b/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h new file mode 100755 index 0000000..e74bd64 --- /dev/null +++ b/jni/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h @@ -0,0 +1,77 @@ +#pragma once + +#include "FrameBuffer.h" + +#include <GLES2/gl2.h> + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +//TODO: Add a base class Renderer for WarpRenderer and SurfaceTextureRenderer. +class SurfaceTextureRenderer { + public: + SurfaceTextureRenderer(); + virtual ~SurfaceTextureRenderer(); + + // Initialize OpenGL resources + // @return true if successful + bool InitializeGLProgram(); + + bool SetupGraphics(FrameBuffer* buffer); + bool SetupGraphics(int width, int height); + + bool Clear(float r, float g, float b, float a); + + void SetViewportMatrix(int w, int h, int W, int H); + void SetScalingMatrix(float xscale, float yscale); + bool DrawTexture(GLfloat *affine); + + int GetTextureName(); + void SetInputTextureName(GLuint textureName); + void SetInputTextureDimensions(int width, int height); + void SetInputTextureType(GLenum textureType); + + void InitializeGLContext(); + + void SetSTMatrix(float *stmat); + + protected: + + GLuint loadShader(GLenum shaderType, const char* pSource); + GLuint createProgram(const char*, const char* ); + + int SurfaceWidth() const { return mSurfaceWidth; } + int SurfaceHeight() const { return mSurfaceHeight; } + + private: + // Source code for shaders. + virtual const char* VertexShaderSource() const; + virtual const char* FragmentShaderSource() const; + + // Redefine this to use special texture types such as + // GL_TEXTURE_EXTERNAL_OES. + virtual GLenum InputTextureType() const { return mInputTextureType; } + + GLuint mGlProgram; + GLuint mInputTextureName; + GLenum mInputTextureType; + int mInputTextureWidth; + int mInputTextureHeight; + + // Attribute locations + GLint mScalingtransLoc; + GLint muSTMatrixHandle; + GLint maPositionHandle; + GLint maTextureHandle; + + GLfloat mViewportMatrix[16]; + GLfloat mScalingMatrix[16]; + GLfloat mSTMatrix[16]; + + int mSurfaceWidth; // Width of target surface. + int mSurfaceHeight; // Height of target surface. + + FrameBuffer *mFrameBuffer; +}; + diff --git a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp index 9274721..b2ca8d4 100755 --- a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp +++ b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.cpp @@ -346,6 +346,7 @@ void WarpRenderer::InitializeGLContext() } mInputTextureName = -1; + mInputTextureType = GL_TEXTURE_2D; mGlProgram = 0; mTexHandle = 0; } @@ -360,6 +361,11 @@ void WarpRenderer::SetInputTextureName(GLuint textureName) mInputTextureName = textureName; } +void WarpRenderer::SetInputTextureType(GLenum textureType) +{ + mInputTextureType = textureType; +} + void WarpRenderer::SetInputTextureDimensions(int width, int height) { mInputTextureWidth = width; diff --git a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h index 7986215..774b33f 100755 --- a/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h +++ b/jni/feature_mos/src/mosaic_renderer/WarpRenderer.h @@ -29,6 +29,7 @@ class WarpRenderer { int GetTextureName(); void SetInputTextureName(GLuint textureName); void SetInputTextureDimensions(int width, int height); + void SetInputTextureType(GLenum textureType); void InitializeGLContext(); @@ -47,11 +48,12 @@ class WarpRenderer { // Redefine this to use special texture types such as // GL_TEXTURE_EXTERNAL_OES. - virtual GLenum InputTextureType() const { return GL_TEXTURE_2D; } + virtual GLenum InputTextureType() const { return mInputTextureType; } GLuint mGlProgram; GLuint mInputTextureName; + GLenum mInputTextureType; int mInputTextureWidth; int mInputTextureHeight; diff --git a/jni/feature_mos_jni.cpp b/jni/feature_mos_jni.cpp index c858728..b06c85f 100644 --- a/jni/feature_mos_jni.cpp +++ b/jni/feature_mos_jni.cpp @@ -59,7 +59,6 @@ const int MAX_FRAMES_LR = 200; static double mTx; -enum { LR=0, HR, NR }; int tWidth[NR]; int tHeight[NR]; int H2L_FACTOR = 4; // Can be 2 @@ -311,7 +310,7 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_allocateMosaicMem ImageUtils::IMAGE_TYPE_NUM_CHANNELS); } - AllocateTextureMemory(tWidth[LR], tHeight[LR]); + AllocateTextureMemory(tWidth[HR], tHeight[HR], tWidth[LR], tHeight[LR]); } JNIEXPORT void JNICALL Java_com_android_camera_panorama_Mosaic_freeMosaicMemory( @@ -366,6 +365,63 @@ void decodeYUV444SP(unsigned char* rgb, unsigned char* yuv420sp, int width, } } +static int count = 0; + +JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceImageFromGPU( + JNIEnv* env, jobject thiz) +{ + double t0, t1, time_c; + t0 = now_ms(); + + if(frame_number_HR<MAX_FRAMES_HR && frame_number_LR<MAX_FRAMES_LR) + { + double last_tx = mTx; + + t0 = now_ms(); + + sem_wait(&gPreviewImageRGB_semaphore); + ImageUtils::rgba2yvu(tImage[LR][frame_number_LR], gPreviewImageRGB[LR], + tWidth[LR], tHeight[LR]); + sem_post(&gPreviewImageRGB_semaphore); + + t1 = now_ms(); + time_c = t1 - t0; + LOGV("[%d] RGB => YVU [%d]: %g ms", frame_number_HR, frame_number_LR, + time_c); + + int ret_code = AddFrame(LR, frame_number_LR, gTRS); + + if(ret_code == Mosaic::MOSAIC_RET_OK) + { + // Copy into HR buffer only if this is a valid frame + sem_wait(&gPreviewImageRGB_semaphore); + ImageUtils::rgba2yvu(tImage[HR][frame_number_HR], gPreviewImageRGB[HR], + tWidth[HR], tHeight[HR]); + sem_post(&gPreviewImageRGB_semaphore); + + frame_number_LR++; + frame_number_HR++; + } + } + else + { + gTRS[1] = gTRS[2] = gTRS[3] = gTRS[5] = gTRS[6] = gTRS[7] = 0.0f; + gTRS[0] = gTRS[4] = gTRS[8] = 1.0f; + } + + UpdateWarpTransformation(gTRS); + + gTRS[9] = frame_number_HR; + + jfloatArray bytes = env->NewFloatArray(10); + if(bytes != 0) + { + env->SetFloatArrayRegion(bytes, 0, 10, (jfloat*) gTRS); + } + return bytes; +} + + JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceImage( JNIEnv* env, jobject thiz, jbyteArray photo_data) @@ -402,11 +458,10 @@ JNIEXPORT jfloatArray JNICALL Java_com_android_camera_panorama_Mosaic_setSourceI sem_wait(&gPreviewImageRGB_semaphore); - decodeYUV444SP(gPreviewImageRGB, tImage[LR][frame_number_LR], - gPreviewImageRGBWidth, gPreviewImageRGBHeight); + decodeYUV444SP(gPreviewImageRGB[LR], tImage[LR][frame_number_LR], + gPreviewImageRGBWidth[LR], gPreviewImageRGBHeight[LR]); sem_post(&gPreviewImageRGB_semaphore); - t1 = now_ms(); time_c = t1 - t0; LOGV("[%d] HR->LR [%d]: %g ms", frame_number_HR, frame_number_LR, diff --git a/jni/mosaic_renderer_jni.cpp b/jni/mosaic_renderer_jni.cpp index 21d3d39..c7e11b0 100644 --- a/jni/mosaic_renderer_jni.cpp +++ b/jni/mosaic_renderer_jni.cpp @@ -6,6 +6,7 @@ #include "mosaic/ImageUtils.h" #include "mosaic_renderer/FrameBuffer.h" #include "mosaic_renderer/WarpRenderer.h" +#include "mosaic_renderer/SurfaceTextureRenderer.h" #include <stdio.h> #include <stdlib.h> #include <math.h> @@ -14,22 +15,25 @@ #define LOG_TAG "MosaicRenderer" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) // Texture handle -GLuint textureId[1]; +GLuint gSurfaceTextureID[1]; -bool warp_image = true; +bool gWarpImage = true; -// Low-Res input image frame in RGB format for preview rendering -unsigned char* gPreviewImageRGB; -// Low-Res preview image width -int gPreviewImageRGBWidth; -// Low-Res preview image height -int gPreviewImageRGBHeight; +// Low-Res input image frame in RGB format for preview rendering and processing +// and high-res RGB input image for processing. +unsigned char* gPreviewImageRGB[NR]; +// Low-Res & high-res preview image width +int gPreviewImageRGBWidth[NR]; +// Low-Res & high-res preview image height +int gPreviewImageRGBHeight[NR]; // Semaphore to protect simultaneous read/writes from gPreviewImageRGB sem_t gPreviewImageRGB_semaphore; +sem_t gPreviewImageReady_semaphore; // Off-screen preview FBO width (large enough to store the entire // preview mosaic). @@ -38,19 +42,26 @@ int gPreviewFBOWidth; // preview mosaic). int gPreviewFBOHeight; +// Shader to copy input SurfaceTexture into and RGBA FBO. The two shaders +// render to the textures with dimensions corresponding to the low-res and +// high-res image frames. +SurfaceTextureRenderer gSurfTexRenderer[NR]; +// Off-screen FBOs to store the low-res and high-res RGBA copied out from +// the SurfaceTexture by the gSurfTexRenderers. +FrameBuffer gBufferInput[NR]; // Shader to add warped current frame to the preview FBO WarpRenderer gWarper; // Shader to warp and render the preview FBO to the screen WarpRenderer gPreview; // Off-screen FBO to store the result of gWarper -FrameBuffer *gBuffer; +FrameBuffer gBuffer; // Affine transformation in GL 4x4 format (column-major) to warp the -// current frame into the first frame coordinate system. +// current frame into the first frame coordinate system. GLfloat g_dAffinetransGL[16]; // Affine transformation in GL 4x4 format (column-major) to warp the -// preview FBO into the current frame coordinate system. +// preview FBO into the current frame coordinate system. GLfloat g_dAffinetransInvGL[16]; // GL 4x4 Identity transformation @@ -60,6 +71,7 @@ GLfloat g_dAffinetransIdent[] = { 0., 0., 1., 0., 0., 0., 0., 1.}; +const int GL_TEXTURE_EXTERNAL_OES_ENUM = 0x8D65; static void printGLString(const char *name, GLenum s) { const char *v = (const char *) glGetString(s); @@ -76,28 +88,35 @@ bool checkGlError(const char* op) { return true; } -void LoadTexture(unsigned char *buffer, int width, int height, GLuint texId) +void bindSurfaceTexture(GLuint texId) { - glBindTexture(GL_TEXTURE_2D, texId); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, - GL_UNSIGNED_BYTE, buffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glBindTexture(GL_TEXTURE_EXTERNAL_OES_ENUM, texId); + + // Can't do mipmapping with camera source + glTexParameterf(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_MIN_FILTER, + GL_LINEAR); + glTexParameterf(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_MAG_FILTER, + GL_LINEAR); + // Clamp to edge is the only option + glTexParameteri(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_WRAP_S, + GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_WRAP_T, + GL_CLAMP_TO_EDGE); } -void ReloadTexture(unsigned char *buffer, int width, int height, GLuint texId) +void ClearPreviewImageRGB(int mID) { - glBindTexture(GL_TEXTURE_2D, texId); - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, - GL_UNSIGNED_BYTE, buffer); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + unsigned char* ptr = gPreviewImageRGB[mID]; + for(int j = 0, i = 0; + j < gPreviewImageRGBWidth[mID] * gPreviewImageRGBHeight[mID] * 4; + j += 4) + { + ptr[i++] = 0; + ptr[i++] = 0; + ptr[i++] = 0; + ptr[i++] = 255; + } + } void ConvertAffine3x3toGL4x4(double *matGL44, double *mat33) @@ -139,8 +158,8 @@ void UpdateWarpTransformation(float *trs) double H[9], Hinv[9], Hp[9], Htemp[9]; double K[9], Kinv[9]; - int w = gPreviewImageRGBWidth; - int h = gPreviewImageRGBHeight; + int w = gPreviewImageRGBWidth[LR]; + int h = gPreviewImageRGBHeight[LR]; // K is the transformation to map the canonical [-1,1] vertex coordinate // system to the [0,w] image coordinate system before applying the given @@ -164,8 +183,8 @@ void UpdateWarpTransformation(float *trs) } // Move the origin such that the frame is centered in the previewFBO - H[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth / 2); - H[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight / 2); + H[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth[LR] / 2); + H[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight[LR] / 2); // Hp = inv(K) * H * K db_Identity3x3(Htemp); @@ -197,8 +216,8 @@ void UpdateWarpTransformation(float *trs) db_Identity3x3(Hinv); db_InvertAffineTransform(Hinv, H); - Hinv[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth / 2); - Hinv[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight / 2); + Hinv[2] += (gPreviewFBOWidth / 2 - gPreviewImageRGBWidth[LR] / 2); + Hinv[5] -= (gPreviewFBOHeight / 2 - gPreviewImageRGBHeight[LR] / 2); // Hp = inv(K) * Hinv * K db_Identity3x3(Htemp); @@ -208,22 +227,28 @@ void UpdateWarpTransformation(float *trs) ConvertAffine3x3toGL4x4(g_dAffinetransInv, Hp); } -void AllocateTextureMemory(int width, int height) +void AllocateTextureMemory(int widthHR, int heightHR, int widthLR, int heightLR) { - gPreviewImageRGBWidth = width; - gPreviewImageRGBHeight = height; + gPreviewImageRGBWidth[HR] = widthHR; + gPreviewImageRGBHeight[HR] = heightHR; + + gPreviewImageRGBWidth[LR] = widthLR; + gPreviewImageRGBHeight[LR] = heightLR; sem_init(&gPreviewImageRGB_semaphore, 0, 1); + sem_init(&gPreviewImageReady_semaphore, 0, 1); sem_wait(&gPreviewImageRGB_semaphore); - gPreviewImageRGB = ImageUtils::allocateImage(gPreviewImageRGBWidth, - gPreviewImageRGBHeight, ImageUtils::IMAGE_TYPE_NUM_CHANNELS); - memset(gPreviewImageRGB, 0, gPreviewImageRGBWidth * - gPreviewImageRGBHeight * 3 * sizeof(unsigned char)); + gPreviewImageRGB[LR] = ImageUtils::allocateImage(gPreviewImageRGBWidth[LR], + gPreviewImageRGBHeight[LR], 4); + ClearPreviewImageRGB(LR); + gPreviewImageRGB[HR] = ImageUtils::allocateImage(gPreviewImageRGBWidth[HR], + gPreviewImageRGBHeight[HR], 4); + ClearPreviewImageRGB(HR); sem_post(&gPreviewImageRGB_semaphore); - gPreviewFBOWidth = PREVIEW_FBO_WIDTH_SCALE * gPreviewImageRGBWidth; - gPreviewFBOHeight = PREVIEW_FBO_HEIGHT_SCALE * gPreviewImageRGBHeight; + gPreviewFBOWidth = PREVIEW_FBO_WIDTH_SCALE * gPreviewImageRGBWidth[LR]; + gPreviewFBOHeight = PREVIEW_FBO_HEIGHT_SCALE * gPreviewImageRGBHeight[LR]; UpdateWarpTransformation(g_dAffinetransIdent); } @@ -231,63 +256,146 @@ void AllocateTextureMemory(int width, int height) void FreeTextureMemory() { sem_wait(&gPreviewImageRGB_semaphore); - ImageUtils::freeImage(gPreviewImageRGB); + ImageUtils::freeImage(gPreviewImageRGB[LR]); + ImageUtils::freeImage(gPreviewImageRGB[HR]); sem_post(&gPreviewImageRGB_semaphore); sem_destroy(&gPreviewImageRGB_semaphore); + sem_destroy(&gPreviewImageReady_semaphore); } extern "C" { - JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( + JNIEXPORT jint JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( JNIEnv * env, jobject obj); JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_reset( JNIEnv * env, jobject obj, jint width, jint height); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_preprocess( + JNIEnv * env, jobject obj, jfloatArray stMatrix); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_transferGPUtoCPU( + JNIEnv * env, jobject obj); JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_step( JNIEnv * env, jobject obj); JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_ready( JNIEnv * env, jobject obj); - JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_togglewarping( - JNIEnv * env, jobject obj); + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_setWarping( + JNIEnv * env, jobject obj, jboolean flag); }; -JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( +JNIEXPORT jint JNICALL Java_com_android_camera_panorama_MosaicRenderer_init( JNIEnv * env, jobject obj) { + gSurfTexRenderer[LR].InitializeGLProgram(); + gSurfTexRenderer[HR].InitializeGLProgram(); gWarper.InitializeGLProgram(); gPreview.InitializeGLProgram(); + gBuffer.InitializeGLContext(); + gBufferInput[LR].InitializeGLContext(); + gBufferInput[HR].InitializeGLContext(); glBindFramebuffer(GL_FRAMEBUFFER, 0); - glGenTextures(1, &textureId[0]); + + glGenTextures(1, gSurfaceTextureID); + // bind the surface texture + bindSurfaceTexture(gSurfaceTextureID[0]); + + return (jint) gSurfaceTextureID[0]; } + JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_reset( JNIEnv * env, jobject obj, jint width, jint height) { - gBuffer = new FrameBuffer(); - gBuffer->Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA); + gBuffer.Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA); + + gBufferInput[LR].Init(gPreviewImageRGBWidth[LR], + gPreviewImageRGBHeight[LR], GL_RGBA); + + gBufferInput[HR].Init(gPreviewImageRGBWidth[HR], + gPreviewImageRGBHeight[HR], GL_RGBA); sem_wait(&gPreviewImageRGB_semaphore); - memset(gPreviewImageRGB, 0, gPreviewImageRGBWidth * - gPreviewImageRGBHeight * 3 * sizeof(unsigned char)); + ClearPreviewImageRGB(LR); + ClearPreviewImageRGB(HR); sem_post(&gPreviewImageRGB_semaphore); - // Load texture - LoadTexture(gPreviewImageRGB, gPreviewImageRGBWidth, - gPreviewImageRGBHeight, textureId[0]); + // bind the surface texture + bindSurfaceTexture(gSurfaceTextureID[0]); + + gSurfTexRenderer[LR].SetupGraphics(&gBufferInput[LR]); + gSurfTexRenderer[LR].Clear(0.0, 0.0, 0.0, 1.0); + gSurfTexRenderer[LR].SetViewportMatrix(1, 1, 1, 1); + gSurfTexRenderer[LR].SetScalingMatrix(1.0f, -1.0f); + gSurfTexRenderer[LR].SetInputTextureName(gSurfaceTextureID[0]); + gSurfTexRenderer[LR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM); - gWarper.SetupGraphics(gBuffer); + gSurfTexRenderer[HR].SetupGraphics(&gBufferInput[HR]); + gSurfTexRenderer[HR].Clear(0.0, 0.0, 0.0, 1.0); + gSurfTexRenderer[HR].SetViewportMatrix(1, 1, 1, 1); + gSurfTexRenderer[HR].SetScalingMatrix(1.0f, -1.0f); + gSurfTexRenderer[HR].SetInputTextureName(gSurfaceTextureID[0]); + gSurfTexRenderer[HR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM); + + gWarper.SetupGraphics(&gBuffer); gWarper.Clear(0.0, 0.0, 0.0, 1.0); - gWarper.SetViewportMatrix(gPreviewImageRGBWidth, - gPreviewImageRGBHeight, gBuffer->GetWidth(), gBuffer->GetHeight()); + gWarper.SetViewportMatrix(gPreviewImageRGBWidth[LR], + gPreviewImageRGBHeight[LR], gBuffer.GetWidth(), + gBuffer.GetHeight()); gWarper.SetScalingMatrix(1.0f, 1.0f); - gWarper.SetInputTextureName(textureId[0]); + gWarper.SetInputTextureName(gBufferInput[LR].GetTextureName()); + gWarper.SetInputTextureType(GL_TEXTURE_2D); gPreview.SetupGraphics(width, height); gPreview.Clear(0.0, 0.0, 0.0, 1.0); gPreview.SetViewportMatrix(1, 1, 1, 1); gPreview.SetScalingMatrix(1.0f, -1.0f); - gPreview.SetInputTextureName(gBuffer->GetTextureName()); + gPreview.SetInputTextureName(gBuffer.GetTextureName()); + gPreview.SetInputTextureType(GL_TEXTURE_2D); +} + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_preprocess( + JNIEnv * env, jobject obj, jfloatArray stMatrix) +{ + jfloat *stmat = env->GetFloatArrayElements(stMatrix, 0); + + gSurfTexRenderer[LR].SetSTMatrix((float*) stmat); + gSurfTexRenderer[HR].SetSTMatrix((float*) stmat); + + env->ReleaseFloatArrayElements(stMatrix, stmat, 0); + + gSurfTexRenderer[LR].DrawTexture(g_dAffinetransIdent); + gSurfTexRenderer[HR].DrawTexture(g_dAffinetransIdent); +} + +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_transferGPUtoCPU( + JNIEnv * env, jobject obj) +{ + sem_wait(&gPreviewImageRGB_semaphore); + // Bind to the input LR FBO and read the Low-Res data from there... + glBindFramebuffer(GL_FRAMEBUFFER, gBufferInput[LR].GetFrameBufferName()); + glReadPixels(0, + 0, + gBufferInput[LR].GetWidth(), + gBufferInput[LR].GetHeight(), + GL_RGBA, + GL_UNSIGNED_BYTE, + gPreviewImageRGB[LR]); + + checkGlError("glReadPixels LR"); + + // Bind to the input HR FBO and read the high-res data from there... + glBindFramebuffer(GL_FRAMEBUFFER, gBufferInput[HR].GetFrameBufferName()); + glReadPixels(0, + 0, + gBufferInput[HR].GetWidth(), + gBufferInput[HR].GetHeight(), + GL_RGBA, + GL_UNSIGNED_BYTE, + gPreviewImageRGB[HR]); + + checkGlError("glReadPixels HR"); + + sem_post(&gPreviewImageRGB_semaphore); } JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_step( @@ -297,37 +405,43 @@ JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_step( // current frame and then add it to the gBuffer FBO. gWarper.DrawTexture(g_dAffinetransGL); - // Clear the screen to black. - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - // Use the gPreview shader to apply the inverse of the current frame // transformation to the gBuffer FBO and render it to the screen. gPreview.DrawTexture(g_dAffinetransInvGL); } -JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_togglewarping( - JNIEnv * env, jobject obj) +JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_setWarping( + JNIEnv * env, jobject obj, jboolean flag) { - warp_image = !warp_image; + // TODO: Review this logic + if(gWarpImage != (bool) flag) //switching from viewfinder to capture or vice-versa + { + gWarper.Clear(0.0, 0.0, 0.0, 1.0); + gPreview.Clear(0.0, 0.0, 0.0, 1.0); + // Clear the screen to black. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + gWarpImage = (bool)flag; } JNIEXPORT void JNICALL Java_com_android_camera_panorama_MosaicRenderer_ready( JNIEnv * env, jobject obj) { - sem_wait(&gPreviewImageRGB_semaphore); - ReloadTexture(gPreviewImageRGB, gPreviewImageRGBWidth, - gPreviewImageRGBHeight, textureId[0]); - sem_post(&gPreviewImageRGB_semaphore); - - if(!warp_image) + if(!gWarpImage) { + // TODO: Review this logic... + UpdateWarpTransformation(g_dAffinetransIdent); + for(int i=0; i<16; i++) { g_dAffinetrans[i] = g_dAffinetransIdent[i]; + g_dAffinetransInv[i] = g_dAffinetransIdent[i]; } + g_dAffinetrans[12] = 1.0f; + g_dAffinetrans[13] = 1.0f; } for(int i=0; i<16; i++) diff --git a/jni/mosaic_renderer_jni.h b/jni/mosaic_renderer_jni.h index ed4180f..c4ba500 100644 --- a/jni/mosaic_renderer_jni.h +++ b/jni/mosaic_renderer_jni.h @@ -9,15 +9,21 @@ const int PREVIEW_FBO_WIDTH_SCALE = 4; const int PREVIEW_FBO_HEIGHT_SCALE = 2; -extern "C" void AllocateTextureMemory(int width, int height); +const int LR = 0; // Low-resolution mode +const int HR = 1; // High-resolution mode +const int NR = 2; // Number of resolution modes + +extern "C" void AllocateTextureMemory(int widthHR, int heightHR, + int widthLR, int heightLR); extern "C" void FreeTextureMemory(); extern "C" void UpdateWarpTransformation(float *trs); -extern unsigned char* gPreviewImageRGB; -extern int gPreviewImageRGBWidth; -extern int gPreviewImageRGBHeight; +extern unsigned char* gPreviewImageRGB[NR]; +extern int gPreviewImageRGBWidth[NR]; +extern int gPreviewImageRGBHeight[NR]; extern sem_t gPreviewImageRGB_semaphore; +extern sem_t gPreviewImageReady_semaphore; extern double g_dAffinetrans[16]; extern double g_dAffinetransInv[16]; diff --git a/res/layout-w1024dp/pano_capture.xml b/res/layout-w1024dp/pano_capture.xml new file mode 100644 index 0000000..0ce34b9 --- /dev/null +++ b/res/layout-w1024dp/pano_capture.xml @@ -0,0 +1,49 @@ +<?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. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/pano_capture_layout" android:visibility="invisible" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <FrameLayout android:layout_weight="1" android:gravity="center" + android:layout_width="match_parent" android:layout_height="match_parent"> + <com.android.camera.panorama.CaptureView + android:id="@+id/pano_capture_view" android:layout_width="match_parent" + android:layout_height="match_parent" /> + <com.android.camera.panorama.MosaicRendererSurfaceView + android:id="@+id/pano_renderer" android:layout_gravity="center" + android:layout_width="960dp" android:layout_height="360dp" /> + </FrameLayout> + + <ImageView style="@style/PanoViewHorizontalGrayBar" + android:layout_alignParentTop="true" android:layout_width="match_parent" /> + + <LinearLayout android:id="@+id/pano_capture_control" + style="@style/PanoViewHorizontalGrayBar" + android:gravity="right" + android:orientation="horizontal" + android:layout_alignParentBottom="true" + android:layout_width="match_parent"> + + <Button android:id="@+id/pano_capture_stop_button" + android:text="@string/pano_capture_stop" + android:onClick="onStopButtonClicked" + android:textSize="24dp" + android:layout_width="180dp" + android:layout_height="180dp" /> + </LinearLayout> +</RelativeLayout> diff --git a/res/layout/pano_capture.xml b/res/layout/pano_capture.xml index 0ce34b9..e95218a 100644 --- a/res/layout/pano_capture.xml +++ b/res/layout/pano_capture.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent" /> <com.android.camera.panorama.MosaicRendererSurfaceView android:id="@+id/pano_renderer" android:layout_gravity="center" - android:layout_width="960dp" android:layout_height="360dp" /> + android:layout_width="640dp" android:layout_height="240dp" /> </FrameLayout> <ImageView style="@style/PanoViewHorizontalGrayBar" diff --git a/res/layout/pano_preview.xml b/res/layout/pano_preview.xml index 96ab4ff..a2ac86b 100644 --- a/res/layout/pano_preview.xml +++ b/res/layout/pano_preview.xml @@ -29,8 +29,8 @@ <SurfaceView android:id="@+id/pano_preview_view" android:layout_gravity="center" - android:layout_width="240dp" - android:layout_height="180dp"/> + android:layout_width="960dp" + android:layout_height="360dp"/> </FrameLayout> <include layout="@layout/pano_control" android:id="@+id/pano_control_layout" /> diff --git a/src/com/android/camera/panorama/Mosaic.java b/src/com/android/camera/panorama/Mosaic.java index 5012ff4..80e8263 100644 --- a/src/com/android/camera/panorama/Mosaic.java +++ b/src/com/android/camera/panorama/Mosaic.java @@ -99,6 +99,20 @@ public class Mosaic { public native float[] setSourceImage(byte[] pixels); /** + * This is an alternative to the setSourceImage function above. This should + * be called when the image data is already on the native side in a fixed + * byte array. In implementation, this array is filled by the GL thread + * using glReadPixels directly from GPU memory (where it is accessed by + * an associated SurfaceTexture). + * + * @return Float array of length 10; first 9 entries correspond to the 3x3 + * transformation matrix between the first frame and the passed frame, + * and the last entry is the number of the passed frame, + * where the counting starts from 1. + */ + public native float[] setSourceImageFromGPU(); + + /** * Set the type of blending. * * @param type the blending type defined in the class. {BLENDTYPE_FULL, diff --git a/src/com/android/camera/panorama/MosaicFrameProcessor.java b/src/com/android/camera/panorama/MosaicFrameProcessor.java index 6496f11..502d41f 100644 --- a/src/com/android/camera/panorama/MosaicFrameProcessor.java +++ b/src/com/android/camera/panorama/MosaicFrameProcessor.java @@ -56,18 +56,20 @@ public class MosaicFrameProcessor { private int mPreviewWidth; private int mPreviewHeight; private int mPreviewBufferSize; + private boolean mUseSurfaceTexture; public interface ProgressListener { public void onProgress(boolean isFinished, float translationRate, int traversedAngleX, int traversedAngleY); } - public MosaicFrameProcessor(int sweepAngle, int previewWidth, int previewHeight, int bufSize) { + public MosaicFrameProcessor(int sweepAngle, int previewWidth, int previewHeight, int bufSize, boolean useSurfaceTexture) { mMosaicer = new Mosaic(); mCompassThreshold = sweepAngle; mPreviewWidth = previewWidth; mPreviewHeight = previewHeight; mPreviewBufferSize = bufSize; + mUseSurfaceTexture = useSurfaceTexture; } public void setProgressListener(ProgressListener listener) { @@ -75,7 +77,7 @@ public class MosaicFrameProcessor { } public void initialize() { - setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize); + setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize, mUseSurfaceTexture); reset(); } @@ -87,8 +89,7 @@ public class MosaicFrameProcessor { } } - - private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) { + private void setupMosaicer(int previewWidth, int previewHeight, int bufSize, boolean useSurfaceTexture) { Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize); mMosaicer.allocateMosaicMemory(previewWidth, previewHeight); @@ -133,11 +134,14 @@ public class MosaicFrameProcessor { } long t1 = System.currentTimeMillis(); mFrameTimestamp[mFillIn] = t1; - System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length); + + // TODO: Remove the case of byte data copy when SurfaceTexture is ready + if(!mUseSurfaceTexture) + System.arraycopy(data, 0, mFrames[mFillIn], 0, data.length); + mCurrProcessFrameIdx = mFillIn; mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER); - // Check that we are trying to process a frame different from the // last one processed (useful if this class was running asynchronously) if (mCurrProcessFrameIdx != mLastProcessFrameIdx) { @@ -184,11 +188,12 @@ public class MosaicFrameProcessor { float deltaTime = (now - mLastProcessedFrameTimestamp) / 1000.0f; mLastProcessedFrameTimestamp = now; - float[] frameData = mMosaicer.setSourceImage(data); + float[] frameData = mUseSurfaceTexture ? + mMosaicer.setSourceImageFromGPU() : + mMosaicer.setSourceImage(data); mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX]; float translationCurrX = frameData[X_COORD_INDEX]; float translationCurrY = frameData[Y_COORD_INDEX]; - mTranslationRate = Math.max(Math.abs(translationCurrX - mTranslationLastX), Math.abs(translationCurrY - mTranslationLastY)) / deltaTime; mTranslationLastX = translationCurrX; diff --git a/src/com/android/camera/panorama/MosaicRenderer.java b/src/com/android/camera/panorama/MosaicRenderer.java index 8db95c0..2bf471c 100644 --- a/src/com/android/camera/panorama/MosaicRenderer.java +++ b/src/com/android/camera/panorama/MosaicRenderer.java @@ -30,9 +30,12 @@ public class MosaicRenderer /** * Function to be called in onSurfaceCreated() to initialize * the GL context, load and link the shaders and create the - * program. + * program. Returns a texture ID to be used for SurfaceTexture. + * + * @return textureID the texture ID of the newly generated texture to + * be assigned to the SurfaceTexture object. */ - public static native void init(); + public static native int init(); /** * Pass the drawing surface's width and height to initialize the @@ -44,6 +47,23 @@ public class MosaicRenderer public static native void reset(int width, int height); /** + * Calling this function will render the SurfaceTexture to a new 2D texture + * using the provided STMatrix and then call glReadPixels to fill the data + * array that will be processed by the mosaicing thread. + * + * @param stMatrix texture coordinate transform matrix obtained from the + * Surface texture + */ + public static native void preprocess(float[] stMatrix); + + /** + * This function calls glReadPixels to transfer both the low-res and high-res + * data from the GPU memory to the CPU memory for further processing by the + * mosaicing library. + */ + public static native void transferGPUtoCPU(); + + /** * Function to be called in onDrawFrame() to update the screen with * the new frame data. */ @@ -59,9 +79,11 @@ public class MosaicRenderer /** * This function allows toggling between showing the input image data - * (without applying any warp) and the warped image data. This is more - * for debugging purposes to see if the image data is being updated - * correctly or not. + * (without applying any warp) and the warped image data. For running + * the renderer as a viewfinder, we set the flag to false. To see the + * preview mosaic, we set the flag to true. + * + * @param flag boolean flag to set the warping to true or false. */ - public static native void togglewarping(); + public static native void setWarping(boolean flag); } diff --git a/src/com/android/camera/panorama/MosaicRendererSurfaceView.java b/src/com/android/camera/panorama/MosaicRendererSurfaceView.java index 3220b22..6767c87 100644 --- a/src/com/android/camera/panorama/MosaicRendererSurfaceView.java +++ b/src/com/android/camera/panorama/MosaicRendererSurfaceView.java @@ -21,9 +21,12 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; +import android.app.Activity; import android.content.Context; import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; import android.opengl.GLSurfaceView; +import android.os.ConditionVariable; import android.util.AttributeSet; import android.util.Log; @@ -31,6 +34,7 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { private static String TAG = "MosaicRendererSurfaceView"; private static final boolean DEBUG = false; private MosaicRendererSurfaceViewRenderer mRenderer; + private ConditionVariable mPreviewFrameReadyForProcessing; public MosaicRendererSurfaceView(Context context) { super(context); @@ -50,7 +54,6 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { setZOrderMediaOverlay(true); } - private void init(boolean translucent, int depth, int stencil) { /* By default, GLSurfaceView() creates a RGB_565 opaque surface. @@ -80,6 +83,7 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { mRenderer = new MosaicRendererSurfaceViewRenderer(); setRenderer(mRenderer); setRenderMode(RENDERMODE_WHEN_DIRTY); + mPreviewFrameReadyForProcessing = new ConditionVariable(); } private static class ContextFactory implements GLSurfaceView.EGLContextFactory { @@ -298,6 +302,21 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { private int[] mValue = new int[1]; } + public void lockPreviewReadyFlag() + { + mPreviewFrameReadyForProcessing.close(); + } + + private void unlockPreviewReadyFlag() + { + mPreviewFrameReadyForProcessing.open(); + } + + public void waitUntilPreviewReady() + { + mPreviewFrameReadyForProcessing.block(); + } + public void setReady() { queueEvent(new Runnable() { @@ -309,14 +328,67 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { }); } - public void toggleWarping() + public void preprocess() + { + queueEvent(new Runnable() { + + @Override + public void run() { + mRenderer.preprocess(); + } + }); + } + + public void transferGPUtoCPU() + { + queueEvent(new Runnable() { + + @Override + public void run() { + mRenderer.transferGPUtoCPU(); + unlockPreviewReadyFlag(); + } + }); + } + + public void setUIObject(final Activity activity) { queueEvent(new Runnable() { @Override public void run() { - mRenderer.toggleWarping(); + mRenderer.setUIObject(activity); } }); } + + public void setWarping(final boolean flag) + { + queueEvent(new Runnable() { + + @Override + public void run() { + mRenderer.setWarping(flag); + } + }); + } + + public int getTextureID() { + return mRenderer.getTextureID(); + } + + public void setSurfaceTexture(SurfaceTexture surface) { + mRenderer.setSurfaceTexture(surface); + } + + public void updateSurfaceTexture() { + queueEvent(new Runnable() { + + @Override + public void run() { + mRenderer.updateSurfaceTexture(); + } + }); + } + } diff --git a/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java b/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java index a27d4be..d29765a 100644 --- a/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java +++ b/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java @@ -19,37 +19,78 @@ package com.android.camera.panorama; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import android.app.Activity; +import android.graphics.SurfaceTexture; import android.opengl.GLSurfaceView; - +import android.util.Log; public class MosaicRendererSurfaceViewRenderer implements GLSurfaceView.Renderer { @Override - public void onDrawFrame(GL10 gl) - { + public void onDrawFrame(GL10 gl) { MosaicRenderer.step(); } @Override - public void onSurfaceChanged(GL10 gl, int width, int height) - { + public void onSurfaceChanged(GL10 gl, int width, int height) { + Log.i(TAG, "Renderer: onSurfaceChanged"); MosaicRenderer.reset(width, height); + Log.i(TAG, "Renderer: onSurfaceChanged"); } @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) - { - MosaicRenderer.init(); + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + mTextureID = MosaicRenderer.init(); + + mActivity.runOnUiThread(new Runnable() { + + @Override + public void run() { + mActivity.createSurfaceTextureAndStartPreview(mTextureID); + setSurfaceTexture(mActivity.getSurfaceTexture()); + } + }); } - public void setReady() - { + public void setReady() { MosaicRenderer.ready(); } - public void toggleWarping() - { - MosaicRenderer.togglewarping(); + public void preprocess() { + MosaicRenderer.preprocess(mSTMatrix); + } + + public void transferGPUtoCPU() { + MosaicRenderer.transferGPUtoCPU(); + } + + public void setWarping(boolean flag) { + MosaicRenderer.setWarping(flag); + } + + public void updateSurfaceTexture() { + mSurface.updateTexImage(); + mSurface.getTransformMatrix(mSTMatrix); } + public void setUIObject(Activity activity) { + mActivity = (PanoramaActivity)activity; + } + + public int getTextureID() { + return mTextureID; + } + + public void setSurfaceTexture(SurfaceTexture surface) { + mSurface = surface; + } + + private float[] mSTMatrix = new float[16]; + private int mTextureID; + + private PanoramaActivity mActivity; + + private static String TAG = "MosaicRendererSurfaceViewRenderer"; + + private SurfaceTexture mSurface; } diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java index 0b1ec60..d9645ff 100644 --- a/src/com/android/camera/panorama/PanoramaActivity.java +++ b/src/com/android/camera/panorama/PanoramaActivity.java @@ -33,19 +33,19 @@ import android.graphics.BitmapFactory; import android.graphics.ImageFormat; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.SurfaceTexture; import android.graphics.YuvImage; import android.hardware.Camera; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; -import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.WindowManager; @@ -58,7 +58,8 @@ import java.util.List; * Activity to handle panorama capturing. */ public class PanoramaActivity extends Activity implements - ModePicker.OnModeChangeListener, SurfaceHolder.Callback { + ModePicker.OnModeChangeListener, + SurfaceTexture.OnFrameAvailableListener { public static final int DEFAULT_SWEEP_ANGLE = 60; public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; @@ -69,6 +70,9 @@ public class PanoramaActivity extends Activity implements private static final String TAG = "PanoramaActivity"; private static final int PREVIEW_STOPPED = 0; private static final int PREVIEW_ACTIVE = 1; + private static final int CAPTURE_VIEWFINDER = 0; + private static final int CAPTURE_MOSAIC = 1; + // Ratio of nanosecond to second private static final float NS2S = 1.0f / 1000000000.0f; @@ -89,6 +93,7 @@ public class PanoramaActivity extends Activity implements private int mPreviewHeight; private Camera mCameraDevice; private int mCameraState; + private int mCaptureState; private SensorManager mSensorManager; private Sensor mSensor; private ModePicker mModePicker; @@ -96,7 +101,8 @@ public class PanoramaActivity extends Activity implements private String mCurrentImagePath = null; private long mTimeTaken; private Handler mMainHandler; - private SurfaceHolder mSurfaceHolder; + private SurfaceTexture mSurface; + private boolean mUseSurfaceTexture = true; private boolean mThreadRunning; @@ -133,6 +139,22 @@ public class PanoramaActivity extends Activity implements }; } + public void createSurfaceTextureAndStartPreview(int textureID) + { + /* + * Create the SurfaceTexture that will feed this textureID, and pass it to the camera + */ + mSurface = new SurfaceTexture(textureID); + mSurface.setOnFrameAvailableListener(this); + startPreview(); + Log.i(TAG, "Created Surface Texture"); + } + + public SurfaceTexture getSurfaceTexture() + { + return mSurface; + } + private void setupCamera() { openCamera(); Parameters parameters = mCameraDevice.getParameters(); @@ -223,14 +245,16 @@ public class PanoramaActivity extends Activity implements CameraHolder.instance().getBackCameraId()); mCameraDevice.setDisplayOrientation(orientation); - int bufSize = getPreviewBufSize(); - Log.v(TAG, "BufSize = " + bufSize); - for (int i = 0; i < 10; i++) { - try { - mCameraDevice.addCallbackBuffer(new byte[bufSize]); - } catch (OutOfMemoryError e) { - Log.v(TAG, "Buffer allocation failed: buffer " + i); - break; + if(!mUseSurfaceTexture) { + int bufSize = getPreviewBufSize(); + Log.v(TAG, "BufSize = " + bufSize); + for (int i = 0; i < 10; i++) { + try { + mCameraDevice.addCallbackBuffer(new byte[bufSize]); + } catch (OutOfMemoryError e) { + Log.v(TAG, "Buffer allocation failed: buffer " + i); + break; + } } } } @@ -252,9 +276,51 @@ public class PanoramaActivity extends Activity implements } } + public void runViewFinder() { + mRealTimeMosaicView.setWarping(false); + + // First update the surface texture... + mRealTimeMosaicView.updateSurfaceTexture(); + // ...then call preprocess to render it to low-res and high-res RGB textures + mRealTimeMosaicView.preprocess(); + + mRealTimeMosaicView.setReady(); + mRealTimeMosaicView.requestRender(); + } + + public void runMosaicCapture() { + mRealTimeMosaicView.setWarping(true); + + // Lock the condition variable + mRealTimeMosaicView.lockPreviewReadyFlag(); + // First update the surface texture... + mRealTimeMosaicView.updateSurfaceTexture(); + // ...then call preprocess to render it to low-res and high-res RGB textures + mRealTimeMosaicView.preprocess(); + // Now, transfer the textures from GPU to CPU memory for processing + mRealTimeMosaicView.transferGPUtoCPU(); + // Wait on the condition variable (will be opened when GPU->CPU transfer is done). + mRealTimeMosaicView.waitUntilPreviewReady(); + + mMosaicFrameProcessor.processFrame(null); + } + + synchronized public void onFrameAvailable(SurfaceTexture surface) { + /* For simplicity, SurfaceTexture calls here when it has new + * data available. Call may come in from some random thread, + * so let's be safe and use synchronize. No OpenGL calls can be done here. + */ + if (mCaptureState == CAPTURE_VIEWFINDER) { + runViewFinder(); + } else { + runMosaicCapture(); + } + } + public void startCapture() { // Reset values so we can do this again. mTimeTaken = System.currentTimeMillis(); + mCaptureState = CAPTURE_MOSAIC; mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { @Override @@ -268,26 +334,38 @@ public class PanoramaActivity extends Activity implements } }); - // Preview callback used whenever new viewfinder frame is available - mCameraDevice.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() { - @Override - public void onPreviewFrame(final byte[] data, Camera camera) { - mMosaicFrameProcessor.processFrame(data); - // The returned buffer needs be added back to callback buffer - // again. - camera.addCallbackBuffer(data); - } - }); + if (!mUseSurfaceTexture) { + // Preview callback used whenever new viewfinder frame is available + mCameraDevice.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() { + @Override + public void onPreviewFrame(final byte[] data, Camera camera) { + mMosaicFrameProcessor.processFrame(data); + // The returned buffer needs be added back to callback buffer + // again. + camera.addCallbackBuffer(data); + } + }); + } mCaptureLayout.setVisibility(View.VISIBLE); mPreview.setVisibility(View.INVISIBLE); // will be re-used, invisible is better than gone. mRealTimeMosaicView.setVisibility(View.VISIBLE); mPanoControlLayout.setVisibility(View.GONE); + } private void stopCapture() { + mCaptureState = CAPTURE_VIEWFINDER; + mMosaicFrameProcessor.setProgressListener(null); stopPreview(); + + if (!mUseSurfaceTexture) { + mCameraDevice.setPreviewCallbackWithBuffer(null); + } + + mSurface.setOnFrameAvailableListener(null); + // TODO: show some dialog for long computation. if (!mThreadRunning) { mThreadRunning = true; @@ -322,17 +400,19 @@ public class PanoramaActivity extends Activity implements private void createContentView() { setContentView(R.layout.panorama); + mCaptureState = CAPTURE_VIEWFINDER; + mCaptureLayout = (View) findViewById(R.id.pano_capture_layout); mReviewLayout = (View) findViewById(R.id.pano_review_layout); mPreview = (SurfaceView) findViewById(R.id.pano_preview_view); - mPreview.getHolder().addCallback(this); + mCaptureView = (CaptureView) findViewById(R.id.pano_capture_view); mCaptureView.setStartAngle(-DEFAULT_SWEEP_ANGLE / 2); mReview = (ImageView) findViewById(R.id.pano_reviewarea); mRealTimeMosaicView = (MosaicRendererSurfaceView) findViewById(R.id.pano_renderer); - mRealTimeMosaicView.setVisibility(View.GONE); + mRealTimeMosaicView.setUIObject(this); mShutterButton = (ShutterButton) findViewById(R.id.pano_shutter_button); mShutterButton.setOnClickListener(new View.OnClickListener() { @@ -349,6 +429,8 @@ public class PanoramaActivity extends Activity implements mModePicker.setVisibility(View.VISIBLE); mModePicker.setOnModeChangeListener(this); mModePicker.setCurrentMode(ModePicker.MODE_PANORAMA); + + mRealTimeMosaicView.setVisibility(View.VISIBLE); } @OnClickAttr @@ -377,13 +459,19 @@ public class PanoramaActivity extends Activity implements } private void resetToPreview() { + mCaptureState = CAPTURE_VIEWFINDER; + + mReviewLayout.setVisibility(View.GONE); mPreview.setVisibility(View.VISIBLE); mPanoControlLayout.setVisibility(View.VISIBLE); - mRealTimeMosaicView.setVisibility(View.GONE); mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.GONE); mMosaicFrameProcessor.reset(); + + mSurface.setOnFrameAvailableListener(this); + if (!mPausing) startPreview(); + + mRealTimeMosaicView.setVisibility(View.VISIBLE); } private void showFinalMosaic(Bitmap bitmap) { @@ -394,6 +482,7 @@ public class PanoramaActivity extends Activity implements mReviewLayout.setVisibility(View.VISIBLE); mCaptureView.setStatusText(""); mCaptureView.setSweepAngle(0); + mCaptureView.invalidate(); } } @@ -416,7 +505,7 @@ public class PanoramaActivity extends Activity implements if (mMosaicFrameProcessor == null) { // Start the activity for the first time. mMosaicFrameProcessor = new MosaicFrameProcessor(DEFAULT_SWEEP_ANGLE - 5, - mPreviewWidth, mPreviewHeight, getPreviewBufSize()); + mPreviewWidth, mPreviewHeight, getPreviewBufSize(), mUseSurfaceTexture); } mMosaicFrameProcessor.initialize(); } @@ -424,10 +513,12 @@ public class PanoramaActivity extends Activity implements @Override protected void onPause() { super.onPause(); + mSurface.setOnFrameAvailableListener(null); releaseCamera(); mPausing = true; - mCaptureView.onPause(); + mRealTimeMosaicView.onPause(); + mCaptureView.onPause(); mSensorManager.unregisterListener(mListener); clearMosaicFrameProcessorIfNeeded(); System.gc(); @@ -446,12 +537,11 @@ public class PanoramaActivity extends Activity implements * resources. */ mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); - + mCaptureState = CAPTURE_VIEWFINDER; setupCamera(); // Camera must be initialized before MosaicFrameProcessor is initialized. The preview size // has to be decided by camera device. initMosaicFrameProcessorIfNeeded(); - startPreview(); mCaptureView.onResume(); mRealTimeMosaicView.onResume(); } @@ -526,45 +616,12 @@ public class PanoramaActivity extends Activity implements System.gc(); } - @Override - public void surfaceCreated(SurfaceHolder holder) { - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - mSurfaceHolder = holder; - - if (mCameraDevice == null) return; - - // Set preview display if the surface is being created. Preview was - // already started. Also restart the preview if display rotation has - // changed. Sometimes this happens when the device is held in portrait - // and camera app is opened. Rotation animation takes some time and - // display rotation in onCreate may not be what we want. - if (holder.isCreating()) { - // Set preview display if the surface is being created and preview - // was already started. That means preview display was set to null - // and we need to set it now. - setPreviewDisplay(holder); - } else { - // 1. Restart the preview if the size of surface was changed. The - // framework may not support changing preview display on the fly. - // 2. Start the preview now if surface was destroyed and preview - // stopped. - startPreview(); - } - } - - private void setPreviewDisplay(SurfaceHolder holder) { + private void setPreviewTexture(SurfaceTexture surface) { try { - mCameraDevice.setPreviewDisplay(holder); + mCameraDevice.setPreviewTexture(surface); } catch (Throwable ex) { releaseCamera(); - throw new RuntimeException("setPreviewDisplay failed", ex); + throw new RuntimeException("setPreviewTexture failed", ex); } } @@ -573,7 +630,7 @@ public class PanoramaActivity extends Activity implements // the screen). if (mCameraState != PREVIEW_STOPPED) stopPreview(); - setPreviewDisplay(mSurfaceHolder); + setPreviewTexture(mSurface); try { Log.v(TAG, "startPreview"); |