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

213 lines
9.5 KiB
C++

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