// SPDX-License-Identifier: MPL-2.0 // Bounded diagnostic for the Android Camera1 service on Nokia 2720 (API 23). #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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& 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(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&) 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 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 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 producer; android::sp consumer; android::sp 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 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; }