From ffa7ef15ae9a4204155659376ada82f4cda42410 Mon Sep 17 00:00:00 2001 From: shinya Date: Tue, 19 May 2026 13:23:37 +0200 Subject: [PATCH] init --- flake.nix | 53 ++++++++++++ main.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 flake.nix create mode 100644 main.cpp diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e028ecc --- /dev/null +++ b/flake.nix @@ -0,0 +1,53 @@ +{ + description = "Cross compile libcamera app for Raspberry Pi Zero"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + system = "x86_64-linux"; + + pkgs = import nixpkgs { + inherit system; + + crossSystem = { + config = "armv6l-unknown-linux-gnueabihf"; + }; + }; + in + { + devShells.${system}.default = + pkgs.mkShell { + packages = with pkgs; [ + stdenv.cc + + pkg-config + + libcamera + + libGL + libglvnd + + mesa + + # useful tools + gdb + ]; + + shellHook = '' + echo "ARM cross shell for Raspberry Pi Zero" + echo + + export CC=arm-linux-gnueabihf-gcc + export CXX=arm-linux-gnueabihf-g++ + + export PKG_CONFIG_ALLOW_CROSS=1 + + echo "Compiler:" + $CXX --version + ''; + }; + }; +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..dd9eefb --- /dev/null +++ b/main.cpp @@ -0,0 +1,243 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace libcamera; + +static std::atomic capturing{true}; +static std::shared_ptr camera; +static std::mutex frameMutex; +static std::condition_variable frameCv; +static std::queue done; +static int clientFd = -1; + +struct MappedBuffer { void *mem; size_t len; }; +static std::unordered_map mapped; + +enum class Mode { Motion, Raw }; + +static Mode parseMode(int argc, char **argv) +{ + if (argc < 2) + return Mode::Motion; + if (!strcmp(argv[1], "--raw") || !strcmp(argv[1], "raw")) + return Mode::Raw; + if (!strcmp(argv[1], "--motion") || !strcmp(argv[1], "motion")) + return Mode::Motion; + std::cerr << "Usage: " << argv[0] << " [--motion|--raw]\n"; + exit(1); +} + +static int waitForClient(int port, const char *label) +{ + int server = socket(AF_INET, SOCK_STREAM, 0); + int yes = 1; + setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + sockaddr_in addr{}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + bind(server, reinterpret_cast(&addr), sizeof(addr)); + listen(server, 1); + std::cerr << "tcp://0.0.0.0:" << port << " waiting...\n"; + int client = accept(server, nullptr, nullptr); + close(server); + setsockopt(client, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); + std::cerr << "streaming " << label << "\n"; + return client; +} + +static bool sendAll(const unsigned char *data, size_t size) +{ + while (size && capturing) { + ssize_t n = send(clientFd, data, size, MSG_NOSIGNAL); + if (n <= 0) + return false; + data += n; + size -= n; + } + return true; +} + +static void requestComplete(Request *request) +{ + if (request->status() == Request::RequestCancelled) + return; + std::lock_guard lock(frameMutex); + done.push(request); + frameCv.notify_one(); +} + +// Tight RGB24 row copy (Pi: BGR888 libcamera format = RGB bytes in memory). +static void packRgb24(const unsigned char *src, unsigned char *dst, int w, int h, int stride) +{ + for (int y = 0; y < h; ++y) + memcpy(dst + y * w * 3, src + y * stride, w * 3); +} + +// Same math as the GLES shader: luminance diff, brighter = more motion. +static void motionRgb24(const unsigned char *prev, const unsigned char *cur, unsigned char *out, int pixels) +{ + for (int i = 0; i < pixels; ++i) { + int o = i * 3; + int a = (prev[o] * 77 + prev[o + 1] * 150 + prev[o + 2] * 29) >> 8; + int b = (cur[o] * 77 + cur[o + 1] * 150 + cur[o + 2] * 29) >> 8; + unsigned char d = static_cast(std::min(255, abs(b - a) * 10)); + out[o] = out[o + 1] = out[o + 2] = d; + } +} + +int main(int argc, char **argv) +{ + Mode mode = parseMode(argc, argv); + std::signal(SIGINT, [](int) { capturing = false; }); + + auto cm = std::make_unique(); + cm->start(); + if (cm->cameras().empty()) { + std::cerr << "no camera\n"; + return 1; + } + + camera = cm->get(cm->cameras()[0]->id()); + camera->acquire(); + + auto config = camera->generateConfiguration({ StreamRole::Viewfinder }); + StreamConfiguration &sc = config->at(0); + Size full = sc.size; + sc.size.width = (full.width / 2) & ~1u; + sc.size.height = (full.height / 2) & ~1u; + sc.pixelFormat = formats::BGR888; + sc.colorSpace = ColorSpace::Sycc; + sc.bufferCount = 4; + if (config->validate() == CameraConfiguration::Invalid || sc.pixelFormat != formats::BGR888) { + std::cerr << "BGR888 config failed\n"; + return 1; + } + camera->configure(config.get()); + + const int w = sc.size.width, h = sc.size.height, stride = sc.stride; + const int pixels = w * h; + const size_t frameBytes = pixels * 3; + std::cerr << "mode=" << (mode == Mode::Raw ? "raw" : "motion") + << " " << w << "x" << h << " (half of " << full.toString() << ")" + << " stride=" << stride << "\n"; + + std::vector cur(frameBytes), prev(frameBytes), out(frameBytes); + + clientFd = waitForClient(5000, mode == Mode::Raw ? "raw rgb24" : "motion rgb24"); + + FrameBufferAllocator *allocator = new FrameBufferAllocator(camera); + Stream *stream = sc.stream(); + if (allocator->allocate(stream) < 0) { + std::cerr << "buffer alloc failed\n"; + return 1; + } + + for (const auto &buffer : allocator->buffers(stream)) { + const auto &plane = buffer->planes()[0]; + mapped[buffer.get()] = { + mmap(nullptr, plane.length, PROT_READ, MAP_SHARED, plane.fd.get(), plane.offset), + plane.length + }; + } + + std::vector> requests; + for (const auto &buffer : allocator->buffers(stream)) { + auto request = camera->createRequest(); + if (!request || request->addBuffer(stream, buffer.get()) < 0) + return 1; + requests.push_back(std::move(request)); + } + + camera->requestCompleted.connect(requestComplete); + camera->start(); + for (auto &r : requests) + camera->queueRequest(r.get()); + + bool havePrev = false; + unsigned long frames = 0; + + while (capturing) { + Request *request = nullptr; + { + std::unique_lock lock(frameMutex); + frameCv.wait_for(lock, std::chrono::milliseconds(50), [] { return !done.empty(); }); + if (done.empty()) + continue; + + // Drop stale frames: only process the newest completed request. + while (done.size() > 1) { + Request *stale = done.front(); + done.pop(); + lock.unlock(); + stale->reuse(Request::ReuseBuffers); + camera->queueRequest(stale); + lock.lock(); + } + request = done.front(); + done.pop(); + } + + FrameBuffer *buffer = request->buffers().begin()->second; + if (buffer->metadata().status == FrameMetadata::FrameSuccess) { + auto *mem = static_cast(mapped[buffer].mem); + + if (mode == Mode::Raw) { + if (stride == w * 3) { + if (!sendAll(mem, frameBytes)) + capturing = false; + } else { + packRgb24(mem, cur.data(), w, h, stride); + if (!sendAll(cur.data(), frameBytes)) + capturing = false; + } + } else { + packRgb24(mem, cur.data(), w, h, stride); + if (havePrev) { + motionRgb24(prev.data(), cur.data(), out.data(), pixels); + if (!sendAll(out.data(), frameBytes)) + capturing = false; + } + prev.swap(cur); + havePrev = true; + } + + if (++frames % 60 == 0) + std::cerr << "frames " << frames << "\n"; + } + + request->reuse(Request::ReuseBuffers); + camera->queueRequest(request); + } + + camera->stop(); + camera->requestCompleted.disconnect(); + if (clientFd >= 0) + close(clientFd); + for (auto &[_, m] : mapped) + munmap(m.mem, m.len); + allocator->free(stream); + delete allocator; + camera->release(); + cm->stop(); + return 0; +}