Release Vulpes OS 2.7-preview.24
This commit is contained in:
parent
49572b6b45
commit
97ef99f827
135 changed files with 7717 additions and 2524 deletions
54
tundra/probes/beatles-camera-broker.c
Normal file
54
tundra/probes/beatles-camera-broker.c
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Fixed-command local broker; camera access is confined to Android UID 1006.
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <grp.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SOCKET_DIR "/data/local/vulpes-beatles/camera-ipc"
|
||||
#define SOCKET_PATH SOCKET_DIR "/camera.sock"
|
||||
#define WORKER "/data/local/tmp/vulpes-beatles-camera-worker"
|
||||
|
||||
static volatile sig_atomic_t stopping;
|
||||
static void stop_now(int signal_number) { (void)signal_number; stopping = 1; }
|
||||
|
||||
int main(void) {
|
||||
if (setpgid(0,0) && errno != EACCES) return 1;
|
||||
struct sigaction action = {0}; action.sa_handler = stop_now;
|
||||
sigemptyset(&action.sa_mask); sigaction(SIGTERM,&action,NULL); sigaction(SIGINT,&action,NULL);
|
||||
umask(0);
|
||||
if (mkdir(SOCKET_DIR,0777) && errno != EEXIST) return 2;
|
||||
chmod(SOCKET_DIR,0777); unlink(SOCKET_PATH);
|
||||
int server=socket(AF_UNIX,SOCK_STREAM,0); if(server<0) return 3;
|
||||
struct sockaddr_un address={0}; address.sun_family=AF_UNIX;
|
||||
strncpy(address.sun_path,SOCKET_PATH,sizeof(address.sun_path)-1);
|
||||
if(bind(server,(struct sockaddr *)&address,sizeof(address)) || chmod(SOCKET_PATH,0666) || listen(server,2)) return 4;
|
||||
printf("camera broker ready uid=%d socket=%s\n",getuid(),SOCKET_PATH); fflush(stdout);
|
||||
while(!stopping) {
|
||||
int client=accept(server,NULL,NULL);
|
||||
if(client<0) { if(errno==EINTR) continue; break; }
|
||||
pid_t child=fork();
|
||||
if(child==0) {
|
||||
close(server);
|
||||
fprintf(stderr,"camera worker accepted pid=%d\n",getpid()); fflush(stderr);
|
||||
gid_t groups[]={1006};
|
||||
if(setgroups(1,groups) || setgid(1006) || setuid(1006)) _exit(120);
|
||||
if(dup2(client,STDIN_FILENO)<0 || dup2(client,STDOUT_FILENO)<0) _exit(121);
|
||||
if(client>STDOUT_FILENO) close(client);
|
||||
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
|
||||
execl(WORKER,"vulpes-beatles-camera-worker",(char *)NULL);
|
||||
_exit(127);
|
||||
}
|
||||
close(client);
|
||||
while(waitpid(-1,NULL,WNOHANG)>0) {}
|
||||
}
|
||||
close(server); unlink(SOCKET_PATH); return 0;
|
||||
}
|
||||
175
tundra/probes/beatles-camera-glconsumer-probe.cpp
Normal file
175
tundra/probes/beatles-camera-glconsumer-probe.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Bounded Android API 23 Camera1 diagnostic using an EGL/GLConsumer output path.
|
||||
#include <camera/Camera.h>
|
||||
#include <camera/CameraParameters.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <gui/BufferQueue.h>
|
||||
#include <gui/GLConsumer.h>
|
||||
#include <gui/GraphicBufferAlloc.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <GLES2/gl2ext.h>
|
||||
#include <utils/String16.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#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<android::Camera> 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<android::IGraphicBufferProducer> producer;
|
||||
android::sp<android::IGraphicBufferConsumer> consumer;
|
||||
android::sp<android::GraphicBufferAlloc> allocator =
|
||||
new android::GraphicBufferAlloc();
|
||||
android::BufferQueue::createBufferQueue(&producer, &consumer, allocator);
|
||||
consumer->setDefaultBufferSize(width, height);
|
||||
android::sp<android::GLConsumer> 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;
|
||||
}
|
||||
213
tundra/probes/beatles-camera-gonkqueue-probe.cpp
Normal file
213
tundra/probes/beatles-camera-gonkqueue-probe.cpp
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// RAM-only diagnostic reproducing Gecko's Android 6 GonkBufferQueue path.
|
||||
#include <camera/Camera.h>
|
||||
#include <camera/CameraParameters.h>
|
||||
#include <system/camera.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <gui/GraphicBufferAlloc.h>
|
||||
#include <gui/IConsumerListener.h>
|
||||
#include <ui/Fence.h>
|
||||
#include <ui/GraphicBuffer.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define ANDROID_VERSION 23
|
||||
#include "GonkBufferQueueLL.h"
|
||||
#include "GonkBufferQueueProducer.h"
|
||||
|
||||
class CameraEvents : public android::CameraListener {
|
||||
public:
|
||||
void notify(int32_t type, int32_t ext1, int32_t ext2) override {
|
||||
fprintf(stderr, "camera_notify=%d,%d,%d\n", type, ext1, ext2);
|
||||
}
|
||||
void postData(int32_t type, const android::sp<android::IMemory>& memory,
|
||||
camera_frame_metadata_t*) override {
|
||||
if (memory == nullptr) return;
|
||||
const size_t size = memory->size();
|
||||
const unsigned char* bytes = static_cast<const unsigned char*>(memory->pointer());
|
||||
unsigned int low = 255, high = 0;
|
||||
uint64_t sum = 0;
|
||||
const size_t ySize = size < 640u * 480u ? size : 640u * 480u;
|
||||
for (size_t i = 0; i < ySize; ++i) {
|
||||
if (bytes[i] < low) low = bytes[i];
|
||||
if (bytes[i] > high) high = bytes[i];
|
||||
sum += bytes[i];
|
||||
}
|
||||
fprintf(stderr, "camera_data=%d size=%zu y=%u..%u mean=%.2f\n",
|
||||
type, size, low, high, ySize ? (double)sum / ySize : 0);
|
||||
static bool saved = false;
|
||||
if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
|
||||
FILE* jpeg = fopen("/data/local/tmp/vulpes-camera-callback.jpg", "wb");
|
||||
if (jpeg) { fwrite(bytes, 1, size, jpeg); fclose(jpeg); chmod("/data/local/tmp/vulpes-camera-callback.jpg", 0644); }
|
||||
fprintf(stderr, "captured_jpeg=%zu\n", size);
|
||||
} else if (!saved) {
|
||||
FILE* output = fopen("/data/local/tmp/vulpes-camera-callback.yuv", "wb");
|
||||
if (output) { fwrite(bytes, 1, size, output); fclose(output); chmod("/data/local/tmp/vulpes-camera-callback.yuv", 0644); saved = true; }
|
||||
}
|
||||
}
|
||||
void postDataTimestamp(nsecs_t, int32_t type,
|
||||
const android::sp<android::IMemory>&) override {
|
||||
fprintf(stderr, "camera_timestamp_data=%d\n", type);
|
||||
}
|
||||
};
|
||||
|
||||
class QueueListener : public android::ConsumerListener {
|
||||
public:
|
||||
void onFrameAvailable(const android::BufferItem&) override {}
|
||||
void onFrameReplaced(const android::BufferItem&) override {}
|
||||
void onBuffersReleased() override {}
|
||||
void onSidebandStreamChanged() override {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
android::ProcessState::self()->startThreadPool();
|
||||
const int count = android::Camera::getNumberOfCameras();
|
||||
fprintf(stderr, "camera_count=%d uid=%d\n", count, getuid());
|
||||
if (count < 1) return 2;
|
||||
|
||||
android::sp<android::Camera> camera = android::Camera::connect(
|
||||
// Match GonkCameraHwMgr.cpp exactly. CameraService/AppOps on this
|
||||
// firmware treats the client package as part of camera authorization.
|
||||
0, android::String16("gonk.camera"),
|
||||
android::Camera::USE_CALLING_UID);
|
||||
if (camera == nullptr) return 3;
|
||||
android::CameraParameters params(camera->getParameters());
|
||||
android::Vector<android::Size> previews, pictures;
|
||||
params.getSupportedPreviewSizes(previews);
|
||||
params.getSupportedPictureSizes(pictures);
|
||||
android::Size picture = {0, 0};
|
||||
for (size_t i = 0; i < pictures.size(); ++i)
|
||||
if (pictures[i].width * pictures[i].height > picture.width * picture.height)
|
||||
picture = pictures[i];
|
||||
android::Size preview = {0, 0};
|
||||
uint64_t bestDelta = UINT64_MAX;
|
||||
for (size_t i = 0; i < previews.size(); ++i) {
|
||||
const android::Size candidate = previews[i];
|
||||
if (picture.width && candidate.width * picture.height != picture.width * candidate.height)
|
||||
continue;
|
||||
const uint64_t area = (uint64_t)candidate.width * candidate.height;
|
||||
const uint64_t delta = area > 640u * 480u ? area - 640u * 480u : 640u * 480u - area;
|
||||
if (delta < bestDelta) { preview = candidate; bestDelta = delta; }
|
||||
}
|
||||
if (!preview.width && previews.size()) preview = previews[0];
|
||||
if (!preview.width || !picture.width) {
|
||||
fprintf(stderr, "no supported preview/picture size (preview=%zu picture=%zu)\n",
|
||||
previews.size(), pictures.size());
|
||||
camera->disconnect();
|
||||
return 4;
|
||||
}
|
||||
params.setPreviewSize(preview.width, preview.height);
|
||||
params.setPictureSize(picture.width, picture.height);
|
||||
// Gecko applies its recording hint after selecting still/preview sizes and
|
||||
// before starting the preview. Keep the probe's Camera1 state in step.
|
||||
params.set("recording-hint", "false");
|
||||
android::String8 configured = params.flatten();
|
||||
android::status_t status = camera->setParameters(configured);
|
||||
fprintf(stderr, "set_parameters=%d requested_preview=%dx%d picture=%dx%d\n",
|
||||
status, preview.width, preview.height, picture.width, picture.height);
|
||||
if (status != android::NO_ERROR) { camera->disconnect(); return 4; }
|
||||
params.unflatten(camera->getParameters());
|
||||
int width = 0, height = 0;
|
||||
params.getPreviewSize(&width, &height);
|
||||
int pictureWidth = 0, pictureHeight = 0;
|
||||
params.getPictureSize(&pictureWidth, &pictureHeight);
|
||||
fprintf(stderr, "preview=%dx%d picture=%dx%d format=%s\n", width, height,
|
||||
pictureWidth, pictureHeight, params.getPreviewFormat());
|
||||
|
||||
android::sp<android::IGraphicBufferProducer> producer;
|
||||
android::sp<android::IGonkGraphicBufferConsumer> consumer;
|
||||
android::sp<android::GraphicBufferAlloc> allocator =
|
||||
new android::GraphicBufferAlloc();
|
||||
android::GonkBufferQueue::createBufferQueue(&producer, &consumer, allocator);
|
||||
status = static_cast<android::GonkBufferQueueProducer*>(producer.get())
|
||||
->setSynchronousMode(false);
|
||||
fprintf(stderr, "async_producer=%d\n", status);
|
||||
status = consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN);
|
||||
fprintf(stderr, "cpu_read_usage=%d\n", status);
|
||||
status = consumer->setMaxAcquiredBufferCount(4);
|
||||
fprintf(stderr, "max_acquired=%d\n", status);
|
||||
android::sp<QueueListener> listener = new QueueListener();
|
||||
android::sp<android::IConsumerListener> proxy =
|
||||
new android::GonkBufferQueue::ProxyConsumerListener(listener);
|
||||
status = consumer->consumerConnect(proxy, false);
|
||||
fprintf(stderr, "consumer_connect=%d\n", status);
|
||||
android::sp<CameraEvents> cameraEvents = new CameraEvents();
|
||||
camera->setListener(cameraEvents);
|
||||
status = camera->setPreviewTarget(producer);
|
||||
fprintf(stderr, "set_preview_target=%d\n", status);
|
||||
camera->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
|
||||
fprintf(stderr, "preview_callback_flags=%d\n", CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
|
||||
const android::status_t focusEvents = camera->sendCommand(
|
||||
CAMERA_CMD_ENABLE_FOCUS_MOVE_MSG, 1, 0);
|
||||
fprintf(stderr, "focus_move_events=%d\n", focusEvents);
|
||||
status = camera->startPreview();
|
||||
fprintf(stderr, "start_preview=%d\n", status);
|
||||
if (status) return 4;
|
||||
|
||||
unsigned acquired = 0;
|
||||
bool saved = false;
|
||||
const int64_t deadline = systemTime(SYSTEM_TIME_MONOTONIC) + 5000000000LL;
|
||||
while (systemTime(SYSTEM_TIME_MONOTONIC) < deadline) {
|
||||
android::IGonkGraphicBufferConsumer::BufferItem item;
|
||||
status = consumer->acquireBuffer(&item, 0);
|
||||
if (status != android::NO_ERROR) { usleep(20000); continue; }
|
||||
++acquired;
|
||||
android::sp<android::GraphicBuffer> buffer = item.mGraphicBuffer;
|
||||
if (item.mFence != nullptr) item.mFence->waitForever("vulpes-gonk-camera");
|
||||
void* pixels = nullptr;
|
||||
if (buffer != nullptr &&
|
||||
buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels) == android::NO_ERROR &&
|
||||
pixels != nullptr) {
|
||||
const size_t bytes = (size_t)buffer->getStride() * buffer->getHeight() * 3 / 2;
|
||||
const unsigned char* y = static_cast<const unsigned char*>(pixels);
|
||||
unsigned int minimum = 255, maximum = 0;
|
||||
uint64_t sum = 0;
|
||||
const size_t yBytes = (size_t)buffer->getStride() * buffer->getHeight();
|
||||
for (size_t i = 0; i < yBytes; ++i) {
|
||||
if (y[i] < minimum) minimum = y[i];
|
||||
if (y[i] > maximum) maximum = y[i];
|
||||
sum += y[i];
|
||||
}
|
||||
fprintf(stderr, "frame=%u number=%llu timestamp=%llu y=%u..%u mean=%.2f format=%d\n",
|
||||
acquired, (unsigned long long)item.mFrameNumber, (unsigned long long)item.mTimestamp, minimum, maximum,
|
||||
(double)sum / yBytes, buffer->format);
|
||||
if (!saved || acquired % 10 == 0) {
|
||||
FILE* output = fopen("/data/local/tmp/vulpes-camera-gonkqueue.nv21", "wb");
|
||||
if (output) {
|
||||
fwrite(pixels, 1, bytes, output);
|
||||
fclose(output);
|
||||
chmod("/data/local/tmp/vulpes-camera-gonkqueue.nv21", 0644);
|
||||
FILE* meta = fopen("/data/local/tmp/vulpes-camera-gonkqueue.txt", "w");
|
||||
if (meta) {
|
||||
fprintf(meta, "%u %u %u %d %zu %u %u %u %u\n", buffer->getWidth(),
|
||||
buffer->getHeight(), buffer->getStride(), buffer->format, bytes,
|
||||
acquired, minimum, maximum, (unsigned)(sum / yBytes));
|
||||
fclose(meta);
|
||||
chmod("/data/local/tmp/vulpes-camera-gonkqueue.txt", 0644);
|
||||
}
|
||||
saved = true;
|
||||
}
|
||||
}
|
||||
buffer->unlock();
|
||||
}
|
||||
consumer->releaseBuffer(item.mBuf, item.mFrameNumber, android::Fence::NO_FENCE);
|
||||
}
|
||||
fprintf(stderr, "acquired=%u saved=%d; taking_still\n", acquired, saved);
|
||||
status = camera->takePicture(CAMERA_MSG_SHUTTER | CAMERA_MSG_COMPRESSED_IMAGE);
|
||||
fprintf(stderr, "take_picture=%d\n", status);
|
||||
sleep(3);
|
||||
fprintf(stderr, "stopping_preview\n");
|
||||
camera->stopPreview();
|
||||
fprintf(stderr, "camera_stopped; disconnecting\n");
|
||||
camera->disconnect();
|
||||
consumer->consumerDisconnect();
|
||||
fprintf(stderr, "done\n");
|
||||
return saved ? 0 : 5;
|
||||
}
|
||||
99
tundra/probes/beatles-camera-permission-bridge.cpp
Normal file
99
tundra/probes/beatles-camera-permission-bridge.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Temporary, narrowly scoped API-23 permission controller for a RAM camera test.
|
||||
#include <binder/Binder.h>
|
||||
#include <binder/IServiceManager.h>
|
||||
#include <binder/Parcel.h>
|
||||
#include <binder/IPCThreadState.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <camera/ICameraService.h>
|
||||
#include <grp.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Vector.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
namespace {
|
||||
const android::String16 kDescriptor("android.os.IPermissionController");
|
||||
const android::String16 kCameraPermission("android.permission.CAMERA");
|
||||
const android::String16 kSystemEventPermission("android.permission.CAMERA_SEND_SYSTEM_EVENTS");
|
||||
const android::String16 kProbePackage("gonk.camera");
|
||||
const android::String16 kSystemPackage("android");
|
||||
const int32_t kCameraUid = 1006;
|
||||
|
||||
class CameraPermissionBridge : public android::BBinder {
|
||||
public:
|
||||
const android::String16& getInterfaceDescriptor() const override {
|
||||
return kDescriptor;
|
||||
}
|
||||
|
||||
protected:
|
||||
android::status_t onTransact(uint32_t code, const android::Parcel& data,
|
||||
android::Parcel* reply, uint32_t flags) override {
|
||||
if (code == android::IBinder::FIRST_CALL_TRANSACTION) {
|
||||
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
|
||||
const android::String16 permission = data.readString16();
|
||||
const int32_t pid = data.readInt32();
|
||||
const int32_t uid = data.readInt32();
|
||||
(void)pid;
|
||||
const bool grant = (permission == kCameraPermission && uid == kCameraUid) ||
|
||||
(permission == kSystemEventPermission && uid == 1000);
|
||||
android::String8 permissionName(permission);
|
||||
fprintf(stderr, "permission check uid=%d name=%s grant=%d\n", uid,
|
||||
permissionName.string(), grant);
|
||||
reply->writeNoException();
|
||||
reply->writeInt32(grant ? 1 : 0);
|
||||
return android::NO_ERROR;
|
||||
}
|
||||
if (code == android::IBinder::FIRST_CALL_TRANSACTION + 1) {
|
||||
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
|
||||
const int32_t uid = data.readInt32();
|
||||
reply->writeNoException();
|
||||
const bool hasPackage = uid == kCameraUid || uid == 1000;
|
||||
reply->writeInt32(hasPackage ? 1 : 0);
|
||||
if (uid == kCameraUid) reply->writeString16(kProbePackage);
|
||||
else if (uid == 1000) reply->writeString16(kSystemPackage);
|
||||
return android::NO_ERROR;
|
||||
}
|
||||
if (code == android::IBinder::FIRST_CALL_TRANSACTION + 2) {
|
||||
if (!data.enforceInterface(kDescriptor)) return android::PERMISSION_DENIED;
|
||||
const android::String16 permission = data.readString16();
|
||||
reply->writeNoException();
|
||||
reply->writeInt32(permission == kCameraPermission ? 1 : 0);
|
||||
return android::NO_ERROR;
|
||||
}
|
||||
return android::BBinder::onTransact(code, data, reply, flags);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int main(int argc, char**) {
|
||||
if (argc != 1) return 2;
|
||||
android::ProcessState::self()->startThreadPool();
|
||||
android::sp<android::IServiceManager> manager = android::defaultServiceManager();
|
||||
android::sp<CameraPermissionBridge> service = new CameraPermissionBridge();
|
||||
android::status_t result = manager->addService(android::String16("permission"), service);
|
||||
fprintf(stderr, "register permission bridge pid=%d uid=%d result=%d\n",
|
||||
getpid(), getuid(), result);
|
||||
if (result != android::NO_ERROR) return 3;
|
||||
gid_t systemGroup = 1000;
|
||||
if (setgroups(1, &systemGroup) || setgid(1000) || setuid(1000)) {
|
||||
perror("drop permission bridge to Android system UID");
|
||||
return 4;
|
||||
}
|
||||
fprintf(stderr, "permission bridge ready\n");
|
||||
android::sp<android::IBinder> cameraBinder =
|
||||
manager->checkService(android::String16("media.camera"));
|
||||
android::sp<android::ICameraService> cameraService =
|
||||
android::interface_cast<android::ICameraService>(cameraBinder);
|
||||
if (cameraService != nullptr) {
|
||||
const int32_t activeUsers[] = {0};
|
||||
cameraService->notifySystemEvent(android::ICameraService::USER_SWITCHED,
|
||||
activeUsers, 1);
|
||||
fprintf(stderr, "notified CameraService active user 0\n");
|
||||
} else {
|
||||
fprintf(stderr, "CameraService unavailable for active-user notification\n");
|
||||
}
|
||||
android::IPCThreadState::self()->joinThreadPool();
|
||||
return 0;
|
||||
}
|
||||
173
tundra/probes/beatles-camera-service-probe.cpp
Normal file
173
tundra/probes/beatles-camera-service-probe.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Bounded diagnostic for the Android Camera1 service on Nokia 2720 (API 23).
|
||||
#include <camera/Camera.h>
|
||||
#include <camera/CameraParameters.h>
|
||||
#include <hardware/gralloc.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <gui/BufferQueue.h>
|
||||
#include <gui/CpuConsumer.h>
|
||||
#include <gui/GraphicBufferAlloc.h>
|
||||
#include <utils/String16.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <thread>
|
||||
|
||||
class ProbeListener : public android::CameraListener {
|
||||
public:
|
||||
ProbeListener() : frames(0), bytes(0), jpegBytes(0), yMean(0), yMin(0), yMax(0) {}
|
||||
void notify(int32_t type, int32_t first, int32_t second) override {
|
||||
fprintf(stderr, "notify type=%d first=%d second=%d\n", type, first, second);
|
||||
}
|
||||
void postData(int32_t type, const android::sp<android::IMemory>& data,
|
||||
camera_frame_metadata_t*) override {
|
||||
if (data == nullptr) return;
|
||||
if (type == CAMERA_MSG_PREVIEW_FRAME) {
|
||||
android::Mutex::Autolock lock(mutex);
|
||||
++frames;
|
||||
bytes = data->size();
|
||||
const size_t yBytes = bytes < 640u * 480u ? bytes : 640u * 480u;
|
||||
const unsigned char* y = static_cast<const unsigned char*>(data->pointer());
|
||||
uint64_t sum = 0;
|
||||
yMin = 255;
|
||||
yMax = 0;
|
||||
for (size_t i = 0; i < yBytes; ++i) {
|
||||
if (y[i] < yMin) yMin = y[i];
|
||||
if (y[i] > yMax) yMax = y[i];
|
||||
sum += y[i];
|
||||
}
|
||||
yMean = yBytes ? (double)sum / yBytes : 0;
|
||||
if (frames == 20 && bytes) {
|
||||
FILE* out = fopen("/data/local/tmp/vulpes-camera-frame20.nv21", "wb");
|
||||
if (out) {
|
||||
fwrite(data->pointer(), 1, bytes, out);
|
||||
fclose(out);
|
||||
chmod("/data/local/tmp/vulpes-camera-frame20.nv21", 0644);
|
||||
}
|
||||
}
|
||||
} else if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
|
||||
FILE* out = fopen("/data/local/tmp/vulpes-camera-photo.jpg", "wb");
|
||||
if (out) {
|
||||
jpegBytes = fwrite(data->pointer(), 1, data->size(), out);
|
||||
fclose(out);
|
||||
chmod("/data/local/tmp/vulpes-camera-photo.jpg", 0644);
|
||||
}
|
||||
fprintf(stderr, "jpeg_callback_bytes=%zu\n", jpegBytes);
|
||||
}
|
||||
}
|
||||
void postDataTimestamp(nsecs_t, int32_t,
|
||||
const android::sp<android::IMemory>&) override {}
|
||||
android::Mutex mutex;
|
||||
unsigned frames;
|
||||
size_t bytes;
|
||||
size_t jpegBytes;
|
||||
double yMean;
|
||||
unsigned yMin;
|
||||
unsigned yMax;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc > 2) return 2;
|
||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
const int cameraId = argc == 2 ? atoi(argv[1]) : 0;
|
||||
android::ProcessState::self()->startThreadPool();
|
||||
const int count = android::Camera::getNumberOfCameras();
|
||||
fprintf(stderr, "camera_count=%d uid=%d\n", count, getuid());
|
||||
if (cameraId < 0 || cameraId >= count) return 3;
|
||||
android::CameraInfo info;
|
||||
int status = android::Camera::getCameraInfo(cameraId, &info);
|
||||
fprintf(stderr, "camera_info_status=%d facing=%d orientation=%d\n",
|
||||
status, info.facing, info.orientation);
|
||||
android::sp<android::Camera> camera = android::Camera::connect(
|
||||
cameraId, android::String16("gonk.camera"),
|
||||
android::Camera::USE_CALLING_UID);
|
||||
if (camera == nullptr) {
|
||||
fprintf(stderr, "camera_connect_failed\n");
|
||||
return 4;
|
||||
}
|
||||
android::CameraParameters configured(camera->getParameters());
|
||||
// Stock KaiOS Camera reports app.camera.givenPreviewSize as 320x240
|
||||
// on the beatles device. Gecko then selects this 4:3 size against the
|
||||
// 1600x1200 still capture size. Keep the probe on that exact path.
|
||||
configured.setPreviewSize(320, 240);
|
||||
configured.setPictureSize(1600, 1200);
|
||||
configured.setPreviewFormat("yuv420sp");
|
||||
configured.set("recording-hint", "false");
|
||||
status = camera->setParameters(configured.flatten());
|
||||
fprintf(stderr, "set_full_parameters=%d\n", status);
|
||||
android::sp<ProbeListener> listener = new ProbeListener();
|
||||
camera->setListener(listener);
|
||||
camera->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_ENABLE_MASK);
|
||||
android::String8 parameters = camera->getParameters();
|
||||
fprintf(stderr, "camera_parameters=%s\n", parameters.string());
|
||||
fprintf(stderr, "before_bufferqueue_create\n");
|
||||
android::sp<android::IGraphicBufferProducer> producer;
|
||||
android::sp<android::IGraphicBufferConsumer> consumer;
|
||||
android::sp<android::GraphicBufferAlloc> allocator = new android::GraphicBufferAlloc();
|
||||
android::BufferQueue::createBufferQueue(&producer, &consumer, allocator);
|
||||
fprintf(stderr, "after_bufferqueue_create producer=%d consumer=%d\n", producer != nullptr, consumer != nullptr);
|
||||
fprintf(stderr, "before_cpuconsumer\n");
|
||||
android::sp<android::CpuConsumer> previewConsumer =
|
||||
new android::CpuConsumer(consumer, 4);
|
||||
fprintf(stderr, "after_cpuconsumer\n");
|
||||
status = consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN |
|
||||
GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER);
|
||||
fprintf(stderr, "set_consumer_usage=%d bits=%08x\n", status,
|
||||
(unsigned)(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER));
|
||||
fprintf(stderr, "before_consumer_setname\n");
|
||||
previewConsumer->setName(android::String8("Vulpes Camera Probe"));
|
||||
fprintf(stderr, "after_consumer_setname\n");
|
||||
fprintf(stderr, "before_consumer_size\n");
|
||||
previewConsumer->setDefaultBufferSize(320, 240);
|
||||
fprintf(stderr, "after_consumer_size\n");
|
||||
fprintf(stderr, "before_set_preview_target\n");
|
||||
status = camera->setPreviewTarget(producer);
|
||||
fprintf(stderr, "set_preview_target=%d\n", status);
|
||||
status = camera->startPreview();
|
||||
fprintf(stderr, "start_preview=%d\n", status);
|
||||
unsigned graphicFrames = 0, graphicSamples = 0;
|
||||
unsigned long long graphicSum = 0;
|
||||
unsigned graphicMin = 255, graphicMax = 0;
|
||||
for (int elapsed = 0; elapsed < 3; elapsed++) {
|
||||
android::CpuConsumer::LockedBuffer buffer;
|
||||
while (previewConsumer->lockNextBuffer(&buffer) == android::NO_ERROR) {
|
||||
++graphicFrames;
|
||||
fprintf(stderr, "graphic_frame=%u width=%u height=%u stride=%u format=%d\n",
|
||||
graphicFrames, buffer.width, buffer.height, buffer.stride,
|
||||
buffer.format);
|
||||
if (buffer.data && graphicFrames >= 5) {
|
||||
const size_t yBytes = (size_t)buffer.stride * buffer.height;
|
||||
if (graphicFrames == 5) {
|
||||
FILE* out = fopen("/data/local/tmp/vulpes-camera-graphic.nv21", "wb");
|
||||
if (out) { fwrite(buffer.data, 1, yBytes * 3 / 2, out); fclose(out); chmod("/data/local/tmp/vulpes-camera-graphic.nv21", 0644); }
|
||||
}
|
||||
for (size_t i = 0; i < yBytes; i += 4096) {
|
||||
unsigned value = buffer.data[i]; graphicSum += value; ++graphicSamples;
|
||||
if (value < graphicMin) graphicMin = value;
|
||||
if (value > graphicMax) graphicMax = value;
|
||||
}
|
||||
}
|
||||
previewConsumer->unlockBuffer(buffer);
|
||||
}
|
||||
usleep(1000000);
|
||||
}
|
||||
{
|
||||
android::Mutex::Autolock lock(listener->mutex);
|
||||
fprintf(stderr, "preview_frames=%u frame_bytes=%zu callback_y_mean=%.2f min=%u max=%u graphic_frames=%u graphic_samples=%u graphic_y_mean=%.2f min=%u max=%u\n",
|
||||
listener->frames, listener->bytes, listener->yMean, listener->yMin, listener->yMax,
|
||||
graphicFrames, graphicSamples,
|
||||
graphicSamples ? (double)graphicSum / graphicSamples : 0.0,
|
||||
graphicMin, graphicMax);
|
||||
}
|
||||
std::thread([camera]() {
|
||||
fprintf(stderr, "take_picture_begin\n");
|
||||
const int pictureStatus = camera->takePicture(CAMERA_MSG_COMPRESSED_IMAGE);
|
||||
fprintf(stderr, "take_picture=%d\n", pictureStatus);
|
||||
}).detach();
|
||||
sleep(4);
|
||||
camera->stopPreview();
|
||||
camera->disconnect();
|
||||
return 0;
|
||||
}
|
||||
214
tundra/probes/beatles-camera-worker.cpp
Normal file
214
tundra/probes/beatles-camera-worker.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// CameraService worker for the Nokia 2720 Flip. The parent broker drops this
|
||||
// process to Android's camera UID before exec. Gaia talks to it over a local
|
||||
// socket; it never receives Binder or elevated credentials.
|
||||
#include <camera/Camera.h>
|
||||
#include <camera/CameraParameters.h>
|
||||
#include <binder/ProcessState.h>
|
||||
#include <gui/BufferQueue.h>
|
||||
#include <gui/CpuConsumer.h>
|
||||
#include <gui/GraphicBufferAlloc.h>
|
||||
#include <hardware/gralloc.h>
|
||||
#include <utils/Timers.h>
|
||||
#include <utils/Mutex.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/String16.h>
|
||||
#include <condition_variable>
|
||||
#include <chrono>
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
namespace {
|
||||
bool write_all(int fd, const void *data, size_t size) {
|
||||
const char *p = static_cast<const char *>(data);
|
||||
while (size) {
|
||||
ssize_t n = write(fd, p, size);
|
||||
if (n <= 0) return false;
|
||||
p += n; size -= static_cast<size_t>(n);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool read_line(int fd, std::string *line) {
|
||||
line->clear();
|
||||
char c;
|
||||
while (line->size() < 2048) {
|
||||
ssize_t n = read(fd, &c, 1);
|
||||
if (n <= 0) return false;
|
||||
if (c == '\n') return true;
|
||||
line->push_back(c);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool send_json(int fd, const std::string &json, bool frame=false,
|
||||
uint32_t sequence=0, const std::vector<uint8_t> *bytes=nullptr) {
|
||||
uint32_t length = static_cast<uint32_t>(json.size());
|
||||
if (frame) {
|
||||
uint32_t header[2] = {sequence, bytes ? static_cast<uint32_t>(bytes->size()) : 0};
|
||||
return write_all(fd, header, sizeof(header)) && (!bytes || write_all(fd,bytes->data(),bytes->size()));
|
||||
}
|
||||
return write_all(fd,&length,sizeof(length)) && write_all(fd,json.data(),json.size());
|
||||
}
|
||||
|
||||
class CameraWorker : public android::CameraListener {
|
||||
public:
|
||||
explicit CameraWorker(int fd) : fd_(fd), camera_id_(0), sequence_(0), stopping_(false) {}
|
||||
void notify(int32_t type, int32_t first, int32_t second) override {
|
||||
fprintf(stderr,"camera notify type=%d first=%d second=%d\n",type,first,second);
|
||||
}
|
||||
void postData(int32_t type, const android::sp<android::IMemory> &data,
|
||||
camera_frame_metadata_t *) override {
|
||||
if (data == nullptr) return;
|
||||
if (type == CAMERA_MSG_COMPRESSED_IMAGE) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
jpeg_.assign(static_cast<const uint8_t *>(data->pointer()),
|
||||
static_cast<const uint8_t *>(data->pointer()) + data->size());
|
||||
ready_.notify_all();
|
||||
return;
|
||||
}
|
||||
if (type != CAMERA_MSG_PREVIEW_FRAME) return;
|
||||
const size_t expected = 320u * 240u * 3u / 2u;
|
||||
if (data->size() < expected) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
frame_.assign(static_cast<const uint8_t *>(data->pointer()),
|
||||
static_cast<const uint8_t *>(data->pointer()) + expected);
|
||||
++sequence_;
|
||||
if(sequence_==1) fprintf(stderr,"camera first frame bytes=%zu uid=%d\n",frame_.size(),getuid());
|
||||
}
|
||||
ready_.notify_all();
|
||||
}
|
||||
void postDataTimestamp(nsecs_t, int32_t,
|
||||
const android::sp<android::IMemory> &) override {}
|
||||
|
||||
int run() {
|
||||
android::ProcessState::self()->startThreadPool();
|
||||
std::string line;
|
||||
while (read_line(fd_, &line)) {
|
||||
if (line == "start" || line == "start 0") {
|
||||
if (!start()) { send_json(fd_,"{\"ready\":false,\"error\":\"CAMERA_START_FAILED\"}"); continue; }
|
||||
if (!send_json(fd_,"{\"ready\":true,\"width\":320,\"height\":240,\"format\":\"nv21\",\"orientation\":270,\"flashModes\":[\"off\",\"auto\",\"on\",\"torch\"],\"pictureSizes\":[{\"width\":1600,\"height\":1200}],\"focusModes\":[\"fixed\"],\"maxFocusAreas\":0,\"zoomRatios\":[1] }")) return 1;
|
||||
} else if (line == "frame") {
|
||||
std::vector<uint8_t> frame;
|
||||
uint32_t seq;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
const uint32_t previous = delivered_;
|
||||
ready_.wait_for(lock,std::chrono::seconds(2),[&]{return sequence_>previous || stopping_;});
|
||||
if (sequence_<=previous || frame_.empty()) { if(!send_json(fd_,"",true,sequence_,nullptr)) return 1; continue; }
|
||||
seq=sequence_; delivered_=seq; frame=frame_;
|
||||
}
|
||||
if (!send_json(fd_,"",true,seq,&frame)) return 1;
|
||||
} else if (line == "stop") {
|
||||
stop(); send_json(fd_,"{\"ok\":true}"); return 0;
|
||||
} else if (line.find("\"action\":\"flash\"") != std::string::npos) {
|
||||
const size_t key=line.find("\"mode\":\"");
|
||||
const size_t begin=key==std::string::npos?key:key+8;
|
||||
const size_t end=begin==std::string::npos?begin:line.find('"',begin);
|
||||
if(begin==std::string::npos || end==std::string::npos) {
|
||||
send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_INVALID_FLASH_MODE\"}");continue;
|
||||
}
|
||||
const std::string mode=line.substr(begin,end-begin);
|
||||
android::CameraParameters params(camera_->getParameters());
|
||||
params.set(android::CameraParameters::KEY_FLASH_MODE,mode.c_str());
|
||||
const android::status_t status=camera_->setParameters(params.flatten());
|
||||
if(status==android::NO_ERROR) {
|
||||
camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
|
||||
camera_->startPreview();
|
||||
send_json(fd_,"{\"ok\":true,\"result\":true}");
|
||||
}
|
||||
else send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_FLASH_UNAVAILABLE\"}");
|
||||
} else if (line.find("\"action\":\"zoom\"") != std::string::npos) {
|
||||
// This module reports a single zoom ratio (1x), so expose a stable
|
||||
// no-op instead of asking Camera1 to select an unsupported index.
|
||||
send_json(fd_,"{\"ok\":true,\"result\":{\"zoom\":1}}");
|
||||
} else if (line.find("\"action\":\"picture\"") != std::string::npos) {
|
||||
{ std::lock_guard<std::mutex> lock(mutex_); jpeg_.clear(); }
|
||||
const android::status_t status=camera_->takePicture(CAMERA_MSG_COMPRESSED_IMAGE);
|
||||
std::vector<uint8_t> jpeg;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mutex_);
|
||||
ready_.wait_for(lock,std::chrono::seconds(12),[&]{return !jpeg_.empty() || stopping_;});
|
||||
jpeg=jpeg_;
|
||||
}
|
||||
camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
|
||||
camera_->startPreview();
|
||||
if(status!=android::NO_ERROR || jpeg.empty()) {
|
||||
send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_CAPTURE_FAILED\"}");continue;
|
||||
}
|
||||
char reply[128];
|
||||
std::snprintf(reply,sizeof(reply),"{\"ok\":true,\"jpegLength\":%zu}",jpeg.size());
|
||||
if(!send_json(fd_,reply) || !write_all(fd_,jpeg.data(),jpeg.size())) return 1;
|
||||
} else {
|
||||
send_json(fd_,"{\"ok\":false,\"error\":\"CAMERA_UNSUPPORTED_COMMAND\"}");
|
||||
}
|
||||
}
|
||||
stop(); return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
bool start() {
|
||||
if (camera_.get() != nullptr) stop();
|
||||
fprintf(stderr,"camera count=%d uid=%d\n",android::Camera::getNumberOfCameras(),getuid());
|
||||
camera_ = android::Camera::connect(camera_id_,android::String16("gonk.camera"),android::Camera::USE_CALLING_UID);
|
||||
if (camera_.get() == nullptr) {fprintf(stderr,"camera connect failed\n");return false;}
|
||||
android::CameraParameters params(camera_->getParameters());
|
||||
params.setPreviewSize(320,240); params.setPictureSize(1600,1200);
|
||||
params.setPreviewFormat("yuv420sp"); params.set("recording-hint","false");
|
||||
android::status_t status=camera_->setParameters(params.flatten());
|
||||
fprintf(stderr,"camera set parameters=%d\n",status);
|
||||
if (status != android::NO_ERROR) { stop(); return false; }
|
||||
android::sp<android::IGraphicBufferProducer> producer;
|
||||
android::sp<android::IGraphicBufferConsumer> consumer;
|
||||
android::sp<android::GraphicBufferAlloc> allocator = new android::GraphicBufferAlloc();
|
||||
android::BufferQueue::createBufferQueue(&producer,&consumer,allocator);
|
||||
consumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER);
|
||||
consumer->setDefaultBufferSize(320,240);
|
||||
consumer_ = new android::CpuConsumer(consumer,4);
|
||||
consumer_->setName(android::String8("Vulpes Camera"));
|
||||
status=camera_->setPreviewTarget(producer);
|
||||
fprintf(stderr,"camera set target=%d\n",status);
|
||||
if (status != android::NO_ERROR) { stop(); return false; }
|
||||
camera_->setListener(this);
|
||||
// The broker consumes copied Camera1 preview frames rather than video metadata.
|
||||
camera_->setPreviewCallbackFlags(CAMERA_FRAME_CALLBACK_FLAG_CAMERA);
|
||||
status=camera_->startPreview(); fprintf(stderr,"camera start preview=%d\n",status);
|
||||
if (status != android::NO_ERROR) { stop(); return false; }
|
||||
stopping_=false; delivered_=sequence_;
|
||||
consumer_thread_=std::thread([this]{
|
||||
while(!stopping_) {
|
||||
android::CpuConsumer::LockedBuffer buffer;
|
||||
if(consumer_->lockNextBuffer(&buffer)==android::NO_ERROR) consumer_->unlockBuffer(buffer);
|
||||
else usleep(10000);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
void stop() {
|
||||
{ std::lock_guard<std::mutex> lock(mutex_); stopping_=true; }
|
||||
ready_.notify_all();
|
||||
if (camera_.get() != nullptr) { camera_->stopPreview(); camera_->disconnect(); camera_.clear(); }
|
||||
if(consumer_thread_.joinable()) consumer_thread_.join();
|
||||
consumer_.clear();
|
||||
}
|
||||
int fd_; int camera_id_;
|
||||
android::sp<android::Camera> camera_;
|
||||
android::sp<android::CpuConsumer> consumer_;
|
||||
std::mutex mutex_; std::condition_variable ready_;
|
||||
std::thread consumer_thread_;
|
||||
std::vector<uint8_t> frame_, jpeg_; uint32_t sequence_, delivered_; std::atomic<bool> stopping_;
|
||||
};
|
||||
}
|
||||
|
||||
int main() {
|
||||
setvbuf(stderr,nullptr,_IONBF,0);
|
||||
android::sp<CameraWorker> worker = new CameraWorker(STDIN_FILENO);
|
||||
return worker->run();
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
#include "beatles-lid.h"
|
||||
static volatile sig_atomic_t interrupted;
|
||||
static int phone_controls;
|
||||
static int camera_broker_active;
|
||||
static void interrupt(int sig) { interrupted = sig; }
|
||||
static void inventory_camera_nodes(void) {
|
||||
static const int minors[] = {0, 1, 32, 33};
|
||||
|
|
@ -238,6 +239,11 @@ static void inside(int seconds) {
|
|||
require(mount("/proc", ROOT "/proc", NULL, MS_BIND, NULL), "bind proc");
|
||||
require(mount("/sys", ROOT "/sys", NULL, MS_BIND, NULL), "bind sys");
|
||||
require(mount(NULL, ROOT "/sys", NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL), "readonly sys");
|
||||
if (camera_broker_active) {
|
||||
if (mkdir(ROOT "/work/camera-ipc",0777) && errno!=EEXIST) _exit(1);
|
||||
require(mount(BASE "/camera-ipc",ROOT "/work/camera-ipc",NULL,MS_BIND,NULL),"camera IPC mount");
|
||||
require(chmod(ROOT "/work/camera-ipc",0777),"camera IPC permissions");
|
||||
}
|
||||
require(mount("tmpfs", ROOT "/dev", "tmpfs", MS_NOSUID, "size=1m,mode=0755"), "devices");
|
||||
node(ROOT "/dev/null",1,3,0666); node(ROOT "/dev/zero",1,5,0666);
|
||||
node(ROOT "/dev/random",1,8,0666); node(ROOT "/dev/urandom",1,9,0666);
|
||||
|
|
@ -261,6 +267,7 @@ static void inside(int seconds) {
|
|||
setenv("VULPES_TRIAL_MANUAL",seconds >= 900 ? "1" : "0",1);
|
||||
setenv("VULPES_TUNDRA_RADIO",phone_controls?"1":"0",1);
|
||||
setenv("VULPES_TUNDRA_WIFI",phone_controls?"1":"0",1);
|
||||
setenv("VULPES_TUNDRA_CAMERA",camera_broker_active?"1":"0",1);
|
||||
setenv("LANG","C",1); setenv("DISPLAY",":0",1);
|
||||
setenv("LD_LIBRARY_PATH","/usr/lib/arm-linux-gnueabihf:/lib/arm-linux-gnueabihf",1);
|
||||
execl("/bin/sh","sh","/work/display-test.sh",NULL);
|
||||
|
|
@ -272,6 +279,7 @@ int main(int argc, char **argv) {
|
|||
int seconds = !strcmp(argv[1],"--temporary-45s") ? 45 :
|
||||
!strcmp(argv[1],"--temporary-90s") ? 90 :
|
||||
(!strcmp(argv[1],"--temporary-180s") || !strcmp(argv[1],"--phone-180s")) ? 180 :
|
||||
!strcmp(argv[1],"--temporary-240s") ? 240 :
|
||||
(!strcmp(argv[1],"--interactive-900s") || !strcmp(argv[1],"--phone-900s")) ? 900 :
|
||||
(!strcmp(argv[1],"--interactive-1800s") || !strcmp(argv[1],"--phone-1800s")) ? 1800 : 0;
|
||||
if (!seconds) return 2;
|
||||
|
|
@ -309,6 +317,17 @@ int main(int argc, char **argv) {
|
|||
* previous run's image be mistaken for a frame from this session. */
|
||||
unlink(ROOT "/work/camera-frame.yuv");
|
||||
unlink(ROOT "/work/camera-photo.jpg");
|
||||
/* These names belong to our API-23 diagnostic; adb shell cannot remove
|
||||
* camera-UID files from Android's sticky /data/local/tmp directory. */
|
||||
unlink("/data/local/tmp/vulpes-camera-frame.nv21");
|
||||
unlink("/data/local/tmp/vulpes-camera-frame20.nv21");
|
||||
unlink("/data/local/tmp/vulpes-camera-graphic.nv21");
|
||||
unlink("/data/local/tmp/vulpes-camera-photo.jpg");
|
||||
unlink("/data/local/tmp/vulpes-camera-glconsumer.ppm");
|
||||
unlink("/data/local/tmp/vulpes-camera-gonkqueue.nv21");
|
||||
unlink("/data/local/tmp/vulpes-camera-gonkqueue.txt");
|
||||
unlink("/data/local/tmp/vulpes-camera-callback.yuv");
|
||||
unlink("/data/local/tmp/vulpes-camera-callback.jpg");
|
||||
sleep(2);
|
||||
if (!access(BASE "/camera-probe-once", F_OK)) {
|
||||
unlink(BASE "/camera-probe-once");
|
||||
|
|
@ -326,12 +345,80 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
int result = android_service("/system/bin/stop");
|
||||
printf("stop b2g: %d\n",result);
|
||||
pid_t child = -1, radio = -1;
|
||||
pid_t child = -1, radio = -1, permission_bridge = -1, camera_broker = -1;
|
||||
struct lid_state lid;lid_init(&lid,fb);
|
||||
if (!result) {
|
||||
sleep(1);
|
||||
puts("Camera node inventory after stopping B2G");
|
||||
inventory_camera_nodes();
|
||||
int gl_camera_probe = !access(BASE "/camera-gl-probe-once", F_OK);
|
||||
int camera_service_probe = !access(BASE "/camera-service-probe-once", F_OK);
|
||||
if (gl_camera_probe || camera_service_probe) {
|
||||
unlink(BASE "/camera-service-probe-once");
|
||||
unlink(BASE "/camera-gl-probe-once");
|
||||
permission_bridge = fork();
|
||||
if (!permission_bridge) {
|
||||
int output = open(BASE "/camera-permission-bridge.log",
|
||||
O_CREAT | O_TRUNC | O_WRONLY, 0644);
|
||||
if (output >= 0) {
|
||||
fchmod(output, 0644);
|
||||
dup2(output, STDOUT_FILENO);
|
||||
dup2(output, STDERR_FILENO);
|
||||
close(output);
|
||||
}
|
||||
setenv("LD_LIBRARY_PATH", "/system/lib:/data/local/tmp", 1);
|
||||
execl("/data/local/tmp/vulpes-camera-permission-bridge",
|
||||
"vulpes-camera-permission-bridge", NULL);
|
||||
_exit(127);
|
||||
}
|
||||
if (permission_bridge > 0) sleep(2);
|
||||
pid_t probe = fork();
|
||||
if (!probe) {
|
||||
int output = open(gl_camera_probe ? BASE "/camera-gl-probe.log" :
|
||||
BASE "/camera-service-probe.log",
|
||||
O_CREAT | O_TRUNC | O_WRONLY, 0644);
|
||||
if (output >= 0) {
|
||||
fchmod(output, 0644);
|
||||
dup2(output, STDOUT_FILENO);
|
||||
dup2(output, STDERR_FILENO);
|
||||
close(output);
|
||||
}
|
||||
gid_t groups[] = {1006};
|
||||
if (setgroups(1, groups) || setgid(1006) || setuid(1006)) _exit(126);
|
||||
setenv("LD_LIBRARY_PATH", "/system/lib:/data/local/tmp", 1);
|
||||
if (gl_camera_probe)
|
||||
execl("/data/local/tmp/vulpes-camera-gl-probe-20261023",
|
||||
"vulpes-camera-gl-probe-20261023", NULL);
|
||||
else
|
||||
execl("/data/local/tmp/vulpes-camera-probe-20261023",
|
||||
"vulpes-camera-probe-20261023", "0", NULL);
|
||||
_exit(127);
|
||||
}
|
||||
if (probe > 0) {
|
||||
int probe_status = 0, finished = 0;
|
||||
for (int attempt = 0; attempt < (gl_camera_probe ? 200 : 100); attempt++) {
|
||||
if (waitpid(probe, &probe_status, WNOHANG) == probe) {
|
||||
finished = 1;
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
if (!finished) {
|
||||
kill(probe, SIGKILL);
|
||||
waitpid(probe, &probe_status, 0);
|
||||
printf("Camera%s probe timed out after %d seconds\n",
|
||||
gl_camera_probe ? " EGL/GLConsumer" : "1", gl_camera_probe ? 20 : 10);
|
||||
} else {
|
||||
printf("CameraService probe exit: %d\n",
|
||||
WIFEXITED(probe_status) ? WEXITSTATUS(probe_status) : -1);
|
||||
}
|
||||
}
|
||||
if (permission_bridge > 0) {
|
||||
kill(permission_bridge, SIGTERM);
|
||||
waitpid(permission_bridge, NULL, 0);
|
||||
permission_bridge = -1;
|
||||
}
|
||||
}
|
||||
int preview_dump_probe = !access(BASE "/qcamera-preview-dump-once", F_OK);
|
||||
if (!access(BASE "/qcamera-probe-once", F_OK) || preview_dump_probe) {
|
||||
unlink(BASE "/qcamera-probe-once");
|
||||
|
|
@ -393,6 +480,47 @@ int main(int argc, char **argv) {
|
|||
if (prop > 0) waitpid(prop, NULL, 0);
|
||||
if (!umount(dump_path)) rmdir(dump_path);
|
||||
}
|
||||
camera_broker_active = !access(BASE "/camera-broker-once",F_OK);
|
||||
if (camera_broker_active) {
|
||||
unlink(BASE "/camera-broker-once");
|
||||
if (mkdir(BASE "/camera-ipc",0777) && errno!=EEXIST) perror("camera IPC directory");
|
||||
chmod(BASE "/camera-ipc",0777);
|
||||
unlink(BASE "/camera-ipc/camera.sock");
|
||||
permission_bridge=fork();
|
||||
if(!permission_bridge) {
|
||||
int output=open(BASE "/camera-permission-bridge.log",O_CREAT|O_TRUNC|O_WRONLY,0644);
|
||||
if(output>=0){fchmod(output,0644);dup2(output,STDOUT_FILENO);dup2(output,STDERR_FILENO);close(output);}
|
||||
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
|
||||
execl("/data/local/tmp/vulpes-camera-permission-bridge","vulpes-camera-permission-bridge",NULL);
|
||||
_exit(127);
|
||||
}
|
||||
if(permission_bridge>0) {
|
||||
int ready=0;
|
||||
for(int i=0;i<50;i++) {
|
||||
FILE *log=fopen(BASE "/camera-permission-bridge.log","r");
|
||||
if(log){char line[256];while(fgets(line,sizeof(line),log))if(strstr(line,"permission bridge ready"))ready=1;fclose(log);}
|
||||
if(ready)break;
|
||||
usleep(100000);
|
||||
}
|
||||
}
|
||||
camera_broker=fork();
|
||||
if(!camera_broker) {
|
||||
int output=open(BASE "/camera-broker.log",O_CREAT|O_TRUNC|O_WRONLY,0644);
|
||||
if(output>=0){fchmod(output,0644);dup2(output,STDOUT_FILENO);dup2(output,STDERR_FILENO);close(output);}
|
||||
setenv("LD_LIBRARY_PATH","/system/lib:/data/local/tmp",1);
|
||||
execl("/data/local/tmp/vulpes-beatles-camera-broker","vulpes-beatles-camera-broker",NULL);
|
||||
_exit(127);
|
||||
}
|
||||
if(camera_broker>0) {
|
||||
int ready=0;
|
||||
for(int i=0;i<50;i++) {
|
||||
struct stat info;
|
||||
if(!stat(BASE "/camera-ipc/camera.sock",&info) && S_ISSOCK(info.st_mode)){ready=1;break;}
|
||||
usleep(100000);
|
||||
}
|
||||
if(!ready){fprintf(stderr,"Camera broker did not create its socket\n");camera_broker_active=0;}
|
||||
}
|
||||
}
|
||||
if (seconds >= 180) {
|
||||
radio = fork();
|
||||
if (!radio) radio_inside();
|
||||
|
|
@ -450,6 +578,18 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
if(radio>0) {kill(-radio,SIGKILL);waitpid(radio,NULL,0);}
|
||||
}
|
||||
if (camera_broker > 0) {
|
||||
kill(-camera_broker,SIGTERM);
|
||||
for(int i=0;i<30;i++) {
|
||||
if(waitpid(camera_broker,NULL,WNOHANG)==camera_broker){camera_broker=-1;break;}
|
||||
usleep(100000);
|
||||
}
|
||||
if(camera_broker>0){kill(-camera_broker,SIGKILL);waitpid(camera_broker,NULL,0);}
|
||||
}
|
||||
if (permission_bridge > 0) {
|
||||
kill(permission_bridge, SIGTERM);
|
||||
waitpid(permission_bridge, NULL, 0);
|
||||
}
|
||||
clean_runtime();
|
||||
lid_restore(&lid);
|
||||
if (ioctl(fb,FBIOPUT_VSCREENINFO,&original)) perror("restore framebuffer mode");
|
||||
|
|
|
|||
Loading…
Reference in a new issue