一、ImageReader简介
ImageReader 之前经常使用在camera应用里面,创建一个ImageReader,然后获取surface,配流和下发request时候将surface下发给framework,中间具体对ImageReader做了什么没有具体研究过,等到Hal层camera回帧的时候,ImageReader的onFrameAvailable就会回调上来,通过reader就可以获取图片了。
虽然使用上很简单,但是其实ImageReader里面要学习的东西还是很多的。
ImageReader本质上就是封装了BufferQueue和Surface的一些东西,init的时候创建了一个生产者消费者的队列,以下是init的主要代码
二、使用ImageReader接收Opengl绘制结果
为啥用ImageReader接收绘制结果呢?之前写过使用glReadPixels将绘制结果读取到内存,但是glReadPixels在读取较大图片时候很耗时的,尤其是低端机上,因为glReadPixels是直接从显存拷贝到内存,所以并不使用频繁读取的情况。
通过分析ImageReader源码大概可以简单了解到ImageReader基于BufferQueue和Surface的,那我们可以猜到ImageReader相关的内存应该是GraphicBuffer,GraphicBuffer方便于GPU&CPU共享。
下面这段是创建EGL环境的代码,主要就是:
media_status_t status = AImageReader_new(4000, 3000, AIMAGE_FORMAT_RGBA_8888, 3, &imageReader); 创建ImageReader
status = AImageReader_setImageListener(imageReader, &listener); 设置帧回调
ANativeWindow *nwin; status = AImageReader_getWindow(imageReader, &nwin); 获取ANativeWindow, 作为
eglCreateWindowSurface的输入,也就是我们之后绘制的载体了。
JNIEXPORT void JNICALL Java_com_sprd_opengl_test_MyNdk_init2(JNIEnv *env, jobject obj, jobject surface) {// egl ------------------------------------------------------------------- startLOGD("init");AImageReader *imageReader;media_status_t status = AImageReader_new(4000, 3000, AIMAGE_FORMAT_RGBA_8888, 3, &imageReader);LOGD("AImageReader_new status: %d", status);listener.onImageAvailable = onFrameAvailable;status = AImageReader_setImageListener(imageReader, &listener);LOGD("AImageReader_setImageListener status: %d", status);ANativeWindow *nwin;status = AImageReader_getWindow(imageReader, &nwin);LOGD("AImageReader_getWindow status: %d", status);gl_cxt.nw = nwin;gl_cxt.reader = imageReader;EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (display == EGL_NO_DISPLAY) {LOGD("egl display failed");return;}if (EGL_TRUE != eglInitialize(display, 0, 0)) {LOGD("eglInitialize failed");return;}EGLConfig eglConfig;EGLint configNum;EGLint configSpec[] = {EGL_RED_SIZE, 8,EGL_GREEN_SIZE, 8,EGL_BLUE_SIZE, 8,EGL_ALPHA_SIZE, 8,EGL_SURFACE_TYPE, EGL_WINDOW_BIT,EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,EGL_RECORDABLE_ANDROID, EGL_TRUE,EGL_NONE};if (EGL_TRUE != eglChooseConfig(display, configSpec, &eglConfig, 1, &configNum)) {LOGD("eglChooseConfig failed");return;}EGLSurface winSurface = eglCreateWindowSurface(display, eglConfig, nwin, 0);if (winSurface == EGL_NO_SURFACE) {LOGD("eglCreateWindowSurface failed");return;}const EGLint ctxAttr[] = {EGL_CONTEXT_CLIENT_VERSION, 2,EGL_NONE};EGLContext context = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT, ctxAttr);if (context == EGL_NO_CONTEXT) {LOGD("eglCreateContext failed");return;}if (EGL_TRUE != eglMakeCurrent(display, winSurface, winSurface, context)) {LOGD("eglMakeCurrent failed");return;}gl_cxt.display = display;gl_cxt.winSurface = winSurface;gl_cxt.context = context;// egl ------------------------------------------------------------------- end// shader ------------------------------------------------------------------- startGLint vsh = initShader(vertexSimpleShape, GL_VERTEX_SHADER);GLint fsh = initShader(fragSimpleShape, GL_FRAGMENT_SHADER);GLint program = glCreateProgram();if (program == 0) {LOGD("glCreateProgram failed");return;}glAttachShader(program, vsh);glAttachShader(program, fsh);glLinkProgram(program);GLint status2 = 0;glGetProgramiv(program, GL_LINK_STATUS, &status2);if (status2 == 0) {LOGD("glLinkProgram failed");return;}gl_cxt.program = program;LOGD("glLinkProgram success");// shader ------------------------------------------------------------------- end
}
这段是绘制的代码,里面有不少无用的代码,主要是绘制完成之后
eglSwapBuffers一下就可以了,这样才会触发onFrameAvailable回调。
JNIEXPORT void JNICALL Java_com_sprd_opengl_test_MyNdk_process2(JNIEnv *env, jobject obj, jobject bitmap) {LOGD("process2 1");glUseProgram(gl_cxt.program);AndroidBitmapInfo bitmapInfo;if (AndroidBitmap_getInfo(env, bitmap, &bitmapInfo) < 0) {LOGE("AndroidBitmap_getInfo() failed ! ");return;}void *bmpPixels;LOGD("process2 2, format: %d, stride: %d", bitmapInfo.format, bitmapInfo.stride);AndroidBitmap_lockPixels(env, bitmap, &bmpPixels);LOGD("process2 3");unsigned int textureId;glGenTextures(1, &textureId);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, textureId);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);int width = bitmapInfo.width;int height = bitmapInfo.height;LOGD("process2 4");glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmpPixels);unsigned char *pOri = (unsigned char *)bmpPixels;LOGD("process2 5 %d, %d, %d, %d, %d, %d, %d, %d", *(pOri), *(pOri+1), *(pOri+2), *(pOri+3),*(pOri+114), *(pOri+115), *(pOri+116), *(pOri+117));glBindTexture(GL_TEXTURE_2D, 0);AndroidBitmap_unlockPixels(env, bitmap);unsigned int offTexture, fbo;glGenTextures(1, &offTexture);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, offTexture);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);// 没有这句调用, glCheckFramebufferStatus 返回 36054glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);glGenFramebuffers(1, &fbo);LOGD("fb status: %d, error: %d" , glCheckFramebufferStatus(GL_FRAMEBUFFER), glGetError());glBindFramebuffer(GL_FRAMEBUFFER, fbo);glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, offTexture, 0);LOGD("fb status: %d, error: %d" , glCheckFramebufferStatus(GL_FRAMEBUFFER), glGetError());glBindTexture(GL_TEXTURE_2D, 0);glBindFramebuffer(GL_FRAMEBUFFER, 0);/*顶点 纹理*/float vertices[] = {-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,1.0f, -1.0f, 0.0f, 1.0f, 0.0f,1.0f, 1.0f, 0.0f, 1.0f, 1.0f};unsigned int indices[] = {0, 1, 2, // first triangle1, 2, 3 // second triangle};// optimalunsigned int VBO, EBO, VAO;glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);glGenBuffers(1, &VBO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)((0 + 3)*sizeof(float)));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);glGenBuffers(1, &EBO);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);glBindVertexArray(0);glBindVertexArray(VAO);glClearColor(1.0f, 1.0f, 1.0f, 1.0f);glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);// draw to offline texture.glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, offTexture);glBindFramebuffer(GL_FRAMEBUFFER, fbo);LOGD("fb status: %d, error: %d" , glCheckFramebufferStatus(GL_FRAMEBUFFER), glGetError());glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);glBindFramebuffer(GL_FRAMEBUFFER, 0);glBindTexture(GL_TEXTURE_2D, 0);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D, textureId);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);glBindTexture(GL_TEXTURE_2D, 0);glFinish();glDisableVertexAttribArray(0);glDisableVertexAttribArray(1);glBindVertexArray(0);eglSwapBuffers(gl_cxt.display, gl_cxt.winSurface);LOGD("process2 X");
最后就是onFrameAvailable回调了,绘制完成之后onFrameAvailable就会触发,可以暂时不考虑我的memcpy那一段
AHardwareBuffer_lock耗时就十几毫秒(4000*3000*4的数据),所以很快就能从内存得到绘制结果了。
static void onFrameAvailable(void *context, AImageReader *reader) {LOGD("MyNdk onFrameAvailable");AImage *image = nullptr;media_status_t status = AImageReader_acquireNextImage(reader, &image);LOGD("AImageReader_acquireNextImage status: %d, image: %p", status, image);AHardwareBuffer *inBuffer = nullptr;status = AImage_getHardwareBuffer(image, &inBuffer);LOGD("AImage_getHardwareBuffer status: %d, inBuffer: %p", status, inBuffer);int width;int height;AImage_getWidth(image, &width);AImage_getHeight(image, &height);unsigned char *ptrReader = nullptr;unsigned char *dstBuffer = static_cast<unsigned char *>(malloc(width * height * 4));LOGD("AHardwareBuffer_lock E, w:%d, h:%d", width, height);AHardwareBuffer_lock(inBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN, -1, nullptr,(void **) &ptrReader);LOGD("AHardwareBuffer_lock X");memcpy(dstBuffer, ptrReader, width * height * 4);AHardwareBuffer_unlock(inBuffer, nullptr);LOGD("%d, %d, %d, %d", *ptrReader, *(ptrReader+1), *(ptrReader+2), *(ptrReader+3));AImage_delete(image);
}