// SPDX-License-Identifier: MPL-2.0 // Bounded Android API 23 Camera1 diagnostic using an EGL/GLConsumer output path. #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef GL_TEXTURE_EXTERNAL_OES #define GL_TEXTURE_EXTERNAL_OES 0x8D65 #endif static GLuint compileShader(GLenum type, const char* source) { GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); GLint ok = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); if (!ok) { char log[1024]; glGetShaderInfoLog(shader, sizeof(log), nullptr, log); fprintf(stderr, "shader_error=%s\n", log); return 0; } return shader; } int main() { setvbuf(stderr, nullptr, _IONBF, 0); android::ProcessState::self()->startThreadPool(); const int cameraCount = android::Camera::getNumberOfCameras(); fprintf(stderr, "camera_count=%d uid=%d\n", cameraCount, getuid()); if (cameraCount < 1) return 2; android::sp camera = android::Camera::connect( 0, android::String16("org.vulpes.camera.glprobe"), android::Camera::USE_CALLING_UID); if (camera == nullptr) { fprintf(stderr, "camera_connect_failed\n"); return 3; } android::CameraParameters parameters(camera->getParameters()); int width = 0; int height = 0; parameters.getPreviewSize(&width, &height); fprintf(stderr, "stock_preview_size=%dx%d format=%s\n", width, height, parameters.getPreviewFormat()); if (width <= 0 || height <= 0 || width > 1920 || height > 1920) return 4; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); EGLint major = 0, minor = 0; if (display == EGL_NO_DISPLAY || !eglInitialize(display, &major, &minor)) { fprintf(stderr, "egl_initialize_error=%x\n", eglGetError()); return 5; } const EGLint configAttributes[] = { EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE}; EGLConfig config; EGLint configCount = 0; if (!eglChooseConfig(display, configAttributes, &config, 1, &configCount) || configCount < 1) return 6; const EGLint surfaceAttributes[] = { EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; EGLSurface surface = eglCreatePbufferSurface(display, config, surfaceAttributes); const EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes); if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT || !eglMakeCurrent(display, surface, surface, context)) { fprintf(stderr, "egl_context_error=%x\n", eglGetError()); return 7; } GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); android::sp producer; android::sp consumer; android::sp allocator = new android::GraphicBufferAlloc(); android::BufferQueue::createBufferQueue(&producer, &consumer, allocator); consumer->setDefaultBufferSize(width, height); android::sp glConsumer = new android::GLConsumer( consumer, texture, GL_TEXTURE_EXTERNAL_OES, true, false); const int targetStatus = camera->setPreviewTarget(producer); fprintf(stderr, "set_preview_target=%d\n", targetStatus); const int startStatus = camera->startPreview(); fprintf(stderr, "start_preview=%d\n", startStatus); if (targetStatus || startStatus) return 8; const char* vertexSource = "attribute vec4 a; attribute vec2 t; varying vec2 v;" "void main(){gl_Position=a;v=t;}"; const char* fragmentSource = "#extension GL_OES_EGL_image_external : require\n" "precision mediump float; uniform samplerExternalOES s; varying vec2 v;" "void main(){gl_FragColor=texture2D(s,v);}"; GLuint program = glCreateProgram(); GLuint vertex = compileShader(GL_VERTEX_SHADER, vertexSource); GLuint fragment = compileShader(GL_FRAGMENT_SHADER, fragmentSource); if (!vertex || !fragment) return 9; glAttachShader(program, vertex); glAttachShader(program, fragment); glBindAttribLocation(program, 0, "a"); glBindAttribLocation(program, 1, "t"); glLinkProgram(program); GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); if (!linked) { char log[1024]; glGetProgramInfoLog(program, sizeof(log), nullptr, log); fprintf(stderr, "program_link_error=%s\n", log); return 10; } const GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1}; const GLfloat texcoords[] = {0, 1, 1, 1, 0, 0, 1, 0}; glUseProgram(program); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, texcoords); glEnableVertexAttribArray(1); glUniform1i(glGetUniformLocation(program, "s"), 0); bool captured = false; for (int attempt = 0; attempt < 100 && !captured; ++attempt) { usleep(100000); const int updateStatus = glConsumer->updateTexImage(); if (updateStatus != 0) continue; glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glFinish(); unsigned char* pixels = new unsigned char[width * height * 3]; glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels); const GLenum glError = glGetError(); if (glError == GL_NO_ERROR) { FILE* output = fopen("/data/local/tmp/vulpes-camera-glconsumer.ppm", "wb"); if (output) { fprintf(output, "P6\n%d %d\n255\n", width, height); for (int y = height - 1; y >= 0; --y) fwrite(pixels + y * width * 3, 3, width, output); fclose(output); chmod("/data/local/tmp/vulpes-camera-glconsumer.ppm", 0644); captured = true; } } delete[] pixels; fprintf(stderr, "update=%d gl_error=%x captured=%d\n", updateStatus, glError, captured); } fprintf(stderr, "stopping_preview\n"); camera->stopPreview(); fprintf(stderr, "disconnecting_camera\n"); camera->disconnect(); fprintf(stderr, "captured=%d\n", captured); eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(display, context); eglDestroySurface(display, surface); eglTerminate(display); return captured ? 0 : 11; }