To render camera captured images with GPU to the preview window we need to get ANativeWindow* pointer for the preview window. Camera HAL doesn't provide that pointer and in fact hides it. To get this pointer, a new API has to be added to frameworks/base/services/camera/libcameraservice/CameraHardwareInterface.h as follows:
class CameraHardwareInterface : public virtual RefBase {
public:
...
static ANativeWindow* getANativeWindow(struct preview_stream_ops* w)
{
return __to_anw(((struct camera_preview_window*)w)->user);
}
This pointer will be retrieved from hardware/imx/mx6/libcamera/CameraHal.cpp when we create EGL context. In addition, a following line has to be commented out in hardware/imx/mx6/libcamera/Android.mk to prevent warnings from including CameraHardwareInterface.h causing compilation errors:
#LOCAL_CPPFLAGS += -Werror
Preview window will be used for GPU rendering so it can't be used for allocating buffers for camera capture any more. To allocate capture buffers we need to create another native window from the camera HAL code. This window will never be used for displaying anything so buffers will be dequeued from it and never enqueued.
Note that for capture to work through EGL, all EGL initialization and OpenGL rendering has to be done from a single thread. One simple way to do this is to put EGL code into camera capture thread.
Overall steps are:
1. Create native window and allocate capture buffers. No buffers should be allocated from the preview window so that code in CameraHal.cpp should be skipped.
2. Preview window is pre-connected to NATIVE_WINDOW_API_CAMERA so it has to be disconnected before step 3.
3. Peform standard EGL initialization steps. Create EGL context from the ANativeWindow* pointer to the camera preview window
4. Map capture buffers to the GPU textures
5. Load shader program
6. On each captured buffer render in a standard way
7. Skip code in CameraHal.cpp that submits captured buffers to the preview window and only re-queue buffer to the camera device.
8. Upon camera termination, clean up allocated buffers and EGL context.
I'll follow up with some code fragments for above steps.
1. Create native window and allocate capture buffers
Replace original CameraHal::allocateBuffersFromNativeWindow() with the following code:
sp<SurfaceTexture> mST;
sp<SurfaceTextureClient> mSTC;
sp<ANativeWindow> mANW;
ANativeWindowBuffer* anb[6];
unsigned long imageWidth;
unsigned long imageHeight;
unsigned long imageSize;
imageWidth = 640;
imageHeight = 480;
imageSize = imageWidth * imageHeight;
mST = new SurfaceTexture(123);
mSTC = new SurfaceTextureClient(mST);
mANW = mSTC;
native_window_set_usage(mANW.get(), GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_SW_WRITE_OFTEN |
GRALLOC_USAGE_FORCE_CONTIGUOUS |
GRALLOC_USAGE_HW_TEXTURE);
// YV12 can be used when capturing from gray scale camera and U/V plane is a placeholder.
native_window_set_buffers_geometry(mANW.get(), imageWidth, imageHeight, HAL_PIXEL_FORMAT_YV12);
native_window_set_buffer_count(mANW.get(), captureBuffersNumber);
GraphicBufferMapper& mapper = GraphicBufferMapper::get();
Rect rect(imageWidth, imageHeight);
void* pVaddr;
for (int i = 0; i < 6; i++)
{
mANW->dequeueBuffer(mANW.get(), &anb[i]);
buffer_handle_t* buf_h = &anb[i]->handle;
private_handle_t* handle = (private_handle_t*)(*buf_h);
mapper.lock(handle, GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN, rect, &pVaddr);
mCaptureBuffers[i].virt_start = (unsigned char*)handle->base;
mCaptureBuffers[i].phy_offset = handle->phys;
mCaptureBuffers[i].length = handle->size;
mCaptureBuffers[i].native_buf = (void*)buf_h;
mCaptureBuffers[i].refCount = 0;
mCaptureBuffers[i].buf_state = WINDOW_BUFS_DEQUEUED;
// When a buffer is converted into GPU texture, YUV input is color space converted to RGBA8888 in GPU.
// Placeholder U/V values will interfere so they need to be initialized to 128 to cancel out.
void* uvPlane = mCaptureBuffers[i].virt_start + imageSize;
memset(uvPlane, 128, imageSize * 1/2);
}
To clean-up, replace original CameraHal::freeBuffersToNativeWindow() with the following code:
GraphicBufferMapper& mapper = GraphicBufferMapper::get();
for (int i = 0; i < 6; i++)
{
buffer_handle_t* buf_h = (buffer_handle_t*)mCaptureBuffers[i].native_buf;
mapper.unlock(*buf_h);
mANW->cancelBuffer(mANW.get(), anb[i]);
mCaptureBuffers[i].buf_state = WINDOW_BUFS_INVALID;
mCaptureBuffers[i].refCount = 0;
mCaptureBuffers[i].native_buf = NULL;
mCaptureBuffers[i].virt_start = NULL;
mCaptureBuffers[i].length = 0;
mCaptureBuffers[i].phy_offset = 0;
}
mANW.clear();
mSTC.clear();
mST.clear();
2. Preview window is pre-connected to NATIVE_WINDOW_API_CAMERA so it has to be disconnected before step 3:
native_window_api_disconnect(aNativeWindow, NATIVE_WINDOW_API_CAMERA);
Upon camera termination, make sure that you re-connect to camera API, otherwise there will be an error message:
native_window_api_connect(aNativeWindow, NATIVE_WINDOW_API_CAMERA);
3. Peform standard EGL initialization steps. Create EGL context from the ANativeWindow* pointer to the camera preview window:
EGLint majorVersion;
EGLint minorVersion;
EGLConfig eglConfig;
EGLContext eglContext;
EGLSurface eglSurface;
EGLDisplay eglDisplay
static const EGLint contextAttribs[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
static const EGLint configAttribs[] =
{
EGL_SAMPLES, 0,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLint w;
EGLint h;
eglBindAPI(EGL_OPENGL_ES_API);
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(eglDisplay, &majorVersion, &minorVersion);
eglGetConfigs(eglDisplay, NULL, 0, &numConfigs);
eglChooseConfig(eglDisplay, configAttribs, &eglConfig, 1, &numConfigs);
ANativeWindow* anw = CameraHardwareInterface::getANativeWindow(mNativeWindow);
eglCreateWindowSurface(eglDisplay, eglConfig, anw, NULL);
eglContext = eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT, contextAttribs);
eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
eglQuerySurface(eglDisplay, eglSurface, EGL_WIDTH, &w);
eglQuerySurface(eglDisplay, eglSurface, EGL_HEIGHT, &h);
4. Map capture buffers to the GPU textures:
#define GL_GLEXT_PROTOTYPES
#define EGL_EGLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
EGLint imageAttrs[] =
{
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE
};
EGLImageKHR eglImage[6];
GLuint texName[6];
glGenTextures(6, &texName[0]);
for (int i = 0; i < 6; i++)
{
eglImage[i] = eglCreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, (EGLClientBuffer)anb[i], imageAttrs);
glBindTexture(GL_TEXTURE_2D, texName[i]);
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);
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)eglImage[i]);
}
5. Load shader program:
This is done in the standard way. Threre are examples in android code.
6. On each captured buffer render in a standard way:
Standard OpenGL ES code except need to pay attention to the following:
- get the index of the currently captured buffer in the int CameraHal ::captureframeThread():
...
case CMESSAGE_TYPE_NORMAL:
ret = mCaptureDevice->DevDequeue(&bufIndex);
- start rendering code with the following:
glBindTexture(GL_TEXTURE_2D, texName[bufIndex]);
- setup geometry, uniforms, etc
- setup render target:
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, w, h);
- render
- swap buffers
eglSwapBuffers(eglDisplay, eglSurface);
7. Skip code in CameraHal.cpp that submits captured buffers to the preview window and only re-queue buffer to the camera device:
Upon completion of the step 6, in int CameraHal ::previewshowFrameThread(), make sure to execute only this code and skip the rest which
does mNativeWindow->enqueue_buffer() and mNativeWindow->dequeue_buffer():
case CMESSAGE_TYPE_NORMAL:
...
buf_index = display_index;
pInBuf->buf_state = WINDOW_BUFS_QUEUED;
mEnqueuedBufs ++;
mCaptureBuffers[buf_index].buf_state = WINDOW_BUFS_DEQUEUED;
ret = putBufferCount(&mCaptureBuffers[buf_index]);
break;
8. Upon camera termination, clean up allocated buffers and EGL context
This is standard code