VulpesOS/tundra/probes/beatles-camera-service-probe.cpp
2026-11-04 12:52:20 +01:00

173 lines
7.4 KiB
C++

// 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;
}