ImageReader 之前经常使用在camera应用里面,创建一个ImageReader,然后获取surface,配流和下发request时候将surface下发给framework,中间具体对ImageReader做了什么没有具体研究过,等到Hal层camera回帧的时候,ImageReader的onFrameAvailable就会回调上来,通过reader就可以获取图片了。
虽然使用上很简单,但是其实ImageReader里面要学习的东西还是很多的。
ImageReader本质上就是封装了BufferQueue和Surface的一些东西,init的时候创建了一个生产者消费者的队列,以下是init的主要代码
为啥用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 ------------------------------------------------------------------- start
LOGD("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 ------------------------------------------------------------------- start
GLint 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 返回 36054
glTexImage2D(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 triangle
1, 2, 3 // second triangle
};
// optimal
unsigned 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(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);
}