├── .dockerignore ├── .github ├── FUNDING.yml ├── ci │ ├── Dockerfile │ └── build-env └── workflows │ ├── build_release.yaml │ └── build_test.yaml ├── .gitignore ├── .gitmodules ├── .vscode └── c_cpp_properties.json ├── Makefile ├── README.md ├── RELEASE.md ├── VERSION ├── cmd ├── camera-streamer │ ├── http.c │ ├── main.c │ ├── opts.c │ └── status.cc └── list-devices │ └── main.c ├── debian ├── camera-streamer-generic.install ├── camera-streamer-raspi.install ├── changelog ├── compat ├── control ├── rules └── source │ └── format ├── debug ├── btt-pi.txt ├── btt-pi2.txt ├── orangepi4lts.txt ├── raspi4.txt └── rockpi4se.txt ├── device ├── buffer.c ├── buffer.h ├── buffer_list.c ├── buffer_list.h ├── buffer_lock.c ├── buffer_lock.h ├── buffer_queue.c ├── camera │ ├── camera.c │ ├── camera.h │ ├── camera_debug.c │ ├── camera_decoder.c │ ├── camera_input.c │ ├── camera_isp.c │ ├── camera_output.c │ ├── camera_pipeline.c │ └── camera_rescaller.c ├── device.c ├── device.h ├── device_list.c ├── device_list.h ├── dummy │ ├── buffer.c │ ├── buffer_list.c │ ├── device.c │ ├── dummy.c │ └── dummy.h ├── libcamera │ ├── buffer.cc │ ├── buffer_list.cc │ ├── device.cc │ ├── fake_camera.c │ ├── libcamera.cc │ ├── libcamera.hh │ └── options.cc ├── links.c ├── links.h └── v4l2 │ ├── buffer.c │ ├── buffer_list.c │ ├── debug.c │ ├── device.c │ ├── device_list.c │ ├── device_media.c │ ├── device_options.c │ ├── v4l2.c │ └── v4l2.h ├── docs ├── configure.md ├── install-manual.md ├── performance-analysis.md ├── raspi-libcamera.md ├── streaming.md ├── v4l2-isp-mode.md └── v4l2-usb-mode.md ├── html ├── control.html ├── index.html └── webrtc.html ├── output ├── http_ffmpeg.c ├── http_h264.c ├── http_hls.c ├── http_jpeg.c ├── output.c ├── output.h ├── rtsp │ ├── rtsp.cc │ └── rtsp.h └── webrtc │ ├── webrtc.cc │ └── webrtc.h ├── service ├── camera-streamer-arducam-16MP.service ├── camera-streamer-arducam-64MP.service ├── camera-streamer-generic-usb-cam.service ├── camera-streamer-raspi-usb-cam.service ├── camera-streamer-raspi-v2-8MP.service └── camera-streamer-raspi-v3-12MP.service ├── tests ├── broken.jpeg ├── capture.bg10p ├── capture.h264 ├── capture.jpeg ├── capture.sh ├── capture.yuv420 ├── dummy.sh └── libcamera │ └── orientation.cc ├── tools ├── csi_camera.sh ├── dump_cameras.sh ├── libcamera_camera.sh ├── rpi_debug.sh ├── rpi_measure.sh ├── rpi_mem_usage.sh ├── run_all.bash └── usb_camera.sh └── util ├── ffmpeg ├── remuxer.c └── remuxer.h ├── http ├── http.c ├── http.h ├── http_methods.c └── json.hh └── opts ├── control.c ├── control.h ├── fourcc.c ├── fourcc.h ├── helpers.hh ├── log.c ├── log.h ├── opts.c └── opts.h /.dockerignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.d 3 | *.a 4 | test_* 5 | tmp/ 6 | .vscode/ 7 | CMakeFiles/ 8 | examples/ 9 | Dockerfile 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: ayufan 2 | -------------------------------------------------------------------------------- /.github/ci/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DOCKER_ARCH 2 | ARG DEBIAN_VERSION 3 | FROM ${DOCKER_ARCH}debian:${DEBIAN_VERSION} as build_env 4 | 5 | RUN apt-get -y update && apt-get -y install gnupg2 6 | 7 | # Add RPI packages (the whole base system, since RPI ships its own GCC) 8 | ARG DEBIAN_VERSION 9 | ARG BUILD_TYPE="generic" 10 | RUN [ "$BUILD_TYPE" != "raspi" ] || \ 11 | ( \ 12 | ( [ "$(dpkg --print-architecture)" != "armhf" ] || echo "deb http://raspbian.raspberrypi.org/raspbian/ $DEBIAN_VERSION main contrib non-free rpi" > /etc/apt/sources.list ) && \ 13 | echo "deb http://archive.raspberrypi.org/debian/ $DEBIAN_VERSION main" > /etc/apt/sources.list.d/raspi.list && \ 14 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 9165938D90FDDD2E 82B129927FA3303E && \ 15 | apt-get -y update && \ 16 | apt-get -y install libcamera-dev liblivemedia-dev \ 17 | ) 18 | 19 | # Default packages 20 | RUN apt-get -y install build-essential xxd cmake ccache git-core pkg-config \ 21 | libavformat-dev libavutil-dev libavcodec-dev libssl-dev v4l-utils debhelper 22 | 23 | FROM build_env as build 24 | ADD / /src 25 | WORKDIR /src 26 | RUN git clean -ffdx 27 | RUN git submodule update --init --recursive --recommend-shallow 28 | RUN git submodule foreach --recursive git clean -ffdx 29 | 30 | FROM build as deb_make 31 | ARG GIT_VERSION 32 | ARG BUILD_TYPE="generic" 33 | ENV DEB_BUILD_PROFILES="$BUILD_TYPE" 34 | 35 | RUN apt-get build-dep -y $PWD 36 | RUN . /etc/os-release && \ 37 | export RELEASE_SUFFIX="$VERSION_CODENAME" && \ 38 | dpkg-buildpackage -us -uc -b 39 | 40 | RUN mkdir -p /deb && mv ../*.deb /deb/ 41 | -------------------------------------------------------------------------------- /.github/ci/build-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -lt 3 ]]; then 4 | echo "usage: $0 " 5 | exit 1 6 | fi 7 | 8 | docker_image="camera_streamer_build_env" 9 | 10 | build_type="raspi" 11 | [[ -n "$1" ]] && build_type="$1" 12 | 13 | debian_version="bullseye" 14 | [[ -n "$2" ]] && debian_version="$2" && docker_image="${docker_image}_${2}" 15 | 16 | docker_arch="" 17 | [[ -n "$3" ]] && docker_arch="$3/" && docker_image="${docker_image}_${3}" 18 | 19 | PWD=$(pwd) 20 | ROOT=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/../.." &> /dev/null && pwd) 21 | 22 | set -xeo pipefail 23 | 24 | docker build -t "$docker_image" \ 25 | --build-arg "DOCKER_ARCH=$docker_arch" \ 26 | --build-arg "DEBIAN_VERSION=$debian_version" \ 27 | --build-arg "BUILD_TYPE=$build_type" \ 28 | --target build_env - < .github/ci/Dockerfile 29 | 30 | exec docker run --rm -it -u "$UID" -v "$ROOT:$ROOT" -w "$ROOT" "$docker_image" 31 | -------------------------------------------------------------------------------- /.github/workflows/build_release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | permissions: 8 | contents: write 9 | strategy: 10 | matrix: 11 | debian_version: [bullseye, bookworm] 12 | docker_arch: [amd64, arm32v7, arm64v8] 13 | build_type: [generic, raspi] 14 | exclude: 15 | - docker_arch: amd64 16 | build_type: raspi 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | submodules: recursive 23 | - name: Set GIT_VERSION 24 | shell: bash 25 | run: | 26 | majorVer=$(cat VERSION) 27 | lastVer=$(git tag --sort version:refname --list "v$majorVer.*" | tail -n1) 28 | if [[ -n "$lastVer" ]]; then 29 | newVer=(${lastVer//./ }) 30 | newVer[-1]="$((${newVer[-1]}+1))" 31 | nextVer="${newVer[*]}" 32 | nextVer="${nextVer// /.}" 33 | else 34 | nextVer="v$majorVer.0" 35 | fi 36 | echo "MajorVer=$majorVer LastVer=$lastVer NextVer=$nextVer" 37 | echo "GIT_VERSION=${nextVer//v/}" >> $GITHUB_ENV 38 | - name: Set up QEMU 39 | uses: docker/setup-qemu-action@v2 40 | - name: Build Dockerfile 41 | run: docker build --target deb_make --tag deb_make --file .github/ci/Dockerfile --build-arg GIT_VERSION --build-arg DOCKER_ARCH --build-arg DEBIAN_VERSION --build-arg BUILD_TYPE . 42 | env: 43 | DEBIAN_VERSION: ${{ matrix.debian_version }} 44 | DOCKER_ARCH: ${{ matrix.docker_arch }}/ 45 | BUILD_TYPE: ${{ matrix.build_type }} 46 | - name: Create container 47 | run: docker create --name deb_make deb_make 48 | - name: Copy files 49 | run: 'docker cp deb_make:/deb/. deb/' 50 | - name: 'Release debian files' 51 | uses: ncipollo/release-action@v1 52 | with: 53 | tag: "v${{ env.GIT_VERSION }}" 54 | artifacts: "deb/*.deb" 55 | allowUpdates: true 56 | omitBodyDuringUpdate: true 57 | omitNameDuringUpdate: true 58 | updateOnlyUnreleased: true 59 | draft: true 60 | 61 | publish: 62 | runs-on: ubuntu-latest 63 | permissions: 64 | contents: write 65 | needs: [build] 66 | steps: 67 | - name: Checkout 68 | uses: actions/checkout@v3 69 | with: 70 | fetch-depth: 0 71 | submodules: recursive 72 | - name: Set GIT_VERSION 73 | shell: bash 74 | run: | 75 | majorVer=$(cat VERSION) 76 | lastVer=$(git tag --sort version:refname --list "v$majorVer.*" | tail -n1) 77 | if [[ -n "$lastVer" ]]; then 78 | newVer=(${lastVer//./ }) 79 | newVer[-1]="$((${newVer[-1]}+1))" 80 | nextVer="${newVer[*]}" 81 | nextVer="${nextVer// /.}" 82 | else 83 | nextVer="v$majorVer.0" 84 | fi 85 | echo "MajorVer=$majorVer LastVer=$lastVer NextVer=$nextVer" 86 | echo "GIT_VERSION=${nextVer//v/}" >> $GITHUB_ENV 87 | - name: Generate RELEASE details 88 | run: | 89 | echo "# Release ${{ env.GIT_VERSION }}" > RELEASE.tmp 90 | echo "" >> RELEASE.tmp 91 | lastReleasedVer=$(git tag --sort version:refname --list "v$majorVer.*" | tail -n1) 92 | git log --pretty="- %s (%h)" "$lastReleasedVer..HEAD" >> RELEASE.tmp 93 | echo "" >> RELEASE.tmp 94 | cat RELEASE.md >> RELEASE.tmp 95 | - name: 'Release debian files' 96 | uses: ncipollo/release-action@v1 97 | with: 98 | tag: "v${{ env.GIT_VERSION }}" 99 | allowUpdates: true 100 | updateOnlyUnreleased: true 101 | generateReleaseNotes: true 102 | prerelease: true 103 | bodyFile: RELEASE.tmp 104 | -------------------------------------------------------------------------------- /.github/workflows/build_test.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | paths-ignore: 4 | - '.github/workflows/build_test.yaml' 5 | - 'RELEASE.md' 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | debian_version: [bullseye, bookworm] 15 | docker_arch: [amd64, arm32v7, arm64v8] 16 | build_type: [generic, raspi] 17 | exclude: 18 | - docker_arch: amd64 19 | build_type: raspi 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | submodules: recursive 26 | - name: Set GIT_VERSION 27 | run: echo "GIT_VERSION=$(git describe --tags)" >> $GITHUB_ENV 28 | - name: Set up QEMU 29 | uses: docker/setup-qemu-action@v2 30 | - name: Build Dockerfile 31 | run: docker build --target deb_make --tag deb_make --file .github/ci/Dockerfile --build-arg GIT_VERSION --build-arg DOCKER_ARCH --build-arg DEBIAN_VERSION --build-arg BUILD_TYPE . 32 | env: 33 | DEBIAN_VERSION: ${{ matrix.debian_version }} 34 | DOCKER_ARCH: ${{ matrix.docker_arch }}/ 35 | BUILD_TYPE: ${{ matrix.build_type }} 36 | - name: Create container 37 | run: docker create --name deb_make deb_make 38 | - name: Copy files 39 | run: docker cp deb_make:/deb/. deb/ 40 | - name: 'Upload debian files' 41 | uses: actions/upload-artifact@v3 42 | with: 43 | name: ${{ matrix.debian_version }}-${{ matrix.docker_arch }}-${{ matrix.build_type }}.zip 44 | path: deb/ 45 | retention-days: 14 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | *.o 3 | *.d 4 | *.html.c 5 | *.js.c 6 | /camera-streamer 7 | /test_* 8 | .vscode/ 9 | /Procfile* 10 | /version.h 11 | 12 | /debian/.debhelper 13 | /debian/debhelper-build-stamp 14 | /debian/*.substvars 15 | /debian/files 16 | /debian/*.log 17 | /debian/camera-streamer*/ 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/libdatachannel"] 2 | path = third_party/libdatachannel 3 | url = https://github.com/paullouisageneau/libdatachannel.git 4 | ignore = dirty 5 | [submodule "third_party/magic_enum"] 6 | path = third_party/magic_enum 7 | url = https://github.com/Neargye/magic_enum.git 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${workspaceFolder}/third_party/libdatachannel/include", 8 | "${workspaceFolder}/third_party/libdatachannel/deps/json/include", 9 | "/usr/include/libcamera", 10 | "/usr/include/liveMedia", 11 | "/usr/include/groupsock", 12 | "/usr/include/BasicUsageEnvironment", 13 | "/usr/include/UsageEnvironment" 14 | ], 15 | "defines": [ 16 | "USE_LIBCAMERA=1", 17 | "USE_FFMPEG=1", 18 | "USE_RTSP=1", 19 | "USE_LIBDATACHANNEL=1" 20 | ], 21 | "compilerPath": "/usr/bin/gcc", 22 | "cStandard": "gnu17", 23 | "cppStandard": "gnu++14", 24 | "intelliSenseMode": "linux-gcc-arm64" 25 | } 26 | ], 27 | "version": 4 28 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET := camera-streamer 2 | SRC := $(wildcard **/*.c **/*/*.c **/*.cc **/*/*.cc) 3 | HEADERS := $(wildcard **/*.h **/*/*.h **/*.hh **/*/*.hh) 4 | HTML := $(wildcard html/*.js html/*.html) 5 | 6 | GIT_VERSION ?= $(shell git describe --tags) 7 | GIT_REVISION ?= $(shell git rev-parse --short HEAD) 8 | 9 | CFLAGS := -Werror -Wall -g -I$(CURDIR) -D_GNU_SOURCE 10 | LDLIBS := -lpthread -lstdc++ 11 | 12 | # libdatachannel deprecations on bookworm 13 | # error: 'HMAC_Init_ex' is deprecated: Since OpenSSL 3.0 14 | CFLAGS += -Wno-error=deprecated-declarations 15 | 16 | ifneq (x,x$(shell which ccache)) 17 | CCACHE ?= ccache 18 | endif 19 | 20 | LIBDATACHANNEL_PATH ?= third_party/libdatachannel 21 | 22 | USE_HW_H264 ?= 1 23 | USE_FFMPEG ?= $(shell pkg-config libavutil libavformat libavcodec && echo 1) 24 | USE_LIBCAMERA ?= $(shell pkg-config libcamera && echo 1) 25 | USE_RTSP ?= $(shell pkg-config live555 && echo 1) 26 | USE_LIBDATACHANNEL ?= $(shell [ -e $(LIBDATACHANNEL_PATH)/CMakeLists.txt ] && echo 1) 27 | 28 | ifeq (1,$(DEBUG)) 29 | CFLAGS += -g 30 | endif 31 | 32 | ifeq (1,$(USE_HW_H264)) 33 | CFLAGS += -DUSE_HW_H264 34 | endif 35 | 36 | ifeq (1,$(USE_FFMPEG)) 37 | CFLAGS += -DUSE_FFMPEG 38 | LDLIBS += -lavcodec -lavformat -lavutil 39 | endif 40 | 41 | ifeq (1,$(USE_LIBCAMERA)) 42 | CFLAGS += -DUSE_LIBCAMERA $(shell pkg-config --cflags libcamera) 43 | LDLIBS += $(shell pkg-config --libs libcamera) 44 | endif 45 | 46 | ifeq (1,$(USE_RTSP)) 47 | CFLAGS += -DUSE_RTSP $(shell pkg-config --cflags live555) 48 | LDLIBS += $(shell pkg-config --libs live555) 49 | endif 50 | 51 | ifeq (1,$(USE_LIBDATACHANNEL)) 52 | CFLAGS += -DUSE_LIBDATACHANNEL 53 | CFLAGS += -I$(LIBDATACHANNEL_PATH)/include 54 | CFLAGS += -I$(LIBDATACHANNEL_PATH)/deps/json/include 55 | LDLIBS += -L$(LIBDATACHANNEL_PATH)/build -ldatachannel-static 56 | LDLIBS += -L$(LIBDATACHANNEL_PATH)/build/deps/usrsctp/usrsctplib -lusrsctp 57 | LDLIBS += -L$(LIBDATACHANNEL_PATH)/build/deps/libsrtp -lsrtp2 58 | LDLIBS += -L$(LIBDATACHANNEL_PATH)/build/deps/libjuice -ljuice-static 59 | LDLIBS += -lcrypto -lssl 60 | endif 61 | 62 | HTML_SRC = $(addsuffix .c,$(HTML)) 63 | OBJS = $(patsubst %.cc,%.o,$(patsubst %.c,%.o,$(SRC) $(HTML_SRC))) 64 | TARGET_OBJS = $(filter-out third_party/%, $(filter-out tests/%, $(OBJS))) 65 | 66 | all: version 67 | +make $(TARGET) 68 | 69 | install: version 70 | +make $(TARGET) 71 | install $(TARGET) $(DESTDIR)/usr/local/bin/ 72 | 73 | .SUFFIXES: 74 | 75 | ifeq (1,$(USE_LIBDATACHANNEL)) 76 | camera-streamer: $(LIBDATACHANNEL_PATH)/build/libdatachannel-static.a 77 | endif 78 | 79 | camera-streamer: $(filter-out cmd/%, $(TARGET_OBJS)) $(filter cmd/camera-streamer/%, $(TARGET_OBJS)) 80 | $(CCACHE) $(CXX) $(CFLAGS) -o $@ $^ $(LDLIBS) 81 | 82 | .PHONY: version 83 | version: 84 | echo "#define GIT_VERSION \"$(GIT_VERSION)\"\n#define GIT_REVISION \"$(GIT_REVISION)\"" > version.h.tmp 85 | if $(CCACHE) $(CXX) $(CFLAGS) -o tests/libcamera/orientation.o -c tests/libcamera/orientation.cc 2>/dev/null; then \ 86 | echo "#define LIBCAMERA_USES_ORIENTATION" >> version.h.tmp; \ 87 | else \ 88 | echo "#define LIBCAMERA_USES_TRANSFORM" >> version.h.tmp; \ 89 | fi 90 | diff -u version.h version.h.tmp || mv version.h.tmp version.h 91 | -rm -f version.h.tmp 92 | 93 | clean: 94 | rm -f .depend $(OBJS) $(OBJS:.o=.d) $(HTML_SRC) $(TARGET) version.h 95 | 96 | headers: 97 | find -name '*.h' | xargs -n1 $(CCACHE) $(CC) $(CFLAGS) -std=gnu17 -Wno-error -c -o /dev/null 98 | find -name '*.hh' | xargs -n1 $(CCACHE) $(CXX) $(CFLAGS) -std=c++17 -Wno-error -c -o /dev/null 99 | 100 | -include $(OBJS:.o=.d) 101 | 102 | %.o: %.c 103 | $(CCACHE) $(CC) -std=gnu17 -MMD $(CFLAGS) -c -o $@ $< 104 | 105 | %.o: %.cc 106 | $(CCACHE) $(CXX) -std=c++17 -MMD $(CFLAGS) -c -o $@ $< 107 | 108 | .PRECIOUS: html/%.c 109 | html/%.c: html/% 110 | xxd -i $< > $@.tmp 111 | mv $@.tmp $@ 112 | 113 | $(LIBDATACHANNEL_PATH)/build/libdatachannel-static.a: $(LIBDATACHANNEL_PATH) 114 | [ -e $ Use `main` branch for semi-stable changes, or `develop` for experimental changes. 4 | 5 | There's a number of great projects doing an UVC/CSI camera streaming 6 | on SBC (like Raspberry PI's). 7 | 8 | This is yet another **camera-streamer** project that is primarly focused 9 | on supporting a fully hardware accelerated streaming of MJPEG streams 10 | and H264 video streams for minimal latency. 11 | 12 | This supports well CSI cameras that provide 10-bit Bayer packed format 13 | from sensor, by using a dedicated ISP of Raspberry PI's. 14 | 15 | Take into account that this is a draft project, and is nowhere as complete 16 | and well supported as [awesome ustreamer](https://github.com/pikvm/ustreamer). 17 | This project was inspired by mentioned ustreamer. 18 | 19 | ## Requirements 20 | 21 | 1. Debian Bullseye, with at least 5.15 kernel 22 | 1. Best: Raspberry PI for hardware acceleration 23 | 24 | ## Install 25 | 26 | 1. [Use precompiled debian package](https://github.com/ayufan/camera-streamer/releases/latest) (recommended) 27 | 2. [Compile manually](docs/install-manual.md) (advanced) 28 | 29 | ## Configure 30 | 31 | 1. [Configure resolution, brightness or image quality](docs/configure.md) 32 | 1. [See different streaming options](docs/streaming.md) 33 | 1. [See example configurations](service/) 34 | 35 | ## Advanced 36 | 37 | This section contains some advanced explanations that are not complete and might be outdated: 38 | 39 | 1. [High-performance mode via ISP for CSI](docs/v4l2-isp-mode.md) 40 | 1. [High-performance mode via direct decoding for USB](docs/v4l2-usb-mode.md) 41 | 1. [High-compatibility via `libcamera` on Raspberry PI](docs/raspi-libcamera.md) 42 | 1. [Performance analysis](docs/performance-analysis.md) 43 | 44 | ## License 45 | 46 | GNU General Public License v3.0 47 | 48 | ## References 49 | 50 | This project uses: 51 | 52 | - [WebRTC library libdatachannel](https://github.com/paullouisageneau/libdatachannel) 53 | - [Raspberry PI libcamera](https://github.com/raspberrypi/libcamera) 54 | - [RTSP live555](http://www.live555.com) 55 | - [FFmpeg](https://ffmpeg.org/) 56 | - [C++ helper magic_enum](https://github.com/Neargye/magic_enum) 57 | - [C++ JSON nlohmann](https://github.com/nlohmann/json) 58 | - [HTML from ESP32 Cam WebServer](https://github.com/easytarget/esp32-cam-webserver) 59 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Variants 2 | 3 | Download correct version for your platform: 4 | 5 | - Variant: **raspi**: Raspberry PI compatible build with USB, CSI, WebRTC, RTSP support 6 | - Variant: **generic**: All other platforms with USB and MJPEG support only for time being 7 | - System: **bullseye**: Debian Bullseye (11) compatible build 8 | - System: **bookworm**: Debian Bookworm (12) compatible build 9 | - Platform: **amd64**: x86/64 compatible build 10 | - Platform: **arm32**: ARM 32-bit kernel: PIs 0.2W, 2B, and higher, Orange PIs, Rock64, etc. No support for RPI0. 11 | - Platform: **arm64**: ARM 64-bit kernel: PIs 0.2W, 3B, and higher, Orange PIs, Rock64, etc. No support for RPI0 and RPI2B. 12 | 13 | ## Install on Raspberry PI or any other platform 14 | 15 | Copy the below and paste into terminal: 16 | 17 | ```bash 18 | PACKAGE=camera-streamer-$(test -e /etc/default/raspberrypi-kernel && echo raspi || echo generic)_#{GIT_VERSION}.$(. /etc/os-release; echo $VERSION_CODENAME)_$(dpkg --print-architecture).deb 19 | wget "https://github.com/ayufan/camera-streamer/releases/download/v#{GIT_VERSION}/$PACKAGE" 20 | sudo apt install "$PWD/$PACKAGE" 21 | ``` 22 | 23 | Enable one of provided systemd configuration: 24 | 25 | ```bash 26 | ls -al /usr/share/camera-streamer/examples/ 27 | systemctl enable /usr/share/camera-streamer/examples/camera-streamer-raspi-v3-12MP.service 28 | systemctl start camera-streamer-raspi-v3-12MP 29 | ``` 30 | 31 | You can also copy an existing service and fine tune it: 32 | 33 | ```bash 34 | cp /usr/share/camera-streamer/examples/camera-streamer-raspi-v3-12MP.service /etc/systemd/system/camera-streamer.service 35 | edit /etc/systemd/system/camera-streamer.service 36 | systemctl enable camera-streamer 37 | systemctl start camera-streamer 38 | ``` 39 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.2 2 | -------------------------------------------------------------------------------- /cmd/camera-streamer/http.c: -------------------------------------------------------------------------------- 1 | #include "util/http/http.h" 2 | #include "output/webrtc/webrtc.h" 3 | #include "device/camera/camera.h" 4 | #include "output/output.h" 5 | 6 | extern unsigned char html_index_html[]; 7 | extern unsigned int html_index_html_len; 8 | extern unsigned char html_webrtc_html[]; 9 | extern unsigned int html_webrtc_html_len; 10 | extern unsigned char html_control_html[]; 11 | extern unsigned int html_control_html_len; 12 | extern camera_t *camera; 13 | 14 | extern void camera_status_json(http_worker_t *worker, FILE *stream); 15 | 16 | static void http_once(FILE *stream, void (*fn)(FILE *stream, const char *data), void *headersp) 17 | { 18 | bool *headers = headersp; 19 | 20 | if (!*headers) { 21 | fn(stream, ""); 22 | *headers = true; 23 | } 24 | } 25 | 26 | static void camera_post_option(http_worker_t *worker, FILE *stream) 27 | { 28 | char *device_name = http_get_param(worker, "device"); 29 | char *key = http_get_param(worker, "key"); 30 | char *value = http_get_param(worker, "value"); 31 | 32 | if (!key || !value) { 33 | http_400(stream, ""); 34 | fprintf(stream, "No key or value passed.\r\n"); 35 | goto cleanup; 36 | } 37 | 38 | bool found = false; 39 | 40 | for (int i = 0; i < MAX_DEVICES; i++) { 41 | device_t *dev = camera->devices[i]; 42 | if (!dev) { 43 | continue; 44 | } 45 | 46 | if (device_name && strcmp(dev->name, device_name)) { 47 | continue; 48 | } 49 | 50 | int ret = device_set_option_string(dev, key, value); 51 | if (ret > 0) { 52 | http_once(stream, http_200, &found); 53 | fprintf(stream, "%s: The '%s' was set to '%s'.\r\n", dev->name, key, value); 54 | } else if (ret < 0) { 55 | http_once(stream, http_500, &found); 56 | fprintf(stream, "%s: Cannot set '%s' to '%s'.\r\n", dev->name, key, value); 57 | } 58 | } 59 | 60 | if (!found) { 61 | http_once(stream, http_404, &found); 62 | fprintf(stream, "The option was not found for device='%s', key='%s', value='%s'.\r\n", 63 | device_name, key, value); 64 | } 65 | 66 | cleanup: 67 | free(device_name); 68 | free(key); 69 | free(value); 70 | } 71 | 72 | static void http_cors_options(http_worker_t *worker, FILE *stream) 73 | { 74 | fprintf(stream, "HTTP/1.1 204 No Data\r\n"); 75 | fprintf(stream, "Access-Control-Allow-Origin: *\r\n"); 76 | fprintf(stream, "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n"); 77 | fprintf(stream, "Access-Control-Allow-Headers: Content-Type\r\n"); 78 | fprintf(stream, "\r\n"); 79 | } 80 | 81 | http_method_t http_methods[] = { 82 | { "GET", "/snapshot", http_snapshot }, 83 | { "GET", "/snapshot.jpg", http_snapshot }, 84 | { "GET", "/stream", http_stream }, 85 | { "GET", "/?action=snapshot", http_snapshot }, 86 | { "GET", "/?action=stream", http_stream }, 87 | { "GET", "/video", http_detect_video }, 88 | { "GET", "/video.m3u8", http_m3u8_video }, 89 | { "GET", "/video.h264", http_h264_video }, 90 | { "GET", "/video.mkv", http_mkv_video }, 91 | { "GET", "/video.mp4", http_mp4_video }, 92 | { "GET", "/webrtc", http_content, "text/html", html_webrtc_html, 0, &html_webrtc_html_len }, 93 | { "POST", "/webrtc", http_webrtc_offer }, 94 | { "GET", "/control", http_content, "text/html", html_control_html, 0, &html_control_html_len }, 95 | { "GET", "/option", camera_post_option }, 96 | { "POST", "/option", camera_post_option }, 97 | { "GET", "/status", camera_status_json }, 98 | { "GET", "/", http_content, "text/html", html_index_html, 0, &html_index_html_len }, 99 | { "OPTIONS", "*/", http_cors_options }, 100 | { } 101 | }; 102 | -------------------------------------------------------------------------------- /cmd/camera-streamer/main.c: -------------------------------------------------------------------------------- 1 | #include "util/http/http.h" 2 | #include "util/opts/opts.h" 3 | #include "util/opts/log.h" 4 | #include "device/camera/camera.h" 5 | #include "output/rtsp/rtsp.h" 6 | #include "output/webrtc/webrtc.h" 7 | #include "version.h" 8 | 9 | #include 10 | #include 11 | 12 | extern option_t all_options[]; 13 | extern camera_options_t camera_options; 14 | extern http_server_options_t http_options; 15 | extern http_method_t http_methods[]; 16 | extern rtsp_options_t rtsp_options; 17 | extern webrtc_options_t webrtc_options; 18 | 19 | camera_t *camera; 20 | 21 | void deprecations() 22 | { 23 | if (camera_options.high_res_factor > 0) { 24 | printf("Using deprecated `-camera-high_res_factor`. Use `-camera-snapshot.height` instead."); 25 | 26 | if (!camera_options.snapshot.height) 27 | camera_options.snapshot.height = camera_options.height / camera_options.high_res_factor; 28 | } 29 | if (camera_options.low_res_factor > 0) { 30 | printf("Using deprecated `-camera-low_res_factor`. Use `-camera-stream.height` or `-camera-video.height` instead."); 31 | 32 | if (!camera_options.stream.height) 33 | camera_options.stream.height = camera_options.height / camera_options.low_res_factor; 34 | if (!camera_options.video.height) 35 | camera_options.video.height = camera_options.height / camera_options.low_res_factor; 36 | } 37 | } 38 | 39 | void inherit() 40 | { 41 | if (!camera_options.snapshot.height || camera_options.snapshot.height > camera_options.height) 42 | camera_options.snapshot.height = camera_options.height; 43 | 44 | if (!camera_options.video.height || camera_options.video.height > camera_options.snapshot.height) 45 | camera_options.video.height = camera_options.snapshot.height; 46 | 47 | if (!camera_options.stream.height || camera_options.stream.height > camera_options.video.height) 48 | camera_options.stream.height = camera_options.video.height; 49 | } 50 | 51 | int main(int argc, char *argv[]) 52 | { 53 | int http_fd = -1; 54 | int ret = -1; 55 | 56 | if (parse_opts(all_options, argc, argv) < 0) { 57 | return -1; 58 | } 59 | 60 | printf("%s Version: %s (%s)\n", argv[0], GIT_VERSION, GIT_REVISION); 61 | 62 | deprecations(); 63 | inherit(); 64 | 65 | if (camera_options.list_options) { 66 | camera = camera_open(&camera_options); 67 | if (camera) { 68 | printf("\n"); 69 | for (int i = 0; i < MAX_DEVICES; i++) { 70 | device_dump_options(camera->devices[i], stdout); 71 | } 72 | camera_close(&camera); 73 | } 74 | return -1; 75 | } 76 | 77 | http_fd = http_server(&http_options, http_methods); 78 | if (http_fd < 0) { 79 | goto error; 80 | } 81 | 82 | if (rtsp_options.port > 0 && rtsp_server(&rtsp_options) < 0) { 83 | goto error; 84 | } 85 | 86 | if (!webrtc_options.disabled && webrtc_server(&webrtc_options) < 0) { 87 | goto error; 88 | } 89 | 90 | while (true) { 91 | camera = camera_open(&camera_options); 92 | if (camera) { 93 | ret = camera_run(camera); 94 | camera_close(&camera); 95 | } 96 | 97 | if (camera_options.auto_reconnect > 0) { 98 | LOG_INFO(NULL, "Automatically reconnecting in %d seconds...", camera_options.auto_reconnect); 99 | sleep(camera_options.auto_reconnect); 100 | } else { 101 | break; 102 | } 103 | } 104 | 105 | error: 106 | close(http_fd); 107 | return ret; 108 | } 109 | -------------------------------------------------------------------------------- /cmd/camera-streamer/opts.c: -------------------------------------------------------------------------------- 1 | #include "util/http/http.h" 2 | #include "util/opts/opts.h" 3 | #include "util/opts/log.h" 4 | #include "util/opts/fourcc.h" 5 | #include "device/camera/camera.h" 6 | #include "output/rtsp/rtsp.h" 7 | #include "output/webrtc/webrtc.h" 8 | #include "output/output.h" 9 | 10 | camera_options_t camera_options = { 11 | .path = "", 12 | .width = 1920, 13 | .height = 1080, 14 | .format = 0, 15 | .nbufs = 3, 16 | .fps = 30, 17 | .allow_dma = true, 18 | .high_res_factor = 0.0, 19 | .low_res_factor = 0.0, 20 | .auto_reconnect = 0, 21 | .auto_focus = true, 22 | .options = "", 23 | .list_options = false, 24 | .snapshot = { 25 | .options = "compression_quality=80" 26 | }, 27 | .stream = { 28 | .options = "compression_quality=80" 29 | }, 30 | .video = { 31 | #ifdef USE_HW_H264 32 | .disabled = 0, 33 | #else // USE_HW_H264 34 | .disabled = 1, 35 | #endif 36 | .options = 37 | "video_bitrate_mode=0" OPTION_VALUE_LIST_SEP 38 | "video_bitrate=2000000" OPTION_VALUE_LIST_SEP 39 | "repeat_sequence_header=5000000" OPTION_VALUE_LIST_SEP 40 | "h264_i_frame_period=30" OPTION_VALUE_LIST_SEP 41 | "h264_level=4" OPTION_VALUE_LIST_SEP 42 | "h264_profile=high" OPTION_VALUE_LIST_SEP 43 | "h264_minimum_qp_value=16" OPTION_VALUE_LIST_SEP 44 | "h264_maximum_qp_value=32" 45 | } 46 | }; 47 | 48 | http_server_options_t http_options = { 49 | .listen = "127.0.0.1", 50 | .port = 8080, 51 | .maxcons = 10 52 | }; 53 | 54 | log_options_t log_options = { 55 | .debug = false, 56 | .verbose = false 57 | }; 58 | 59 | rtsp_options_t rtsp_options = { 60 | .port = 0, 61 | .allow_truncated = false 62 | }; 63 | 64 | webrtc_options_t webrtc_options = { 65 | }; 66 | 67 | option_value_t camera_formats[] = { 68 | { "DEFAULT", 0 }, 69 | { "YUYV", V4L2_PIX_FMT_YUYV }, 70 | { "YUV420", V4L2_PIX_FMT_YUV420 }, 71 | { "YUYV", V4L2_PIX_FMT_YUYV }, 72 | { "NV12", V4L2_PIX_FMT_NV12 }, 73 | { "NV21", V4L2_PIX_FMT_NV21 }, 74 | { "MJPG", V4L2_PIX_FMT_MJPEG }, 75 | { "MJPEG", V4L2_PIX_FMT_MJPEG }, 76 | { "JPEG", V4L2_PIX_FMT_MJPEG }, 77 | { "H264", V4L2_PIX_FMT_H264 }, 78 | { "RG10", V4L2_PIX_FMT_SRGGB10 }, 79 | { "GB10P", V4L2_PIX_FMT_SGRBG10P }, 80 | { "RG10P", V4L2_PIX_FMT_SRGGB10P }, 81 | { "BG10P", V4L2_PIX_FMT_SBGGR10P }, 82 | { "RGB565", V4L2_PIX_FMT_RGB565 }, 83 | { "RGBP", V4L2_PIX_FMT_RGB565 }, 84 | { "RGB24", V4L2_PIX_FMT_RGB24 }, 85 | { "RGB", V4L2_PIX_FMT_RGB24 }, 86 | { "BGR", V4L2_PIX_FMT_BGR24 }, 87 | {} 88 | }; 89 | 90 | option_value_t camera_type[] = { 91 | { "v4l2", CAMERA_V4L2 }, 92 | { "libcamera", CAMERA_LIBCAMERA }, 93 | { "dummy", CAMERA_DUMMY }, 94 | {} 95 | }; 96 | 97 | option_t all_options[] = { 98 | DEFINE_OPTION_PTR(camera, path, string, "Chooses the camera to use. If empty connect to default."), 99 | DEFINE_OPTION_VALUES(camera, type, camera_type, "Select camera type."), 100 | DEFINE_OPTION(camera, width, uint, "Set the camera capture width."), 101 | DEFINE_OPTION(camera, height, uint, "Set the camera capture height."), 102 | DEFINE_OPTION_VALUES(camera, format, camera_formats, "Set the camera capture format."), 103 | DEFINE_OPTION(camera, nbufs, uint, "Set number of capture buffers. Preferred 2 or 3."), 104 | DEFINE_OPTION(camera, fps, uint, "Set the desired capture framerate."), 105 | DEFINE_OPTION_DEFAULT(camera, allow_dma, bool, "1", "Prefer to use DMA access to reduce memory copy."), 106 | DEFINE_OPTION(camera, high_res_factor, float, "Set the desired high resolution output scale factor."), 107 | DEFINE_OPTION(camera, low_res_factor, float, "Set the desired low resolution output scale factor."), 108 | DEFINE_OPTION_PTR(camera, options, list, "Set the camera options. List all available options with `-camera-list_options`."), 109 | DEFINE_OPTION(camera, auto_reconnect, uint, "Set the camera auto-reconnect delay in seconds."), 110 | DEFINE_OPTION_DEFAULT(camera, auto_focus, bool, "1", "Do auto-focus on start-up (does not work with all camera)."), 111 | DEFINE_OPTION_DEFAULT(camera, force_active, bool, "1", "Force camera to be always active."), 112 | DEFINE_OPTION_DEFAULT(camera, vflip, bool, "1", "Do vertical image flip (does not work with all camera)."), 113 | DEFINE_OPTION_DEFAULT(camera, hflip, bool, "1", "Do horizontal image flip (does not work with all camera)."), 114 | 115 | DEFINE_OPTION_PTR(camera, isp.options, list, "Set the ISP processing options. List all available options with `-camera-list_options`."), 116 | 117 | DEFINE_OPTION_PTR(camera, snapshot.options, list, "Set the JPEG compression options. List all available options with `-camera-list_options`."), 118 | DEFINE_OPTION(camera, snapshot.height, uint, "Override the snapshot height and maintain aspect ratio."), 119 | 120 | DEFINE_OPTION_DEFAULT(camera, stream.disabled, bool, "1", "Disable stream."), 121 | DEFINE_OPTION_PTR(camera, stream.options, list, "Set the JPEG compression options. List all available options with `-camera-list_options`."), 122 | DEFINE_OPTION(camera, stream.height, uint, "Override the stream height and maintain aspect ratio."), 123 | 124 | DEFINE_OPTION_DEFAULT(camera, video.disabled, bool, "1", "Disable video."), 125 | DEFINE_OPTION_PTR(camera, video.options, list, "Set the H264 encoding options. List all available options with `-camera-list_options`."), 126 | DEFINE_OPTION(camera, video.height, uint, "Override the video height and maintain aspect ratio."), 127 | 128 | DEFINE_OPTION_DEFAULT(camera, list_options, bool, "1", "List all available options and exit."), 129 | 130 | DEFINE_OPTION_PTR(http, listen, string, "Set the IP address the HTTP web-server will bind to. Set to 0.0.0.0 to listen on all interfaces."), 131 | DEFINE_OPTION(http, port, uint, "Set the HTTP web-server port."), 132 | DEFINE_OPTION(http, maxcons, uint, "Set maximum number of concurrent HTTP connections."), 133 | 134 | DEFINE_OPTION_DEFAULT(rtsp, port, uint, "8554", "Set the RTSP server port (default: 8854)."), 135 | 136 | DEFINE_OPTION_PTR(webrtc, ice_servers, list, "Specify ICE servers: [(stun|turn|turns)(:|://)][username:password@]hostname[:port][?transport=udp|tcp|tls)]."), 137 | DEFINE_OPTION_DEFAULT(webrtc, disable_client_ice, bool, "1", "Ignore ICE servers provided in '/webrtc' request."), 138 | 139 | DEFINE_OPTION_DEFAULT(log, debug, bool, "1", "Enable debug logging."), 140 | DEFINE_OPTION_DEFAULT(log, verbose, bool, "1", "Enable verbose logging."), 141 | DEFINE_OPTION_DEFAULT(log, stats, uint, "1", "Print statistics every duration."), 142 | DEFINE_OPTION_PTR(log, filter, list, "Enable debug logging from the given files. Ex.: `-log-filter=buffer.cc`"), 143 | 144 | {} 145 | }; 146 | -------------------------------------------------------------------------------- /cmd/list-devices/main.c: -------------------------------------------------------------------------------- 1 | #include "util/opts/log.h" 2 | #include "util/opts/fourcc.h" 3 | #include "device/device.h" 4 | #include "device/device_list.h" 5 | 6 | #include 7 | #include 8 | 9 | log_options_t log_options = { 10 | .debug = true, 11 | .verbose = true 12 | }; 13 | 14 | void print_formats(const char *type, device_info_formats_t *formats) 15 | { 16 | if (!formats->n) { 17 | return; 18 | } 19 | 20 | printf("- %s: ", type); 21 | for (int j = 0; j < formats->n; j++) { 22 | printf("%s ", fourcc_to_string(formats->formats[j]).buf); 23 | } 24 | printf("\n"); 25 | } 26 | 27 | int main(int argc, const char *argv[]) 28 | { 29 | device_list_t *list = device_list_v4l2(); 30 | 31 | printf("Found %d devices\n", list->ndevices); 32 | 33 | for (int i = 0; i < list->ndevices; i++) { 34 | device_info_t * info = &list->devices[i]; 35 | 36 | printf("Device %d: %s / %s / camera: %d, m2m: %d\n", i, info->name, info->path, info->camera, info->m2m); 37 | print_formats("Output / IN", &info->output_formats); 38 | print_formats("Capture / OUT", &info->capture_formats); 39 | } 40 | 41 | device_list_free(list); 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /debian/camera-streamer-generic.install: -------------------------------------------------------------------------------- 1 | camera-streamer usr/bin/ 2 | service/camera-streamer-generic-usb-cam.service /usr/share/camera-streamer/examples/ 3 | tools/dump_cameras.sh /usr/share/camera-streamer/ 4 | -------------------------------------------------------------------------------- /debian/camera-streamer-raspi.install: -------------------------------------------------------------------------------- 1 | camera-streamer usr/bin/ 2 | service/*.service /usr/share/camera-streamer/examples/ 3 | tools/dump_cameras.sh /usr/share/camera-streamer/ 4 | tools/rpi_measure.sh /usr/share/camera-streamer/ 5 | tools/rpi_mem_usage.sh /usr/share/camera-streamer/ 6 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | camera-streamer (0.1.0) unstable; urgency=medium 2 | 3 | * Initial Release. 4 | 5 | -- Kamil Trzciński Fri, 26 May 2023 13:15:22 +0200 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: camera-streamer 2 | Section: unknown 3 | Priority: optional 4 | Maintainer: Kamil Trzciński 5 | Build-Depends: 6 | debhelper, 7 | libavformat-dev, 8 | libavutil-dev, 9 | libavcodec-dev, 10 | libcamera-dev , 11 | liblivemedia-dev , 12 | v4l-utils, 13 | pkg-config, 14 | xxd, 15 | build-essential, 16 | cmake, 17 | libssl-dev 18 | Standards-Version: 4.5.1 19 | Homepage: https://github.com/ayufan/camera-streamer 20 | Vcs-Browser: https://github.com/ayufan/camera-streamer 21 | Vcs-Git: https://github.com/ayufan/camera-streamer.git 22 | Rules-Requires-Root: no 23 | 24 | Package: camera-streamer-raspi 25 | Provides: camera-streamer 26 | Breaks: camera-streamer (<< 0.2) 27 | Conflicts: camera-streamer (<< 0.2) 28 | Replaces: camera-streamer (<< 0.2) 29 | Build-Profiles: 30 | Architecture: any 31 | Depends: ${shlibs:Depends}, ${misc:Depends} 32 | Description: camera-streamer with CSI and USB camera, 33 | RTSP, WebRTC, and H264 support 34 | 35 | Package: camera-streamer-generic 36 | Provides: camera-streamer 37 | Build-Profiles: 38 | Architecture: any 39 | Depends: ${shlibs:Depends}, ${misc:Depends} 40 | Description: camera-streamer with USB camera support 41 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | # output every command that modifies files on the build system. 4 | #export DH_VERBOSE = 1 5 | 6 | 7 | # see FEATURE AREAS in dpkg-buildflags(1) 8 | #export DEB_BUILD_MAINT_OPTIONS = hardening=+all 9 | 10 | # see ENVIRONMENT in dpkg-buildflags(1) 11 | # package maintainers to append CFLAGS 12 | #export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic 13 | # package maintainers to append LDFLAGS 14 | #export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed 15 | 16 | export USE_FFMPEG ?= 1 17 | #export GIT_VERSION ?= $(shell git describe --tags) 18 | export DEB_VERSION ?= $(GIT_VERSION:v%=%)$(addprefix ~,$(RELEASE_SUFFIX)) 19 | 20 | ifneq ($(filter raspi,$(DEB_BUILD_PROFILES)),) 21 | export USE_HW_H264 = 1 22 | export USE_LIBDATACHANNEL = 1 23 | export USE_LIBCAMERA = 1 24 | export USE_RTSP = 1 25 | else ifneq ($(filter generic,$(DEB_BUILD_PROFILES)),) 26 | export USE_HW_H264 = 0 27 | export USE_LIBCAMERA = 0 28 | else 29 | $(error Use DEB_BUILD_PROFILES=raspi or DEB_BUILD_PROFILES=generic) 30 | endif 31 | 32 | %: 33 | dh $@ 34 | 35 | .PHONY: override_dh_auto_install 36 | override_dh_auto_install: 37 | 38 | .PHONY: override_dh_gencontrol 39 | override_dh_gencontrol: 40 | dh_gencontrol -- $(addprefix -v,$(DEB_VERSION)) 41 | 42 | .PHONY: override_dh_strip 43 | override_dh_strip: 44 | dh_strip --keep-debug 45 | 46 | .PHONY: override_dh_shlibdeps 47 | override_dh_shlibdeps: 48 | dh_shlibdeps 49 | 50 | # Make libcamera0 to be exact 51 | sed -i "s/libcamera0 (>=[^)]*)/libcamera0 (= $$(dpkg -s libcamera0 | grep Version | cut -d" " -f2))/g" debian/camera-streamer*.substvars 52 | 53 | .PHONY: override_dh_install 54 | override_dh_install: 55 | dh_install 56 | sed -i "s|/usr/local/bin/camera-streamer|/usr/bin/camera-streamer|g" debian/camera-streamer*/usr/share/camera-streamer/examples/*.service 57 | 58 | # dh_make generated override targets 59 | # This is example for Cmake (See https://bugs.debian.org/641051 ) 60 | #override_dh_auto_configure: 61 | # dh_auto_configure -- \ 62 | # -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) 63 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /device/buffer.c: -------------------------------------------------------------------------------- 1 | #include "device/buffer.h" 2 | #include "device/buffer_list.h" 3 | #include "device/device.h" 4 | #include "util/opts/log.h" 5 | 6 | buffer_t *buffer_open(const char *name, buffer_list_t *buf_list, int index) { 7 | buffer_t *buf = calloc(1, sizeof(buffer_t)); 8 | device_t *dev = buf_list->dev; 9 | 10 | buf->name = strdup(name); 11 | buf->index = index; 12 | buf->buf_list = buf_list; 13 | buf->dma_fd = -1; 14 | buf->mmap_reflinks = 1; 15 | buf->used = 0; 16 | 17 | if (dev->hw->buffer_open(buf) < 0) { 18 | goto error; 19 | } 20 | 21 | return buf; 22 | 23 | error: 24 | buffer_close(buf); 25 | return NULL; 26 | } 27 | 28 | void buffer_close(buffer_t *buf) 29 | { 30 | if (buf == NULL) { 31 | return; 32 | } 33 | 34 | buf->buf_list->dev->hw->buffer_close(buf); 35 | free(buf->name); 36 | free(buf); 37 | } 38 | -------------------------------------------------------------------------------- /device/buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct buffer_s buffer_t; 8 | typedef struct buffer_list_s buffer_list_t; 9 | 10 | typedef struct buffer_s { 11 | char *name; 12 | struct buffer_list_s *buf_list; 13 | int index; 14 | 15 | // Mapped memory (with DMA) 16 | void *start; 17 | size_t used; 18 | size_t length; 19 | int dma_fd; 20 | 21 | struct { 22 | bool is_keyed : 1; 23 | bool is_keyframe : 1; 24 | bool is_last : 1; 25 | } flags; 26 | 27 | union { 28 | struct buffer_v4l2_s *v4l2; 29 | struct buffer_dummy_s *dummy; 30 | struct buffer_libcamera_s *libcamera; 31 | }; 32 | 33 | // State 34 | int mmap_reflinks; 35 | buffer_t *dma_source; 36 | bool enqueued; 37 | uint64_t enqueue_time_us, captured_time_us; 38 | } buffer_t; 39 | 40 | buffer_t *buffer_open(const char *name, buffer_list_t *buf_list, int buffer); 41 | void buffer_close(buffer_t *buf); 42 | 43 | bool buffer_use(buffer_t *buf); 44 | bool buffer_consumed(buffer_t *buf, const char *who); 45 | -------------------------------------------------------------------------------- /device/buffer_list.c: -------------------------------------------------------------------------------- 1 | #include "device/buffer.h" 2 | #include "device/buffer_list.h" 3 | #include "device/device.h" 4 | #include "util/opts/log.h" 5 | #include "util/opts/fourcc.h" 6 | 7 | static int buffer_list_alloc_buffers2(buffer_list_t *buf_list, int got_bufs) 8 | { 9 | if (buf_list->bufs || got_bufs <= 0) { 10 | return -1; 11 | } 12 | 13 | LOG_INFO( 14 | buf_list, 15 | "Using: %ux%u/%s, buffers=%d, bytesperline=%d, sizeimage=%.1fMiB", 16 | buf_list->fmt.width, 17 | buf_list->fmt.height, 18 | fourcc_to_string(buf_list->fmt.format).buf, 19 | got_bufs, 20 | buf_list->fmt.bytesperline, 21 | buf_list->fmt.sizeimage / 1024.0f / 1024.0f 22 | ); 23 | 24 | buf_list->bufs = calloc(got_bufs, sizeof(buffer_t*)); 25 | buf_list->fmt.nbufs = got_bufs; 26 | buf_list->nbufs = got_bufs; 27 | 28 | unsigned mem_used = 0; 29 | 30 | for (unsigned i = 0; i < buf_list->nbufs; i++) { 31 | char name[64]; 32 | sprintf(name, "%s:buf%d", buf_list->name, i); 33 | buffer_t *buf = buffer_open(name, buf_list, i); 34 | if (!buf) { 35 | LOG_ERROR(buf_list, "Cannot open buffer: %u", i); 36 | goto error; 37 | } 38 | 39 | if (buf->dma_fd >= 0) { 40 | mem_used += buf->length; 41 | } 42 | 43 | buf_list->bufs[i] = buf; 44 | } 45 | 46 | LOG_INFO(buf_list, "Opened %u buffers. Memory used: %.1f MiB", buf_list->nbufs, mem_used / 1024.0f / 1024.0f); 47 | return 0; 48 | 49 | error: 50 | buffer_list_free_buffers(buf_list); 51 | return -1; 52 | } 53 | 54 | buffer_list_t *buffer_list_open(const char *name, int index, struct device_s *dev, const char *path, buffer_format_t fmt, bool do_capture, bool do_mmap) 55 | { 56 | buffer_list_t *buf_list = calloc(1, sizeof(buffer_list_t)); 57 | 58 | buf_list->dev = dev; 59 | buf_list->name = strdup(name); 60 | if (path) { 61 | buf_list->path = strdup(path); 62 | } 63 | buf_list->do_capture = do_capture; 64 | buf_list->do_mmap = do_mmap; 65 | buf_list->fmt = fmt; 66 | buf_list->index = index; 67 | 68 | int err = dev->hw->buffer_list_open(buf_list); 69 | if (err > 0) { 70 | err = buffer_list_alloc_buffers2(buf_list, err); 71 | } 72 | 73 | if (err < 0) { 74 | goto error; 75 | } 76 | 77 | return buf_list; 78 | 79 | error: 80 | buffer_list_close(buf_list); 81 | return NULL; 82 | } 83 | 84 | int buffer_list_alloc_buffers(buffer_list_t *buf_list) 85 | { 86 | if (buf_list->bufs) { 87 | return 0; 88 | } 89 | if (!buf_list->dev->hw->buffer_list_alloc_buffers) { 90 | return -1; 91 | } 92 | 93 | int got_bufs = buf_list->dev->hw->buffer_list_alloc_buffers(buf_list); 94 | if (got_bufs < 0) { 95 | return -1; 96 | } 97 | 98 | return buffer_list_alloc_buffers2(buf_list, got_bufs); 99 | } 100 | 101 | void buffer_list_free_buffers(buffer_list_t *buf_list) 102 | { 103 | if (!buf_list->bufs) { 104 | return; 105 | } 106 | 107 | for (int i = 0; i < buf_list->nbufs; i++) { 108 | buffer_close(buf_list->bufs[i]); 109 | } 110 | free(buf_list->bufs); 111 | buf_list->bufs = NULL; 112 | buf_list->nbufs = 0; 113 | 114 | if (buf_list->dev->hw->buffer_list_free_buffers) { 115 | buf_list->dev->hw->buffer_list_free_buffers(buf_list); 116 | } 117 | } 118 | 119 | void buffer_list_close(buffer_list_t *buf_list) 120 | { 121 | if (!buf_list) { 122 | return; 123 | } 124 | 125 | buffer_list_free_buffers(buf_list); 126 | 127 | buf_list->dev->hw->buffer_list_close(buf_list); 128 | free(buf_list->name); 129 | free(buf_list); 130 | } 131 | 132 | int buffer_list_set_stream(buffer_list_t *buf_list, bool do_on) 133 | { 134 | if (!buf_list || !buf_list->dev->hw->buffer_list_set_stream) { 135 | return -1; 136 | } 137 | 138 | if (buf_list->streaming == do_on) { 139 | return 0; 140 | } 141 | 142 | if (buf_list->dev->hw->buffer_list_set_stream(buf_list, do_on) < 0) { 143 | goto error; 144 | } 145 | buf_list->streaming = do_on; 146 | 147 | if (do_on) { 148 | buf_list->last_enqueued_us = get_monotonic_time_us(NULL, NULL); 149 | } else { 150 | buffer_list_clear_queue(buf_list); 151 | } 152 | 153 | int enqueued = buffer_list_count_enqueued(buf_list); 154 | LOG_INFO(buf_list, "Streaming %s... Was %d of %d enqueud", do_on ? "started" : "stopped", enqueued, buf_list->nbufs); 155 | return 0; 156 | 157 | error: 158 | return -1; 159 | } 160 | -------------------------------------------------------------------------------- /device/buffer_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct buffer_s buffer_t; 7 | typedef struct device_s device_t; 8 | struct pollfd; 9 | 10 | typedef enum { 11 | BUFFER_TYPE_DEFAULT = 0, 12 | 13 | BUFFER_TYPE_RAW = 1, 14 | BUFFER_TYPE_IMAGE = 2, 15 | BUFFER_TYPE_VIDEO = 3 16 | } buffer_type_t; 17 | 18 | typedef struct buffer_format_s { 19 | unsigned width, height, format, bytesperline, sizeimage; 20 | unsigned nbufs; 21 | unsigned interval_us; 22 | buffer_type_t type; 23 | } buffer_format_t; 24 | 25 | typedef struct buffer_stats_s { 26 | int frames, dropped; 27 | 28 | int frames_since_reset; 29 | uint64_t max_dequeued_us; 30 | float avg_dequeued_us; 31 | float stddev_dequeued_us; 32 | } buffer_stats_t; 33 | 34 | #define MAX_BUFFER_QUEUE 4 35 | 36 | typedef struct buffer_list_s { 37 | char *name; 38 | char *path; 39 | device_t *dev; 40 | buffer_t **bufs; 41 | int nbufs; 42 | int index; 43 | 44 | buffer_format_t fmt; 45 | bool do_mmap, do_capture, do_timestamps; 46 | 47 | union { 48 | struct buffer_list_v4l2_s *v4l2; 49 | struct buffer_list_dummy_s *dummy; 50 | struct buffer_list_libcamera_s *libcamera; 51 | }; 52 | 53 | buffer_t *queued_bufs[MAX_BUFFER_QUEUE]; 54 | int n_queued_bufs; 55 | 56 | uint64_t last_enqueued_us, last_dequeued_us; 57 | int last_capture_time_us, last_in_queue_time_us; 58 | bool streaming; 59 | buffer_stats_t stats, stats_last; 60 | } buffer_list_t; 61 | 62 | buffer_list_t *buffer_list_open(const char *name, int index, struct device_s *dev, const char *path, buffer_format_t fmt, bool do_capture, bool do_mmap); 63 | void buffer_list_close(buffer_list_t *buf_list); 64 | int buffer_list_alloc_buffers(buffer_list_t *buf_list); 65 | void buffer_list_free_buffers(buffer_list_t *buf_list); 66 | 67 | int buffer_list_set_stream(buffer_list_t *buf_list, bool do_on); 68 | 69 | int buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue); 70 | buffer_t *buffer_list_find_slot(buffer_list_t *buf_list); 71 | buffer_t *buffer_list_dequeue(buffer_list_t *buf_list); 72 | int buffer_list_count_enqueued(buffer_list_t *buf_list); 73 | int buffer_list_enqueue(buffer_list_t *buf_list, buffer_t *dma_buf); 74 | void buffer_list_clear_queue(buffer_list_t *buf_list); 75 | bool buffer_list_push_to_queue(buffer_list_t *buf_list, buffer_t *dma_buf, int max_bufs); 76 | buffer_t *buffer_list_pop_from_queue(buffer_list_t *buf_list); 77 | -------------------------------------------------------------------------------- /device/buffer_lock.c: -------------------------------------------------------------------------------- 1 | #include "device/buffer_lock.h" 2 | #include "device/buffer_list.h" 3 | #include "device/buffer.h" 4 | #include "util/opts/log.h" 5 | 6 | bool buffer_lock_is_used(buffer_lock_t *buf_lock) 7 | { 8 | int refs = 0; 9 | 10 | pthread_mutex_lock(&buf_lock->lock); 11 | refs = buf_lock->refs; 12 | pthread_mutex_unlock(&buf_lock->lock); 13 | 14 | return refs; 15 | } 16 | 17 | void buffer_lock_use(buffer_lock_t *buf_lock, int ref) 18 | { 19 | pthread_mutex_lock(&buf_lock->lock); 20 | buf_lock->refs += ref; 21 | pthread_mutex_unlock(&buf_lock->lock); 22 | } 23 | 24 | bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock) 25 | { 26 | uint64_t now = get_monotonic_time_us(NULL, NULL); 27 | bool needs_buffer = false; 28 | 29 | pthread_mutex_lock(&buf_lock->lock); 30 | if (buf_lock->timeout_us > 0 && now - buf_lock->buf_time_us > buf_lock->timeout_us) { 31 | buffer_consumed(buf_lock->buf, buf_lock->name); 32 | buf_lock->buf = NULL; 33 | } 34 | if (buf_lock->refs > 0) { 35 | needs_buffer = true; 36 | } 37 | for (int i = 0; !needs_buffer && buf_lock->check_streaming[i] && i < BUFFER_LOCK_MAX_CALLBACKS; i++) { 38 | needs_buffer = buf_lock->check_streaming[i](buf_lock); 39 | } 40 | pthread_mutex_unlock(&buf_lock->lock); 41 | 42 | return needs_buffer; 43 | } 44 | 45 | static void buffer_lock_clear_buffers(buffer_lock_t *buf_lock, uint64_t now) 46 | { 47 | buffer_consumed(buf_lock->buf, buf_lock->name); 48 | buf_lock->buf = NULL; 49 | buf_lock->buf_time_us = now; 50 | } 51 | 52 | static void buffer_lock_set_buffer(buffer_lock_t *buf_lock, buffer_t *buf, uint64_t now) 53 | { 54 | buffer_consumed(buf_lock->buf, buf_lock->name); 55 | buffer_use(buf); 56 | buf_lock->buf = buf; 57 | buf_lock->buf_time_us = now; 58 | buf_lock->counter++; 59 | 60 | LOG_DEBUG(buf_lock, "Captured buffer %s (refs=%d), frame=%d/%d, processing_ms=%.1f, frame_ms=%.1f", 61 | dev_name(buf), buf ? buf->mmap_reflinks : 0, 62 | buf_lock->counter, buf_lock->dropped, 63 | (now - buf->captured_time_us) / 1000.0f, 64 | (now - buf_lock->buf_time_us) / 1000.0f); 65 | pthread_cond_broadcast(&buf_lock->cond_wait); 66 | 67 | for (int i = 0; buf_lock->notify_buffer[i] && i < BUFFER_LOCK_MAX_CALLBACKS; i++) { 68 | buf_lock->notify_buffer[i](buf_lock, buf); 69 | } 70 | } 71 | 72 | void buffer_lock_capture(buffer_lock_t *buf_lock, buffer_t *buf) 73 | { 74 | uint64_t now = get_monotonic_time_us(NULL, NULL); 75 | 76 | pthread_mutex_lock(&buf_lock->lock); 77 | 78 | if (!buf) { 79 | buffer_lock_clear_buffers(buf_lock, now); 80 | } else if (buf->flags.is_keyframe) { 81 | buffer_lock_set_buffer(buf_lock, buf, now); 82 | } else if (now - buf_lock->buf_time_us >= buf_lock->frame_interval_ms * 1000) { 83 | buffer_lock_set_buffer(buf_lock, buf, now); 84 | } else { 85 | buf_lock->dropped++; 86 | 87 | LOG_DEBUG(buf_lock, "Dropped buffer %s (refs=%d), frame=%d/%d, frame_ms=%.1f", 88 | dev_name(buf), buf ? buf->mmap_reflinks : 0, 89 | buf_lock->counter, buf_lock->dropped, 90 | (now - buf->captured_time_us) / 1000.0f); 91 | } 92 | 93 | pthread_mutex_unlock(&buf_lock->lock); 94 | } 95 | 96 | buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_ms, int *counter) 97 | { 98 | buffer_t *buf = NULL; 99 | struct timespec timeout; 100 | 101 | if(!timeout_ms) 102 | timeout_ms = DEFAULT_BUFFER_LOCK_GET_TIMEOUT; 103 | 104 | get_time_us(CLOCK_REALTIME, &timeout, NULL, timeout_ms * 1000LL); 105 | 106 | pthread_mutex_lock(&buf_lock->lock); 107 | if (*counter == buf_lock->counter || !buf_lock->buf) { 108 | int ret = pthread_cond_timedwait(&buf_lock->cond_wait, &buf_lock->lock, &timeout); 109 | if (ret == ETIMEDOUT) { 110 | goto ret; 111 | } else if (ret < 0) { 112 | goto ret; 113 | } 114 | } 115 | 116 | buf = buf_lock->buf; 117 | buffer_use(buf); 118 | *counter = buf_lock->counter; 119 | 120 | ret: 121 | pthread_mutex_unlock(&buf_lock->lock); 122 | return buf; 123 | } 124 | 125 | int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, unsigned timeout_ms, buffer_write_fn fn, void *data) 126 | { 127 | int counter = 0; 128 | int frames = 0; 129 | uint64_t deadline_ms = get_monotonic_time_us(NULL, NULL) + DEFAULT_BUFFER_LOCK_GET_TIMEOUT * 1000LL; 130 | uint64_t frame_stop_ms = get_monotonic_time_us(NULL, NULL) + timeout_ms * 1000LL; 131 | 132 | buffer_lock_use(buf_lock, 1); 133 | 134 | while (nframes == 0 || frames < nframes) { 135 | if (timeout_ms && frame_stop_ms < get_monotonic_time_us(NULL, NULL)) { 136 | break; 137 | } 138 | 139 | buffer_t *buf = buffer_lock_get(buf_lock, 0, &counter); 140 | if (!buf) { 141 | goto error; 142 | } 143 | 144 | int ret = fn(buf_lock, buf, frames, data); 145 | buffer_consumed(buf, "write-loop"); 146 | 147 | if (ret > 0) { 148 | frames++; 149 | } else if (ret < 0) { 150 | goto error; 151 | } else if(!frames && deadline_ms < get_monotonic_time_us(NULL, NULL)) { 152 | LOG_DEBUG(buf_lock, "Deadline getting frame elapsed."); 153 | goto error; 154 | } 155 | } 156 | 157 | buffer_lock_use(buf_lock, -1); 158 | return frames; 159 | 160 | error: 161 | buffer_lock_use(buf_lock, -1); 162 | return -frames; 163 | } 164 | 165 | bool buffer_lock_register_check_streaming(buffer_lock_t *buf_lock, buffer_lock_check_streaming check_streaming) 166 | { 167 | bool ret = false; 168 | 169 | pthread_mutex_lock(&buf_lock->lock); 170 | for (int i = 0; i < BUFFER_LOCK_MAX_CALLBACKS; i++) { 171 | if (!buf_lock->check_streaming[i]) { 172 | buf_lock->check_streaming[i] = check_streaming; 173 | ret = true; 174 | break; 175 | } 176 | } 177 | pthread_mutex_unlock(&buf_lock->lock); 178 | 179 | return ret; 180 | } 181 | 182 | bool buffer_lock_register_notify_buffer(buffer_lock_t *buf_lock, buffer_lock_notify_buffer notify_buffer) 183 | { 184 | bool ret = false; 185 | 186 | pthread_mutex_lock(&buf_lock->lock); 187 | for (int i = 0; i < BUFFER_LOCK_MAX_CALLBACKS; i++) { 188 | if (!buf_lock->notify_buffer[i]) { 189 | buf_lock->notify_buffer[i] = notify_buffer; 190 | ret = true; 191 | break; 192 | } 193 | } 194 | pthread_mutex_unlock(&buf_lock->lock); 195 | 196 | return ret; 197 | } 198 | -------------------------------------------------------------------------------- /device/buffer_lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct buffer_s buffer_t; 8 | typedef struct buffer_list_s buffer_list_t; 9 | typedef struct buffer_lock_s buffer_lock_t; 10 | 11 | typedef bool (*buffer_lock_check_streaming)(buffer_lock_t *buf_lock); 12 | typedef void (*buffer_lock_notify_buffer)(buffer_lock_t *buf_lock, buffer_t *buf); 13 | 14 | #define BUFFER_LOCK_MAX_CALLBACKS 10 15 | 16 | typedef struct buffer_lock_s { 17 | const char *name; 18 | buffer_list_t *buf_list; 19 | 20 | buffer_lock_check_streaming check_streaming[BUFFER_LOCK_MAX_CALLBACKS]; 21 | buffer_lock_notify_buffer notify_buffer[BUFFER_LOCK_MAX_CALLBACKS]; 22 | 23 | // private 24 | pthread_mutex_t lock; 25 | pthread_cond_t cond_wait; 26 | buffer_t *buf; 27 | uint64_t buf_time_us; 28 | int counter; 29 | int refs; 30 | int dropped; 31 | uint64_t timeout_us; 32 | 33 | int frame_interval_ms; 34 | } buffer_lock_t; 35 | 36 | #define DEFAULT_BUFFER_LOCK_TIMEOUT 16 // ~60fps 37 | #define DEFAULT_BUFFER_LOCK_GET_TIMEOUT 2000 // 2s 38 | 39 | #define DEFINE_BUFFER_LOCK(_name, _timeout_ms) buffer_lock_t _name = { \ 40 | .name = #_name, \ 41 | .lock = PTHREAD_MUTEX_INITIALIZER, \ 42 | .cond_wait = PTHREAD_COND_INITIALIZER, \ 43 | .timeout_us = (_timeout_ms > DEFAULT_BUFFER_LOCK_TIMEOUT ? _timeout_ms : DEFAULT_BUFFER_LOCK_TIMEOUT) * 1000LL, \ 44 | }; 45 | 46 | #define DECLARE_BUFFER_LOCK(_name) extern buffer_lock_t _name; 47 | 48 | typedef int (*buffer_write_fn)(buffer_lock_t *buf_lock, buffer_t *buf, int frame, void *data); 49 | 50 | void buffer_lock_capture(buffer_lock_t *buf_lock, buffer_t *buf); 51 | buffer_t *buffer_lock_get(buffer_lock_t *buf_lock, int timeout_ms, int *counter); 52 | bool buffer_lock_needs_buffer(buffer_lock_t *buf_lock); 53 | void buffer_lock_use(buffer_lock_t *buf_lock, int ref); 54 | bool buffer_lock_is_used(buffer_lock_t *buf_lock); 55 | int buffer_lock_write_loop(buffer_lock_t *buf_lock, int nframes, unsigned timeout_ms, buffer_write_fn fn, void *data); 56 | bool buffer_lock_register_check_streaming(buffer_lock_t *buf_lock, buffer_lock_check_streaming check_streaming); 57 | bool buffer_lock_register_notify_buffer(buffer_lock_t *buf_lock, buffer_lock_notify_buffer notify_buffer); 58 | -------------------------------------------------------------------------------- /device/camera/camera.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/device.h" 4 | #include "device/device_list.h" 5 | #include "device/buffer_list.h" 6 | #include "device/buffer_lock.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | 11 | camera_t *camera_open(camera_options_t *options) 12 | { 13 | camera_t *camera = calloc(1, sizeof(camera_t)); 14 | camera->name = "CAMERA"; 15 | camera->options = *options; 16 | camera->device_list = device_list_v4l2(); 17 | 18 | if (camera_configure_input(camera) < 0) { 19 | goto error; 20 | } 21 | 22 | if (camera_set_params(camera) < 0) { 23 | goto error; 24 | } 25 | 26 | links_dump(camera->links); 27 | 28 | return camera; 29 | 30 | error: 31 | camera_close(&camera); 32 | return NULL; 33 | } 34 | 35 | void camera_close(camera_t **camerap) 36 | { 37 | if (!camerap || !*camerap) 38 | return; 39 | 40 | camera_t *camera = *camerap; 41 | *camerap = NULL; 42 | 43 | for (int i = MAX_DEVICES; i-- > 0; ) { 44 | link_t *link = &camera->links[i]; 45 | 46 | for (int j = 0; j < link->n_callbacks; j++) { 47 | if (link->callbacks[j].on_buffer) { 48 | link->callbacks[j].on_buffer(NULL); 49 | link->callbacks[j].on_buffer = NULL; 50 | } 51 | if (link->callbacks[j].buf_lock) { 52 | buffer_lock_capture(link->callbacks[j].buf_lock, NULL); 53 | link->callbacks[j].buf_lock = NULL; 54 | } 55 | } 56 | } 57 | 58 | for (int i = MAX_DEVICES; i-- > 0; ) { 59 | if (camera->devices[i]) { 60 | device_close(camera->devices[i]); 61 | camera->devices[i] = NULL; 62 | } 63 | } 64 | 65 | device_list_free(camera->device_list); 66 | free(camera); 67 | } 68 | 69 | link_t *camera_ensure_capture(camera_t *camera, buffer_list_t *capture) 70 | { 71 | for (int i = 0; i < camera->nlinks; i++) { 72 | if (camera->links[i].capture_list == capture) { 73 | return &camera->links[i]; 74 | } 75 | } 76 | 77 | link_t *link = &camera->links[camera->nlinks++]; 78 | link->capture_list = capture; 79 | return link; 80 | } 81 | 82 | void camera_capture_add_output(camera_t *camera, buffer_list_t *capture, buffer_list_t *output) 83 | { 84 | link_t *link = camera_ensure_capture(camera, capture); 85 | ARRAY_APPEND(link->output_lists, link->n_output_lists, output); 86 | } 87 | 88 | void camera_capture_add_callbacks(camera_t *camera, buffer_list_t *capture, link_callbacks_t callbacks) 89 | { 90 | link_t *link = camera_ensure_capture(camera, capture); 91 | if (!ARRAY_APPEND(link->callbacks, link->n_callbacks, callbacks)) 92 | return; 93 | 94 | if (callbacks.buf_lock) { 95 | callbacks.buf_lock->buf_list = capture; 96 | } 97 | } 98 | 99 | int camera_set_params(camera_t *camera) 100 | { 101 | device_set_fps(camera->camera, camera->options.fps); 102 | 103 | // HACK: 104 | // Some cameras require to be in streaming to apply settings 105 | // This applies twice to avoid ordering issues (auto-focus vs focus value) 106 | if (camera->camera->n_capture_list > 0) 107 | buffer_list_set_stream(camera->camera->capture_lists[0], true); 108 | if (device_set_option_list(camera->camera, camera->options.options) < 0) 109 | device_set_option_list(camera->camera, camera->options.options); 110 | 111 | device_set_option_list(camera->isp, camera->options.isp.options); 112 | 113 | if (camera->options.auto_focus) { 114 | device_set_option_string(camera->camera, "AfTrigger", "1"); 115 | } 116 | 117 | // Set some defaults 118 | device_set_option_list(camera->codec_snapshot, camera->options.snapshot.options); 119 | device_set_option_list(camera->codec_stream, camera->options.stream.options); 120 | device_set_option_string(camera->codec_video, "repeat_sequence_header", "1"); // required for force key support 121 | device_set_option_list(camera->codec_video, camera->options.video.options); 122 | return 0; 123 | } 124 | 125 | int camera_run(camera_t *camera) 126 | { 127 | bool running = false; 128 | return links_loop(camera->links, camera->options.force_active, &running); 129 | } 130 | -------------------------------------------------------------------------------- /device/camera/camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "device/links.h" 4 | #include "device/device.h" 5 | 6 | #define MAX_DEVICES 20 7 | #define MAX_RESCALLERS 4 8 | #define MAX_HTTP_METHODS 20 9 | 10 | #define CAMERA_DEVICE_CAMERA 0 11 | #define CAMERA_OPTIONS_LENGTH 4096 12 | 13 | #define MAX_RESCALLER_SIZE 1920 14 | #define RESCALLER_BLOCK_SIZE 32 15 | 16 | typedef enum { 17 | CAMERA_V4L2 = 0, 18 | CAMERA_LIBCAMERA, 19 | CAMERA_DUMMY 20 | } camera_type_t; 21 | 22 | typedef struct camera_output_options_s { 23 | bool disabled; 24 | unsigned height; 25 | char options[CAMERA_OPTIONS_LENGTH]; 26 | } camera_output_options_t; 27 | 28 | typedef struct camera_options_s { 29 | char path[256]; 30 | unsigned width, height, format; 31 | unsigned nbufs, fps; 32 | camera_type_t type; 33 | bool allow_dma; 34 | float high_res_factor; 35 | float low_res_factor; 36 | bool auto_focus; 37 | unsigned auto_reconnect; 38 | bool force_active; 39 | union { 40 | bool vflip; 41 | unsigned vflip_align; 42 | }; 43 | union { 44 | bool hflip; 45 | unsigned hflip_align; 46 | }; 47 | 48 | char options[CAMERA_OPTIONS_LENGTH]; 49 | bool list_options; 50 | 51 | struct { 52 | char options[CAMERA_OPTIONS_LENGTH]; 53 | } isp; 54 | 55 | camera_output_options_t snapshot; 56 | camera_output_options_t stream; 57 | camera_output_options_t video; 58 | } camera_options_t; 59 | 60 | typedef struct camera_s { 61 | const char *name; 62 | 63 | camera_options_t options; 64 | 65 | union { 66 | device_t *devices[MAX_DEVICES]; 67 | struct { 68 | device_t *camera; 69 | device_t *decoder; // decode JPEG/H264 into YUVU 70 | device_t *isp; 71 | device_t *rescallers[3]; 72 | device_t *codec_snapshot; 73 | device_t *codec_stream; 74 | device_t *codec_video; 75 | }; 76 | }; 77 | 78 | struct device_list_s *device_list; 79 | 80 | link_t links[MAX_DEVICES]; 81 | int nlinks; 82 | } camera_t; 83 | 84 | #define CAMERA(DEVICE) camera->devices[DEVICE] 85 | 86 | camera_t *camera_open(camera_options_t *camera); 87 | int camera_set_params(camera_t *camera); 88 | void camera_close(camera_t **camera); 89 | int camera_run(camera_t *camera); 90 | 91 | link_t *camera_ensure_capture(camera_t *camera, buffer_list_t *capture); 92 | void camera_capture_add_output(camera_t *camera, buffer_list_t *capture, buffer_list_t *output); 93 | void camera_capture_add_callbacks(camera_t *camera, buffer_list_t *capture, link_callbacks_t callbacks); 94 | 95 | int camera_configure_input(camera_t *camera); 96 | int camera_configure_pipeline(camera_t *camera, buffer_list_t *camera_capture); 97 | void camera_debug_capture(camera_t *camera, buffer_list_t *capture); 98 | 99 | buffer_list_t *camera_configure_isp(camera_t *camera, buffer_list_t *src_capture); 100 | buffer_list_t *camera_configure_decoder(camera_t *camera, buffer_list_t *src_capture); 101 | buffer_list_t *camera_configure_rescaller(camera_t *camera, buffer_list_t *src_capture, const char *name, unsigned target_height, unsigned formats[]); 102 | int camera_configure_output(camera_t *camera, buffer_list_t *camera_capture, const char *name, camera_output_options_t *options, unsigned formats[], link_callbacks_t callbacks, device_t **device); 103 | bool camera_get_scaled_resolution(buffer_format_t capture_format, camera_output_options_t *options, buffer_format_t *format, int align_size); 104 | -------------------------------------------------------------------------------- /device/camera/camera_debug.c: -------------------------------------------------------------------------------- 1 | #include "device/buffer.h" 2 | #include "device/buffer_list.h" 3 | #include "device/links.h" 4 | #include "device/camera/camera.h" 5 | #include "util/opts/log.h" 6 | #include "util/opts/fourcc.h" 7 | #include "output/output.h" 8 | 9 | #include 10 | 11 | static void debug_capture_on_buffer(buffer_t *buf) 12 | { 13 | if (!buf) { 14 | return; 15 | } 16 | 17 | char path[256]; 18 | sprintf(path, "%s/decoder_capture.%d.%s", getenv("CAMERA_DEBUG_CAPTURE"), buf->index, fourcc_to_string(buf->buf_list->fmt.format).buf); 19 | 20 | FILE *fp = fopen(path, "wb"); 21 | if (!fp) { 22 | return; 23 | } 24 | 25 | fwrite(buf->start, 1, buf->used, fp); 26 | fclose(fp); 27 | } 28 | 29 | static link_callbacks_t debug_capture_callbacks = { 30 | .name = "DEBUG-CAPTURE", 31 | .on_buffer = debug_capture_on_buffer 32 | }; 33 | 34 | void camera_debug_capture(camera_t *camera, buffer_list_t *capture) 35 | { 36 | if (getenv("CAMERA_DEBUG_CAPTURE")) { 37 | mkdir(getenv("CAMERA_DEBUG_CAPTURE"), 0755); 38 | camera_capture_add_callbacks(camera, capture, debug_capture_callbacks); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /device/camera/camera_decoder.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | #include "device/buffer_list.h" 11 | #include "util/http/http.h" 12 | #include "output/rtsp/rtsp.h" 13 | #include "output/output.h" 14 | 15 | static unsigned decoder_formats[] = 16 | { 17 | // best quality 18 | V4L2_PIX_FMT_YUYV, 19 | 20 | // medium quality 21 | V4L2_PIX_FMT_NV12, 22 | V4L2_PIX_FMT_YUV420, 23 | 24 | // low quality 25 | V4L2_PIX_FMT_NV21, 26 | V4L2_PIX_FMT_YVU420, 27 | 0 28 | }; 29 | 30 | buffer_list_t *camera_configure_decoder(camera_t *camera, buffer_list_t *src_capture) 31 | { 32 | unsigned chosen_format = 0; 33 | device_info_t *device = device_list_find_m2m_formats(camera->device_list, src_capture->fmt.format, decoder_formats, &chosen_format); 34 | 35 | if (!device) { 36 | LOG_INFO(camera, "Cannot find '%s' decoder", fourcc_to_string(src_capture->fmt.format).buf); 37 | return NULL; 38 | } 39 | 40 | device_video_force_key(camera->camera); 41 | 42 | camera->decoder = device_v4l2_open("DECODER", device->path); 43 | 44 | buffer_list_t *decoder_output = device_open_buffer_list_output( 45 | camera->decoder, src_capture); 46 | buffer_list_t *decoder_capture = device_open_buffer_list_capture2( 47 | camera->decoder, NULL, decoder_output, chosen_format, true); 48 | 49 | camera_debug_capture(camera, decoder_capture); 50 | camera_capture_add_output(camera, src_capture, decoder_output); 51 | 52 | return decoder_capture; 53 | } 54 | -------------------------------------------------------------------------------- /device/camera/camera_input.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | 11 | static int camera_configure_input_v4l2(camera_t *camera) 12 | { 13 | const char *path = camera->options.path; 14 | 15 | if (!*path) { 16 | path = "/dev/video0"; 17 | } 18 | 19 | camera->camera = device_v4l2_open(camera->name, path); 20 | if (!camera->camera) { 21 | LOG_INFO(camera, "Listing available v4l2 devices:"); 22 | system("v4l2-ctl --list-devices"); 23 | return -1; 24 | } 25 | 26 | device_set_rotation(camera->camera, camera->options.vflip, camera->options.hflip); 27 | 28 | camera->camera->opts.allow_dma = camera->options.allow_dma; 29 | 30 | if (strstr(camera->camera->bus_info, "usb")) { 31 | LOG_INFO(camera, "Disabling DMA since device uses USB (which is likely not working properly)."); 32 | camera->camera->opts.allow_dma = false; 33 | } 34 | 35 | buffer_format_t fmt = { 36 | .width = camera->options.width, 37 | .height = camera->options.height, 38 | .format = camera->options.format, 39 | .nbufs = camera->options.nbufs 40 | }; 41 | 42 | buffer_list_t *camera_capture = device_open_buffer_list(camera->camera, true, fmt, true); 43 | if (!camera_capture) { 44 | return -1; 45 | } 46 | 47 | return camera_configure_pipeline(camera, camera_capture); 48 | } 49 | 50 | static int camera_configure_input_libcamera(camera_t *camera) 51 | { 52 | camera->camera = device_libcamera_open(camera->name, camera->options.path); 53 | if (!camera->camera) { 54 | return -1; 55 | } 56 | 57 | device_set_rotation(camera->camera, camera->options.vflip, camera->options.hflip); 58 | 59 | camera->camera->opts.allow_dma = camera->options.allow_dma; 60 | 61 | buffer_format_t capture_fmt = { 62 | .width = camera->options.width, 63 | .height = camera->options.height, 64 | .format = camera->options.format, 65 | .nbufs = camera->options.nbufs, 66 | .type = BUFFER_TYPE_IMAGE 67 | }; 68 | 69 | bool found = false; 70 | 71 | found = camera_get_scaled_resolution(capture_fmt, &camera->options.snapshot, &capture_fmt, 1); 72 | if (!found) 73 | found = camera_get_scaled_resolution(capture_fmt, &camera->options.stream, &capture_fmt, 1); 74 | if (!found) 75 | found = camera_get_scaled_resolution(capture_fmt, &camera->options.video, &capture_fmt, 1); 76 | 77 | buffer_list_t *camera_capture = device_open_buffer_list(camera->camera, true, capture_fmt, true); 78 | if (!camera_capture) { 79 | return -1; 80 | } 81 | 82 | buffer_format_t raw_fmt = { 83 | .width = camera->options.width, 84 | .height = camera->options.height, 85 | .nbufs = camera->options.nbufs, 86 | .type = BUFFER_TYPE_RAW 87 | }; 88 | 89 | buffer_list_t *raw_capture = device_open_buffer_list(camera->camera, true, raw_fmt, true); 90 | if (!raw_capture) { 91 | return -1; 92 | } 93 | 94 | if (buffer_list_alloc_buffers(camera_capture) < 0) { 95 | return -1; 96 | } 97 | if (buffer_list_alloc_buffers(raw_capture) < 0) { 98 | return -1; 99 | } 100 | 101 | return camera_configure_pipeline(camera, camera_capture); 102 | } 103 | 104 | static int camera_configure_input_dummy(camera_t *camera) 105 | { 106 | camera->camera = device_dummy_open(camera->name, camera->options.path); 107 | if (!camera->camera) { 108 | return -1; 109 | } 110 | 111 | buffer_format_t fmt = { 112 | .width = camera->options.width, 113 | .height = camera->options.height, 114 | .format = camera->options.format, 115 | .nbufs = camera->options.nbufs 116 | }; 117 | 118 | buffer_list_t *camera_capture = device_open_buffer_list(camera->camera, true, fmt, true); 119 | if (!camera_capture) { 120 | return -1; 121 | } 122 | 123 | return camera_configure_pipeline(camera, camera_capture); 124 | } 125 | 126 | int camera_configure_input(camera_t *camera) 127 | { 128 | switch (camera->options.type) { 129 | case CAMERA_V4L2: 130 | return camera_configure_input_v4l2(camera); 131 | 132 | case CAMERA_LIBCAMERA: 133 | return camera_configure_input_libcamera(camera); 134 | 135 | case CAMERA_DUMMY: 136 | return camera_configure_input_dummy(camera); 137 | 138 | default: 139 | LOG_INFO(camera, "Unsupported camera type"); 140 | return -1; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /device/camera/camera_isp.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | #include "device/buffer_list.h" 11 | #include "util/http/http.h" 12 | 13 | buffer_list_t *camera_configure_isp(camera_t *camera, buffer_list_t *src_capture) 14 | { 15 | camera->isp = device_v4l2_open("ISP", "/dev/video13"); 16 | 17 | buffer_list_t *isp_output = device_open_buffer_list_output( 18 | camera->isp, src_capture); 19 | buffer_list_t *isp_capture = device_open_buffer_list_capture2( 20 | camera->isp, "/dev/video14", isp_output, V4L2_PIX_FMT_YUYV, true); 21 | 22 | camera_capture_add_output(camera, src_capture, isp_output); 23 | 24 | return isp_capture; 25 | } 26 | -------------------------------------------------------------------------------- /device/camera/camera_output.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | #include "device/buffer_list.h" 11 | #include "util/http/http.h" 12 | #include "output/rtsp/rtsp.h" 13 | #include "output/output.h" 14 | 15 | #define MATCH_ALIGN_SIZE 32 16 | 17 | static bool camera_output_matches_capture(buffer_list_t *capture, unsigned target_height, unsigned format) 18 | { 19 | if (target_height && abs((int)capture->fmt.height - (int)target_height) > MATCH_ALIGN_SIZE) { 20 | return false; 21 | } 22 | 23 | if (format != capture->fmt.format) { 24 | return false; 25 | } 26 | 27 | return true; 28 | } 29 | 30 | static buffer_list_t *camera_find_capture(camera_t *camera, unsigned target_height, unsigned format) 31 | { 32 | for (int i = 0; i < MAX_DEVICES; i++) { 33 | if (!camera->devices[i]) 34 | continue; 35 | 36 | device_t *device = camera->devices[i]; 37 | for (int j = 0; j < device->n_capture_list; j++) { 38 | buffer_list_t *capture_list = device->capture_lists[j]; 39 | 40 | if (camera_output_matches_capture(capture_list, target_height, format)) { 41 | return capture_list; 42 | } 43 | } 44 | } 45 | 46 | return NULL; 47 | } 48 | 49 | static buffer_list_t *camera_find_capture2(camera_t *camera, unsigned target_height, unsigned formats[]) 50 | { 51 | for (int i = 0; formats[i]; i++) { 52 | buffer_list_t *capture = camera_find_capture(camera, target_height, formats[i]); 53 | if (capture) { 54 | return capture; 55 | } 56 | } 57 | 58 | return NULL; 59 | } 60 | 61 | static unsigned rescalled_formats[] = 62 | { 63 | // best quality 64 | V4L2_PIX_FMT_YUYV, 65 | 66 | // medium quality 67 | V4L2_PIX_FMT_YUV420, 68 | V4L2_PIX_FMT_NV12, 69 | 70 | // low quality 71 | V4L2_PIX_FMT_NV21, 72 | V4L2_PIX_FMT_YVU420, 73 | 74 | 0 75 | }; 76 | 77 | #define OUTPUT_RESCALLER_SIZE 32 78 | 79 | int camera_configure_output(camera_t *camera, buffer_list_t *camera_capture, const char *name, camera_output_options_t *options, unsigned formats[], link_callbacks_t callbacks, device_t **device) 80 | { 81 | buffer_format_t selected_format = {0}; 82 | buffer_format_t rescalled_format = {0}; 83 | 84 | if (!camera_get_scaled_resolution(camera_capture->fmt, options, &selected_format, 1)) { 85 | return 0; 86 | } 87 | 88 | if (!camera_get_scaled_resolution(camera_capture->fmt, options, &rescalled_format, RESCALLER_BLOCK_SIZE)) { 89 | return 0; 90 | } 91 | 92 | // find an existing format 93 | buffer_list_t *src_capture = camera_find_capture2(camera, selected_format.height, formats); 94 | if (src_capture) { 95 | camera_capture_add_callbacks(camera, src_capture, callbacks); 96 | return 0; 97 | } 98 | 99 | // Try to find exact capture 100 | src_capture = camera_find_capture2(camera, rescalled_format.height, rescalled_formats); 101 | 102 | // Try to re-scale capture 103 | if (!src_capture) { 104 | buffer_list_t *other_capture = camera_find_capture2(camera, 0, rescalled_formats); 105 | 106 | if (other_capture) { 107 | src_capture = camera_configure_rescaller(camera, other_capture, name, rescalled_format.height, rescalled_formats); 108 | } 109 | } 110 | 111 | // Try to decode output 112 | if (!src_capture) { 113 | buffer_list_t *decoded_capture = NULL; 114 | 115 | switch (camera_capture->fmt.format) { 116 | case V4L2_PIX_FMT_SRGGB10P: 117 | case V4L2_PIX_FMT_SGRBG10P: 118 | case V4L2_PIX_FMT_SBGGR10P: 119 | case V4L2_PIX_FMT_SRGGB10: 120 | case V4L2_PIX_FMT_SGRBG10: 121 | decoded_capture = camera_configure_isp(camera, camera_capture); 122 | break; 123 | 124 | case V4L2_PIX_FMT_MJPEG: 125 | case V4L2_PIX_FMT_H264: 126 | decoded_capture = camera_configure_decoder(camera, camera_capture); 127 | break; 128 | } 129 | 130 | // Now, do we have exact match 131 | src_capture = camera_find_capture2(camera, selected_format.height, rescalled_formats); 132 | 133 | if (!src_capture) { 134 | src_capture = camera_find_capture2(camera, rescalled_format.height, rescalled_formats); 135 | } 136 | 137 | // Otherwise rescalle decoded output 138 | if (!src_capture && decoded_capture) { 139 | src_capture = camera_configure_rescaller(camera, decoded_capture, name, selected_format.height, rescalled_formats); 140 | } 141 | } 142 | 143 | if (!src_capture) { 144 | LOG_INFO(camera, "Cannot find source for '%s' for one of the formats '%s'.", name, many_fourcc_to_string(formats).buf); 145 | return -1; 146 | } 147 | 148 | unsigned chosen_format = 0; 149 | device_info_t *device_info = device_list_find_m2m_formats(camera->device_list, src_capture->fmt.format, formats, &chosen_format); 150 | 151 | if (!device_info) { 152 | LOG_INFO(camera, "Cannot find encoder to convert from '%s'", fourcc_to_string(src_capture->fmt.format).buf); 153 | return -1; 154 | } 155 | 156 | *device = device_v4l2_open(name, device_info->path); 157 | 158 | buffer_list_t *output = device_open_buffer_list_output(*device, src_capture); 159 | buffer_list_t *capture = device_open_buffer_list_capture2(*device, NULL, output, chosen_format, true); 160 | 161 | if (!capture) { 162 | return -1; 163 | } 164 | 165 | camera_capture_add_output(camera, src_capture, output); 166 | camera_capture_add_callbacks(camera, capture, callbacks); 167 | camera_debug_capture(camera, capture); 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /device/camera/camera_pipeline.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | #include "device/buffer_list.h" 11 | #include "util/http/http.h" 12 | #include "output/output.h" 13 | 14 | static unsigned snapshot_formats[] = 15 | { 16 | V4L2_PIX_FMT_JPEG, 17 | V4L2_PIX_FMT_MJPEG, 18 | 0 19 | }; 20 | 21 | static link_callbacks_t snapshot_callbacks = 22 | { 23 | .name = "SNAPSHOT-CAPTURE", 24 | .buf_lock = &snapshot_lock 25 | }; 26 | 27 | static link_callbacks_t stream_callbacks = 28 | { 29 | .name = "STREAM-CAPTURE", 30 | .buf_lock = &stream_lock 31 | }; 32 | 33 | static unsigned video_formats[] = 34 | { 35 | V4L2_PIX_FMT_H264, 36 | 0 37 | }; 38 | 39 | static link_callbacks_t video_callbacks = 40 | { 41 | .name = "VIDEO-CAPTURE", 42 | .buf_lock = &video_lock 43 | }; 44 | 45 | int camera_configure_pipeline(camera_t *camera, buffer_list_t *camera_capture) 46 | { 47 | camera_capture->do_timestamps = true; 48 | 49 | camera_debug_capture(camera, camera_capture); 50 | 51 | if (camera_configure_output(camera, camera_capture, "SNAPSHOT", &camera->options.snapshot, 52 | snapshot_formats, snapshot_callbacks, &camera->codec_snapshot) < 0) { 53 | return -1; 54 | } 55 | 56 | if (camera_configure_output(camera, camera_capture, "STREAM", &camera->options.stream, 57 | snapshot_formats, stream_callbacks, &camera->codec_stream) < 0) { 58 | return -1; 59 | } 60 | 61 | if (camera_configure_output(camera, camera_capture, "VIDEO", &camera->options.video, 62 | video_formats, video_callbacks, &camera->codec_video) < 0) { 63 | return -1; 64 | } 65 | 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /device/camera/camera_rescaller.c: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | 3 | #include "device/buffer.h" 4 | #include "device/buffer_list.h" 5 | #include "device/device.h" 6 | #include "device/device_list.h" 7 | #include "device/links.h" 8 | #include "util/opts/log.h" 9 | #include "util/opts/fourcc.h" 10 | #include "device/buffer_list.h" 11 | #include "util/http/http.h" 12 | 13 | static unsigned camera_rescaller_align_size(unsigned size, int align_size) 14 | { 15 | if (align_size > 0) 16 | return (size + align_size - 1) / align_size * align_size; 17 | else if (align_size < 0) 18 | return size / -align_size * -align_size; 19 | else 20 | return size; 21 | } 22 | 23 | void camera_get_scaled_resolution2(unsigned in_width, unsigned in_height, unsigned proposed_height, unsigned *target_width, unsigned *target_height, int align_size) 24 | { 25 | proposed_height = MIN(proposed_height, in_height); 26 | 27 | *target_height = camera_rescaller_align_size(proposed_height, align_size); 28 | *target_height = MIN(*target_height, MAX_RESCALLER_SIZE); 29 | 30 | // maintain aspect ratio on target width 31 | *target_width = camera_rescaller_align_size(*target_height * in_width / in_height, align_size); 32 | 33 | // if width is larger then rescaller, try to maintain scale down height 34 | if (*target_width > MAX_RESCALLER_SIZE) { 35 | *target_width = MAX_RESCALLER_SIZE; 36 | *target_height = camera_rescaller_align_size(*target_width * in_height / in_width, align_size); 37 | } 38 | } 39 | 40 | bool camera_get_scaled_resolution(buffer_format_t capture_format, camera_output_options_t *options, buffer_format_t *format, int align_size) 41 | { 42 | if (options->disabled) 43 | return false; 44 | 45 | camera_get_scaled_resolution2( 46 | capture_format.width, 47 | capture_format.height, 48 | options->height, 49 | &format->width, 50 | &format->height, 51 | align_size 52 | ); 53 | return format->height > 0; 54 | } 55 | 56 | buffer_list_t *camera_try_rescaller(camera_t *camera, buffer_list_t *src_capture, const char *name, unsigned target_height, unsigned target_format) 57 | { 58 | device_info_t *device_info = device_list_find_m2m_format(camera->device_list, src_capture->fmt.format, target_format); 59 | 60 | if (!device_info) { 61 | return NULL; 62 | } 63 | 64 | if (target_height > src_capture->fmt.height) { 65 | LOG_INFO(src_capture, "Upscaling from %dp to %dp does not make sense. Lowering to %dp.", 66 | src_capture->fmt.height, target_height, src_capture->fmt.height); 67 | } 68 | 69 | char name2[256]; 70 | sprintf(name2, "RESCALLER:%s", name); 71 | 72 | device_t *device = device_v4l2_open(name2, device_info->path); 73 | 74 | buffer_list_t *rescaller_output = device_open_buffer_list_output( 75 | device, src_capture); 76 | 77 | buffer_format_t target_fmt = { 78 | .format = target_format 79 | }; 80 | 81 | camera_get_scaled_resolution2( 82 | src_capture->fmt.width, src_capture->fmt.height, 83 | target_height, 84 | &target_fmt.width, &target_fmt.height, 85 | RESCALLER_BLOCK_SIZE 86 | ); 87 | 88 | buffer_list_t *rescaller_capture = device_open_buffer_list_capture( 89 | device, NULL, rescaller_output, target_fmt, true); 90 | 91 | if (!rescaller_capture) { 92 | device_close(device); 93 | return NULL; 94 | } 95 | 96 | camera_capture_add_output(camera, src_capture, rescaller_output); 97 | return rescaller_capture; 98 | } 99 | 100 | buffer_list_t *camera_configure_rescaller(camera_t *camera, buffer_list_t *src_capture, const char *name, unsigned target_height, unsigned formats[]) 101 | { 102 | int rescallers = 0; 103 | for ( ; rescallers < MAX_RESCALLERS && camera->rescallers[rescallers]; rescallers++); 104 | if (rescallers == MAX_RESCALLERS) { 105 | return NULL; 106 | } 107 | 108 | buffer_list_t *rescaller_capture = camera_try_rescaller(camera, src_capture, name, target_height, src_capture->fmt.format); 109 | 110 | for (int i = 0; !rescaller_capture && formats[i]; i++) { 111 | rescaller_capture = camera_try_rescaller(camera, src_capture, name, target_height, formats[i]); 112 | } 113 | 114 | if (!rescaller_capture) { 115 | LOG_INFO(src_capture, "Cannot find rescaller to scale from '%s' to 'YUYV'", fourcc_to_string(src_capture->fmt.format).buf); 116 | return NULL; 117 | } 118 | 119 | camera->rescallers[rescallers] = rescaller_capture->dev; 120 | return rescaller_capture; 121 | } 122 | -------------------------------------------------------------------------------- /device/device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct buffer_s buffer_t; 8 | typedef struct buffer_list_s buffer_list_t; 9 | typedef struct buffer_format_s buffer_format_t; 10 | typedef struct device_option_s device_option_t; 11 | typedef struct device_s device_t; 12 | struct pollfd; 13 | 14 | typedef int device_option_fn(device_option_t *option, void *opaque); 15 | 16 | typedef struct device_hw_s { 17 | int (*device_open)(device_t *dev); 18 | void (*device_close)(device_t *dev); 19 | int (*device_video_force_key)(device_t *dev); 20 | void (*device_dump_options)(device_t *dev, FILE *stream); 21 | int (*device_dump_options2)(device_t *dev, device_option_fn fn, void *opaque); 22 | int (*device_set_fps)(device_t *dev, int desired_fps); 23 | int (*device_set_rotation)(device_t *dev, bool vflip, bool hflip); 24 | int (*device_set_option)(device_t *dev, const char *key, const char *value); 25 | 26 | int (*buffer_open)(buffer_t *buf); 27 | void (*buffer_close)(buffer_t *buf); 28 | int (*buffer_enqueue)(buffer_t *buf, const char *who); 29 | int (*buffer_list_dequeue)(buffer_list_t *buf_list, buffer_t **bufp); 30 | int (*buffer_list_pollfd)(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue); 31 | 32 | int (*buffer_list_open)(buffer_list_t *buf_list); 33 | void (*buffer_list_close)(buffer_list_t *buf_list); 34 | int (*buffer_list_alloc_buffers)(buffer_list_t *buf_list); 35 | void (*buffer_list_free_buffers)(buffer_list_t *buf_list); 36 | int (*buffer_list_set_stream)(buffer_list_t *buf_list, bool do_on); 37 | } device_hw_t; 38 | 39 | typedef struct device_s { 40 | char *name; 41 | char *path; 42 | char bus_info[64]; 43 | 44 | device_hw_t *hw; 45 | int n_capture_list; 46 | buffer_list_t **capture_lists; 47 | buffer_list_t *output_list; 48 | 49 | struct { 50 | bool allow_dma; 51 | } opts; 52 | 53 | union { 54 | struct device_v4l2_s *v4l2; 55 | struct device_dummy_s *dummy; 56 | struct device_libcamera_s *libcamera; 57 | }; 58 | 59 | bool paused; 60 | } device_t; 61 | 62 | typedef enum device_option_type_s { 63 | device_option_type_u8, // comp-type 64 | device_option_type_u16, // comp-type 65 | device_option_type_u32, // comp-type 66 | device_option_type_bool, 67 | device_option_type_integer, 68 | device_option_type_integer64, 69 | device_option_type_float, 70 | device_option_type_string 71 | } device_option_type_t; 72 | 73 | #define MAX_DEVICE_OPTION_MENU 20 74 | 75 | typedef struct device_option_menu_s { 76 | int id; 77 | char name[64]; 78 | } device_option_menu_t; 79 | 80 | typedef struct device_option_s { 81 | char name[64]; 82 | unsigned int control_id; 83 | 84 | device_option_type_t type; 85 | int elems; 86 | struct { 87 | bool read_only : 1; 88 | bool invalid : 1; 89 | } flags; 90 | 91 | device_option_menu_t menu[MAX_DEVICE_OPTION_MENU]; 92 | int menu_items; 93 | 94 | char value[256]; 95 | char description[256]; 96 | } device_option_t; 97 | 98 | device_t *device_open(const char *name, const char *path, device_hw_t *hw); 99 | void device_close(device_t *dev); 100 | 101 | buffer_list_t *device_open_buffer_list(device_t *dev, bool do_capture, buffer_format_t fmt, bool do_mmap); 102 | buffer_list_t *device_open_buffer_list2(device_t *dev, const char *path, bool do_capture, buffer_format_t fmt, bool do_mmap); 103 | buffer_list_t *device_open_buffer_list_output(device_t *dev, buffer_list_t *capture_list); 104 | buffer_list_t *device_open_buffer_list_capture(device_t *dev, const char *path, buffer_list_t *output_list, buffer_format_t fmt, bool do_mmap); 105 | buffer_list_t *device_open_buffer_list_capture2(device_t *dev, const char *path, buffer_list_t *output_list, unsigned choosen_format, bool do_mmap); 106 | 107 | int device_set_stream(device_t *dev, bool do_on); 108 | int device_video_force_key(device_t *dev); 109 | 110 | void device_dump_options(device_t *dev, FILE *stream); 111 | int device_dump_options2(device_t *dev, device_option_fn fn, void *opaque); 112 | int device_set_fps(device_t *dev, int desired_fps); 113 | int device_set_rotation(device_t *dev, bool vflip, bool hflip); 114 | int device_set_option_string(device_t *dev, const char *option, const char *value); 115 | int device_set_option_list(device_t *dev, const char *option_list); 116 | 117 | int device_output_enqueued(device_t *dev); 118 | int device_capture_enqueued(device_t *dev, int *max); 119 | 120 | device_t *device_v4l2_open(const char *name, const char *path); 121 | device_t *device_libcamera_open(const char *name, const char *path); 122 | device_t *device_dummy_open(const char *name, const char *path); 123 | -------------------------------------------------------------------------------- /device/device_list.c: -------------------------------------------------------------------------------- 1 | #include "device/device_list.h" 2 | 3 | #include 4 | #include 5 | 6 | bool device_info_has_format(device_info_t *info, bool capture, unsigned format) 7 | { 8 | if (!info) { 9 | return false; 10 | } 11 | 12 | device_info_formats_t *formats = capture ? &info->capture_formats : &info->output_formats; 13 | 14 | for (int i = 0; i < formats->n; i++) { 15 | if (formats->formats[i] == format) { 16 | return true; 17 | } 18 | } 19 | 20 | return false; 21 | } 22 | 23 | device_info_t *device_list_find_m2m_format(device_list_t *list, unsigned output, unsigned capture) 24 | { 25 | if (!list) 26 | return NULL; 27 | 28 | for (int i = 0; i < list->ndevices; i++) { 29 | device_info_t *info = &list->devices[i]; 30 | 31 | if (info->m2m && device_info_has_format(info, false, output) && device_info_has_format(info, true, capture)) { 32 | return info; 33 | } 34 | } 35 | 36 | return NULL; 37 | } 38 | 39 | device_info_t *device_list_find_m2m_formats(device_list_t *list, unsigned output, unsigned capture_formats[], unsigned *found_format) 40 | { 41 | for (int i = 0; capture_formats[i]; i++) { 42 | device_info_t *info = device_list_find_m2m_format(list, output, capture_formats[i]); 43 | if (info) { 44 | if (found_format) { 45 | *found_format = capture_formats[i]; 46 | } 47 | return info; 48 | } 49 | } 50 | 51 | return NULL; 52 | } 53 | 54 | void device_list_free(device_list_t *list) 55 | { 56 | if (!list) 57 | return; 58 | 59 | for (int i = 0; i < list->ndevices; i++) { 60 | device_info_t *info = &list->devices[i]; 61 | free(info->name); 62 | free(info->path); 63 | free(info->output_formats.formats); 64 | free(info->capture_formats.formats); 65 | } 66 | 67 | free(list); 68 | } 69 | -------------------------------------------------------------------------------- /device/device_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct device_info_formats_s { 6 | unsigned *formats; 7 | unsigned n; 8 | } device_info_formats_t; 9 | 10 | typedef struct device_info_s { 11 | char *name; 12 | char *path; 13 | 14 | bool camera; 15 | bool m2m; 16 | 17 | device_info_formats_t output_formats; 18 | device_info_formats_t capture_formats; 19 | } device_info_t; 20 | 21 | typedef struct device_list_s { 22 | device_info_t *devices; 23 | int ndevices; 24 | } device_list_t; 25 | 26 | device_list_t *device_list_v4l2(); 27 | bool device_info_has_format(device_info_t *info, bool capture, unsigned format); 28 | device_info_t *device_list_find_m2m_format(device_list_t *list, unsigned output, unsigned capture); 29 | device_info_t *device_list_find_m2m_formats(device_list_t *list, unsigned output, unsigned capture_formats[], unsigned *found_format); 30 | void device_list_free(device_list_t *list); 31 | -------------------------------------------------------------------------------- /device/dummy/buffer.c: -------------------------------------------------------------------------------- 1 | #include "dummy.h" 2 | #include "device/buffer.h" 3 | #include "device/buffer_list.h" 4 | #include "util/opts/log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int dummy_buffer_open(buffer_t *buf) 11 | { 12 | buf->dummy = calloc(1, sizeof(buffer_dummy_t)); 13 | buf->start = buf->buf_list->dummy->data; 14 | buf->used = buf->buf_list->dummy->length; 15 | buf->length = buf->buf_list->dummy->length; 16 | return 0; 17 | } 18 | 19 | void dummy_buffer_close(buffer_t *buf) 20 | { 21 | free(buf->dummy); 22 | } 23 | 24 | int dummy_buffer_enqueue(buffer_t *buf, const char *who) 25 | { 26 | unsigned index = buf->index; 27 | if (write(buf->buf_list->dummy->fds[1], &index, sizeof(index)) != sizeof(index)) { 28 | return -1; 29 | } 30 | return 0; 31 | } 32 | 33 | int dummy_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp) 34 | { 35 | unsigned index = 0; 36 | int n = read(buf_list->dummy->fds[0], &index, sizeof(index)); 37 | if (n != sizeof(index)) { 38 | LOG_INFO(buf_list, "Received invalid result from `read`: %d", n); 39 | return -1; 40 | } 41 | 42 | if (index >= (unsigned)buf_list->nbufs) { 43 | LOG_INFO(buf_list, "Received invalid index from `read`: %d >= %d", index, buf_list->nbufs); 44 | return -1; 45 | } 46 | 47 | *bufp = buf_list->bufs[index]; 48 | return 0; 49 | } 50 | 51 | int dummy_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue) 52 | { 53 | int count_enqueued = buffer_list_count_enqueued(buf_list); 54 | pollfd->fd = buf_list->dummy->fds[0]; // write end 55 | pollfd->events = POLLHUP; 56 | if (can_dequeue && count_enqueued > 0) { 57 | pollfd->events |= POLLIN; 58 | } 59 | pollfd->revents = 0; 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /device/dummy/buffer_list.c: -------------------------------------------------------------------------------- 1 | #include "dummy.h" 2 | #include "device/buffer_list.h" 3 | #include "device/device.h" 4 | #include "util/opts/log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int dummy_buffer_list_open(buffer_list_t *buf_list) 11 | { 12 | int fd = -1; 13 | 14 | buf_list->dummy = calloc(1, sizeof(buffer_list_dummy_t)); 15 | buf_list->dummy->fds[0] = -1; 16 | buf_list->dummy->fds[1] = -1; 17 | 18 | if (!buf_list->do_capture) { 19 | LOG_ERROR(buf_list, "Only capture mode supported"); 20 | } 21 | 22 | if (pipe2(buf_list->dummy->fds, O_DIRECT|O_CLOEXEC) < 0) { 23 | LOG_INFO(buf_list, "Cannot open `pipe2`."); 24 | return -1; 25 | } 26 | 27 | fd = open(buf_list->dev->path, O_RDWR|O_NONBLOCK); 28 | if (fd < 0) { 29 | LOG_ERROR(buf_list, "Can't open device: %s", buf_list->dev->path); 30 | } 31 | 32 | struct stat st; 33 | if (fstat(fd, &st) < 0) { 34 | LOG_ERROR(buf_list, "Can't get fstat: %s", buf_list->dev->path); 35 | } 36 | 37 | buf_list->dummy->data = malloc(st.st_size); 38 | if (!buf_list->dummy->data) { 39 | LOG_ERROR(buf_list, "Can't allocate %ld bytes for %s", st.st_size, buf_list->dev->path); 40 | } 41 | 42 | buf_list->dummy->length = read(fd, buf_list->dummy->data, st.st_size); 43 | if (!buf_list->dummy->data) { 44 | LOG_ERROR(buf_list, "Can't read %ld bytes for %s. Only read %zu.", st.st_size, buf_list->dev->path, buf_list->dummy->length); 45 | } 46 | 47 | close(fd); 48 | return buf_list->fmt.nbufs; 49 | 50 | error: 51 | close(fd); 52 | return -1; 53 | } 54 | 55 | void dummy_buffer_list_close(buffer_list_t *buf_list) 56 | { 57 | if (buf_list->dummy) { 58 | close(buf_list->dummy->fds[0]); 59 | close(buf_list->dummy->fds[1]); 60 | free(buf_list->dummy->data); 61 | } 62 | 63 | free(buf_list->dummy); 64 | } 65 | 66 | int dummy_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on) 67 | { 68 | return 0; 69 | } 70 | -------------------------------------------------------------------------------- /device/dummy/device.c: -------------------------------------------------------------------------------- 1 | #include "dummy.h" 2 | #include "device/device.h" 3 | 4 | #include 5 | 6 | int dummy_device_open(device_t *dev) 7 | { 8 | dev->opts.allow_dma = false; 9 | dev->dummy = calloc(1, sizeof(device_dummy_t)); 10 | return 0; 11 | } 12 | 13 | void dummy_device_close(device_t *dev) 14 | { 15 | free(dev->dummy); 16 | } 17 | 18 | int dummy_device_video_force_key(device_t *dev) 19 | { 20 | return -1; 21 | } 22 | 23 | int dummy_device_set_fps(device_t *dev, int desired_fps) 24 | { 25 | return -1; 26 | } 27 | 28 | int dummy_device_set_option(device_t *dev, const char *key, const char *value) 29 | { 30 | return -1; 31 | } 32 | -------------------------------------------------------------------------------- /device/dummy/dummy.c: -------------------------------------------------------------------------------- 1 | #include "dummy.h" 2 | 3 | #include "device/device.h" 4 | 5 | device_hw_t dummy_device_hw = { 6 | .device_open = dummy_device_open, 7 | .device_close = dummy_device_close, 8 | .device_video_force_key = dummy_device_video_force_key, 9 | .device_set_fps = dummy_device_set_fps, 10 | .device_set_option = dummy_device_set_option, 11 | 12 | .buffer_open = dummy_buffer_open, 13 | .buffer_close = dummy_buffer_close, 14 | .buffer_enqueue = dummy_buffer_enqueue, 15 | 16 | .buffer_list_dequeue = dummy_buffer_list_dequeue, 17 | .buffer_list_pollfd = dummy_buffer_list_pollfd, 18 | .buffer_list_open = dummy_buffer_list_open, 19 | .buffer_list_close = dummy_buffer_list_close, 20 | .buffer_list_set_stream = dummy_buffer_list_set_stream 21 | }; 22 | 23 | device_t *device_dummy_open(const char *name, const char *path) 24 | { 25 | return device_open(name, path, &dummy_device_hw); 26 | } 27 | -------------------------------------------------------------------------------- /device/dummy/dummy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct buffer_s buffer_t; 8 | typedef struct buffer_list_s buffer_list_t; 9 | typedef struct device_s device_t; 10 | struct pollfd; 11 | 12 | typedef struct device_dummy_s { 13 | } device_dummy_t; 14 | 15 | typedef struct buffer_list_dummy_s { 16 | int fds[2]; 17 | void *data; 18 | size_t length; 19 | } buffer_list_dummy_t; 20 | 21 | typedef struct buffer_dummy_s { 22 | } buffer_dummy_t; 23 | 24 | int dummy_device_open(device_t *dev); 25 | void dummy_device_close(device_t *dev); 26 | int dummy_device_video_force_key(device_t *dev); 27 | int dummy_device_set_fps(device_t *dev, int desired_fps); 28 | int dummy_device_set_option(device_t *dev, const char *key, const char *value); 29 | 30 | int dummy_buffer_open(buffer_t *buf); 31 | void dummy_buffer_close(buffer_t *buf); 32 | int dummy_buffer_enqueue(buffer_t *buf, const char *who); 33 | int dummy_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp); 34 | int dummy_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue); 35 | 36 | int dummy_buffer_list_open(buffer_list_t *buf_list); 37 | void dummy_buffer_list_close(buffer_list_t *buf_list); 38 | int dummy_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on); 39 | -------------------------------------------------------------------------------- /device/libcamera/buffer.cc: -------------------------------------------------------------------------------- 1 | #ifdef USE_LIBCAMERA 2 | #include "libcamera.hh" 3 | 4 | #include 5 | 6 | int libcamera_buffer_open(buffer_t *buf) 7 | { 8 | buf->libcamera = new buffer_libcamera_t{}; 9 | buf->libcamera->request = buf->buf_list->dev->libcamera->camera->createRequest(buf->index); 10 | if (!buf->libcamera->request) { 11 | LOG_INFO(buf, "Can't create request"); 12 | return -1; 13 | } 14 | 15 | auto &configurations = buf->buf_list->dev->libcamera->configuration; 16 | auto &configuration = configurations->at(buf->buf_list->index); 17 | auto stream = configuration.stream(); 18 | const std::vector> &buffers = 19 | buf->buf_list->dev->libcamera->allocator->buffers(stream); 20 | auto const &buffer = buffers[buf->index]; 21 | 22 | if (buf->libcamera->request->addBuffer(stream, buffer.get()) < 0) { 23 | LOG_ERROR(buf, "Can't set buffer for request"); 24 | } 25 | if (buf->start) { 26 | LOG_ERROR(buf, "Too many streams."); 27 | } 28 | 29 | if (buffer->planes().empty()) { 30 | LOG_ERROR(buf, "No planes allocated"); 31 | } 32 | 33 | { 34 | uint64_t offset = buffer->planes()[0].offset; 35 | uint64_t length = 0; 36 | libcamera::SharedFD dma_fd = buffer->planes()[0].fd; 37 | 38 | // Require that planes are continuous 39 | for (auto const &plane : buffer->planes()) { 40 | if (plane.fd != dma_fd) { 41 | LOG_ERROR(buf, "Plane does not share FD: fd=%d, expected=%d", plane.fd.get(), dma_fd.get()); 42 | } 43 | 44 | if (offset + length != plane.offset) { 45 | LOG_ERROR(buf, "Plane is not continuous: offset=%u, expected=%" PRIu64, plane.offset, offset + length); 46 | } 47 | 48 | length += plane.length; 49 | } 50 | 51 | buf->start = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, dma_fd.get(), offset); 52 | if (!buf->start || buf->start == MAP_FAILED) { 53 | LOG_ERROR(buf, "Failed to mmap DMA buffer"); 54 | } 55 | 56 | buf->dma_fd = dma_fd.get(); 57 | buf->length = length; 58 | 59 | LOG_DEBUG(buf, "Mapped buffer: start=%p, length=%zu, fd=%d, planes=%zu", 60 | buf->start, buf->length, buf->dma_fd, buffer->planes().size()); 61 | } 62 | 63 | return 0; 64 | 65 | error: 66 | return -1; 67 | } 68 | 69 | void libcamera_buffer_close(buffer_t *buf) 70 | { 71 | if (buf->libcamera) { 72 | delete buf->libcamera; 73 | buf->libcamera = NULL; 74 | } 75 | } 76 | 77 | int libcamera_buffer_enqueue(buffer_t *buf, const char *who) 78 | { 79 | auto &request = buf->libcamera->request; 80 | auto const &camera = buf->buf_list->dev->libcamera->camera; 81 | 82 | request->reuse(libcamera::Request::ReuseBuffers); 83 | request->controls() = std::move(buf->buf_list->dev->libcamera->controls); 84 | 85 | if (camera->queueRequest(buf->libcamera->request.get()) < 0) { 86 | LOG_ERROR(buf, "Can't queue buffer."); 87 | } 88 | libcamera_device_apply_controls(buf->buf_list->dev); 89 | return 0; 90 | 91 | error: 92 | return -1; 93 | } 94 | 95 | void libcamera_buffer_dump_metadata(buffer_t *buf) 96 | { 97 | auto &metadata = buf->libcamera->request->metadata(); 98 | auto idMap = metadata.idMap(); 99 | 100 | for (auto const &control : metadata) { 101 | if (!control.first) 102 | continue; 103 | 104 | auto control_id = control.first; 105 | auto control_value = control.second; 106 | std::string control_id_name = ""; 107 | 108 | if (auto control_id_info = idMap ? idMap->at(control_id) : NULL) { 109 | control_id_name = control_id_info->name(); 110 | } 111 | 112 | LOG_VERBOSE(buf, "Metadata: %s (%08x, type=%d): %s", 113 | control_id_name.c_str(), control_id, control_value.type(), control_value.toString().c_str()); 114 | } 115 | } 116 | 117 | void buffer_list_libcamera_t::libcamera_buffer_list_dequeued(libcamera::Request *request) 118 | { 119 | if (request->status() == libcamera::Request::RequestComplete) { 120 | unsigned index = request->cookie(); 121 | if (write(buf_list->libcamera->fds[1], &index, sizeof(index)) == sizeof(index)) { 122 | return; 123 | } 124 | } 125 | 126 | // put back into queue, as it failed 127 | buf_list->dev->libcamera->camera->queueRequest(request); 128 | } 129 | 130 | int libcamera_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp) 131 | { 132 | unsigned index = 0; 133 | int n = read(buf_list->libcamera->fds[0], &index, sizeof(index)); 134 | if (n != sizeof(index)) { 135 | LOG_INFO(buf_list, "Received invalid result from `read`: %d", n); 136 | return -1; 137 | } 138 | 139 | if (index >= (unsigned)buf_list->nbufs) { 140 | LOG_INFO(buf_list, "Received invalid index from `read`: %d >= %d", index, buf_list->nbufs); 141 | return -1; 142 | } 143 | 144 | *bufp = buf_list->bufs[index]; 145 | 146 | std::optional sensor_timestamp((*bufp)->libcamera->request->metadata(). 147 | get(libcamera::controls::SensorTimestamp)); 148 | uint64_t sensor_timestamp_us = sensor_timestamp.value_or(0) / 1000; 149 | uint64_t boot_time_us = get_time_us(CLOCK_BOOTTIME, NULL, NULL, 0); 150 | 151 | uint64_t now_us = get_monotonic_time_us(NULL, NULL); 152 | 153 | (*bufp)->captured_time_us = now_us - (boot_time_us - sensor_timestamp_us); 154 | (*bufp)->used = 0; 155 | 156 | for (auto &bufferMap : (*bufp)->libcamera->request->buffers()) { 157 | auto frameBuffer = bufferMap.second; 158 | 159 | for (auto const &plane : frameBuffer->metadata().planes()) { 160 | (*bufp)->used += plane.bytesused; 161 | } 162 | } 163 | 164 | if (index == 0) { 165 | libcamera_buffer_dump_metadata(*bufp); 166 | } 167 | return 0; 168 | } 169 | 170 | int libcamera_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue) 171 | { 172 | int count_enqueued = buffer_list_count_enqueued(buf_list); 173 | pollfd->fd = buf_list->libcamera->fds[0]; // write end 174 | pollfd->events = POLLHUP; 175 | if (can_dequeue && count_enqueued > 0) { 176 | pollfd->events |= POLLIN; 177 | } 178 | pollfd->revents = 0; 179 | return 0; 180 | } 181 | #endif // USE_LIBCAMERA 182 | -------------------------------------------------------------------------------- /device/libcamera/buffer_list.cc: -------------------------------------------------------------------------------- 1 | #ifdef USE_LIBCAMERA 2 | #include "libcamera.hh" 3 | 4 | struct libcamera_format_s { 5 | unsigned fourcc; 6 | libcamera::PixelFormat pixelFormat; 7 | }; 8 | 9 | static libcamera_format_s libcamera_formats[] = { 10 | { V4L2_PIX_FMT_RGB24, libcamera::formats::RGB888 }, 11 | { V4L2_PIX_FMT_BGR24, libcamera::formats::BGR888 }, 12 | { 0 }, 13 | }; 14 | 15 | libcamera::PixelFormat libcamera_from_fourcc(unsigned fourcc) 16 | { 17 | for (int i = 0; libcamera_formats[i].fourcc; i++) { 18 | if (libcamera_formats[i].fourcc == fourcc) { 19 | return libcamera_formats[i].pixelFormat; 20 | } 21 | } 22 | 23 | return libcamera::PixelFormat(fourcc); 24 | } 25 | 26 | unsigned libcamera_to_fourcc(libcamera::PixelFormat pixelFormat) 27 | { 28 | for (int i = 0; libcamera_formats[i].fourcc; i++) { 29 | if (libcamera_formats[i].pixelFormat == pixelFormat) { 30 | return libcamera_formats[i].fourcc; 31 | } 32 | } 33 | 34 | return pixelFormat.fourcc(); 35 | } 36 | 37 | int libcamera_buffer_list_open(buffer_list_t *buf_list) 38 | { 39 | if (!buf_list->do_capture) { 40 | LOG_INFO(buf_list, "Only capture mode is supported."); 41 | return -1; 42 | } 43 | 44 | if (!buf_list->do_mmap) { 45 | LOG_INFO(buf_list, "Only mmap buffers are supported."); 46 | return -1; 47 | } 48 | 49 | buf_list->libcamera = new buffer_list_libcamera_t{}; 50 | buf_list->libcamera->buf_list = buf_list; 51 | buf_list->libcamera->fds[0] = -1; 52 | buf_list->libcamera->fds[1] = -1; 53 | 54 | if (pipe2(buf_list->libcamera->fds, O_DIRECT|O_CLOEXEC) < 0) { 55 | LOG_INFO(buf_list, "Cannot open `pipe2`."); 56 | return -1; 57 | } 58 | 59 | auto &configurations = buf_list->dev->libcamera->configuration; 60 | 61 | // add new configuration based on a buffer 62 | { 63 | libcamera::StreamRole role = libcamera::StreamRole::StillCapture; 64 | 65 | switch(buf_list->fmt.type) { 66 | case BUFFER_TYPE_RAW: 67 | role = libcamera::StreamRole::Raw; 68 | break; 69 | 70 | case BUFFER_TYPE_VIDEO: 71 | role = libcamera::StreamRole::VideoRecording; 72 | break; 73 | 74 | default: 75 | role = libcamera::StreamRole::StillCapture; 76 | break; 77 | } 78 | 79 | auto newConfigurations = buf_list->dev->libcamera->camera->generateConfiguration({ role }); 80 | configurations->addConfiguration(newConfigurations->at(0)); 81 | } 82 | 83 | if (buf_list->index >= (int)configurations->size()) { 84 | LOG_INFO(buf_list, "Not enough configurations."); 85 | return -1; 86 | } 87 | 88 | auto &configuration = configurations->at(buf_list->index); 89 | configuration.size = libcamera::Size(buf_list->fmt.width, buf_list->fmt.height); 90 | if (buf_list->fmt.format) { 91 | configuration.pixelFormat = libcamera_from_fourcc(buf_list->fmt.format); 92 | } 93 | if (buf_list->fmt.bytesperline > 0) { 94 | configuration.stride = buf_list->fmt.bytesperline; 95 | } else { 96 | configuration.stride = 0; 97 | } 98 | if (buf_list->fmt.nbufs > 0) { 99 | configuration.bufferCount = buf_list->fmt.nbufs; 100 | } 101 | if (configurations->validate() == libcamera::CameraConfiguration::Invalid) { 102 | LOG_ERROR(buf_list, "Camera configuration invalid"); 103 | } 104 | #ifdef LIBCAMERA_USES_ORIENTATION 105 | if (buf_list->dev->libcamera->vflip && buf_list->dev->libcamera->hflip) { 106 | configurations->orientation = libcamera::Orientation::Rotate180; 107 | } else if (buf_list->dev->libcamera->vflip) { 108 | configurations->orientation = libcamera::Orientation::Rotate180Mirror; 109 | } else if (buf_list->dev->libcamera->hflip) { 110 | configurations->orientation = libcamera::Orientation::Rotate0Mirror; 111 | } 112 | #else // LIBCAMERA_USES_ORIENTATION 113 | if (buf_list->dev->libcamera->vflip) { 114 | configurations->transform |= libcamera::Transform::VFlip; 115 | } 116 | if (buf_list->dev->libcamera->hflip) { 117 | configurations->transform |= libcamera::Transform::HFlip; 118 | } 119 | if (!!(configurations->transform & libcamera::Transform::Transpose)) { 120 | LOG_ERROR(buf_list, "Transformation requiring transpose not supported"); 121 | } 122 | #endif // LIBCAMERA_USES_ORIENTATION 123 | 124 | if (buf_list->dev->libcamera->camera->configure(configurations.get()) < 0) { 125 | LOG_ERROR(buf_list, "Failed to configure camera"); 126 | } 127 | 128 | buf_list->fmt.width = configuration.size.width; 129 | buf_list->fmt.height = configuration.size.height; 130 | buf_list->fmt.format = libcamera_to_fourcc(configuration.pixelFormat); 131 | buf_list->fmt.bytesperline = configuration.stride; 132 | return 0; 133 | 134 | error: 135 | return -1; 136 | } 137 | 138 | int libcamera_buffer_list_alloc_buffers(buffer_list_t *buf_list) 139 | { 140 | auto &configurations = buf_list->dev->libcamera->configuration; 141 | auto &configuration = configurations->at(buf_list->index); 142 | 143 | if (buf_list->dev->libcamera->allocator->allocate(configuration.stream()) < 0) { 144 | LOG_ERROR(buf_list, "Can't allocate buffers"); 145 | } 146 | 147 | { 148 | int allocated = buf_list->dev->libcamera->allocator->buffers( 149 | configuration.stream()).size(); 150 | return std::min(buf_list->fmt.nbufs, allocated); 151 | } 152 | 153 | error: 154 | return -1; 155 | } 156 | 157 | void libcamera_buffer_list_free_buffers(buffer_list_t *buf_list) 158 | { 159 | auto &configurations = buf_list->dev->libcamera->configuration; 160 | auto &configuration = configurations->at(buf_list->index); 161 | buf_list->dev->libcamera->allocator->free(configuration.stream()); 162 | } 163 | 164 | void libcamera_buffer_list_close(buffer_list_t *buf_list) 165 | { 166 | if (buf_list->libcamera) { 167 | close(buf_list->libcamera->fds[0]); 168 | close(buf_list->libcamera->fds[1]); 169 | 170 | delete buf_list->libcamera; 171 | buf_list->libcamera = NULL; 172 | } 173 | } 174 | 175 | int libcamera_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on) 176 | { 177 | if (do_on) { 178 | buf_list->dev->libcamera->camera->requestCompleted.connect( 179 | buf_list->libcamera, &buffer_list_libcamera_t::libcamera_buffer_list_dequeued); 180 | 181 | if (buf_list->dev->libcamera->camera->start(&buf_list->dev->libcamera->controls) < 0) { 182 | LOG_ERROR(buf_list, "Failed to start camera."); 183 | } 184 | 185 | libcamera_device_apply_controls(buf_list->dev); 186 | } else { 187 | buf_list->dev->libcamera->camera->requestCompleted.disconnect( 188 | buf_list->libcamera, &buffer_list_libcamera_t::libcamera_buffer_list_dequeued); 189 | 190 | if (buf_list->dev->libcamera->camera->stop() < 0) { 191 | LOG_ERROR(buf_list, "Failed to stop camera."); 192 | } 193 | } 194 | 195 | return 0; 196 | 197 | error: 198 | return -1; 199 | } 200 | #endif // USE_LIBCAMERA 201 | -------------------------------------------------------------------------------- /device/libcamera/device.cc: -------------------------------------------------------------------------------- 1 | #ifdef USE_LIBCAMERA 2 | #include "libcamera.hh" 3 | 4 | void libcamera_print_cameras(device_t *dev) 5 | { 6 | if (dev->libcamera->camera_manager->cameras().size()) { 7 | LOG_INFO(dev, "Available cameras (%zu)", dev->libcamera->camera_manager->cameras().size()); 8 | 9 | for (auto const &camera : dev->libcamera->camera_manager->cameras()) { 10 | LOG_INFO(dev, "- %s", camera->id().c_str()); 11 | } 12 | } else { 13 | LOG_INFO(dev, "No available cameras"); 14 | } 15 | } 16 | 17 | int libcamera_device_open(device_t *dev) 18 | { 19 | dev->libcamera = new device_libcamera_t{}; 20 | 21 | dev->libcamera->camera_manager = std::make_shared(); 22 | int ret = dev->libcamera->camera_manager->start(); 23 | if (ret < 0) { 24 | LOG_ERROR(dev, "Cannot start camera_manager."); 25 | } 26 | 27 | if (strlen(dev->path) == 0) { 28 | if (dev->libcamera->camera_manager->cameras().size() != 1) { 29 | libcamera_print_cameras(dev); 30 | LOG_ERROR(dev, "Too many cameras was found. Cannot select default."); 31 | } 32 | 33 | dev->libcamera->camera = dev->libcamera->camera_manager->cameras().front(); 34 | } else { 35 | dev->libcamera->camera = dev->libcamera->camera_manager->get(dev->path); 36 | 37 | // Do partial match of camera 38 | if (!dev->libcamera->camera) { 39 | for (auto& camera : dev->libcamera->camera_manager->cameras()) { 40 | if (!strstr(camera->id().c_str(), dev->path)) 41 | continue; 42 | 43 | if (dev->libcamera->camera) { 44 | libcamera_print_cameras(dev); 45 | LOG_ERROR(dev, "Many cameras matching found."); 46 | } 47 | dev->libcamera->camera = camera; 48 | } 49 | } 50 | } 51 | 52 | if (!dev->libcamera->camera) { 53 | libcamera_print_cameras(dev); 54 | LOG_ERROR(dev, "Camera `%s` was not found.", dev->path); 55 | } 56 | 57 | if (dev->libcamera->camera->acquire()) { 58 | LOG_ERROR(dev, "Failed to acquire `%s` camera.", dev->libcamera->camera->id().c_str()); 59 | } 60 | 61 | dev->libcamera->configuration = dev->libcamera->camera->generateConfiguration(); 62 | 63 | dev->libcamera->allocator = std::make_shared( 64 | dev->libcamera->camera); 65 | 66 | LOG_INFO(dev, "Device path=%s opened", dev->libcamera->camera->id().c_str()); 67 | return 0; 68 | 69 | error: 70 | return -1; 71 | } 72 | 73 | void libcamera_device_close(device_t *dev) 74 | { 75 | if (dev->libcamera) { 76 | if (dev->libcamera->camera) { 77 | dev->libcamera->camera->release(); 78 | } 79 | 80 | delete dev->libcamera; 81 | dev->libcamera = NULL; 82 | } 83 | } 84 | 85 | int libcamera_device_set_fps(device_t *dev, int desired_fps) 86 | { 87 | int64_t frame_time = desired_fps ? 1000000 / desired_fps : 0; 88 | dev->libcamera->controls.set(libcamera::controls::FrameDurationLimits, libcamera::Span({ frame_time, frame_time })); 89 | return 0; 90 | } 91 | 92 | int libcamera_device_set_rotation(device_t *dev, bool vflip, bool hflip) 93 | { 94 | dev->libcamera->vflip = vflip; 95 | dev->libcamera->hflip = hflip; 96 | return 0; 97 | } 98 | 99 | void libcamera_device_apply_controls(device_t *dev) 100 | { 101 | auto &controls = dev->libcamera->controls; 102 | auto &applied_controls = dev->libcamera->applied_controls; 103 | 104 | for (auto &control : controls) { 105 | applied_controls.set(control.first, control.second); 106 | } 107 | 108 | controls.clear(); 109 | } 110 | #endif // USE_LIBCAMERA 111 | -------------------------------------------------------------------------------- /device/libcamera/fake_camera.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "util/opts/log.h" 12 | 13 | // This is special code to force `libcamera` to think that given sensor is different 14 | // ex.: fake `arducam_64mp` to be run as `imx519` 15 | 16 | void fake_camera_sensor(struct media_v2_topology *topology) 17 | { 18 | struct media_v2_entity *ents = (struct media_v2_entity *)(intptr_t)topology->ptr_entities; 19 | if (!ents) { 20 | return; 21 | } 22 | 23 | const char *fake_camera = getenv("FAKE_CAMERA_SENSOR"); 24 | if (!fake_camera) { 25 | return; 26 | } 27 | 28 | char source[256]; 29 | strcpy(source, fake_camera); 30 | 31 | char *target = strstr(source, "="); 32 | if (!target) { 33 | return; 34 | } 35 | 36 | *target++ = ' '; 37 | 38 | for (int i = 0; i < topology->num_entities; i++) { 39 | if (strncmp(ents[i].name, source, target-source) != 0) { 40 | continue; 41 | } 42 | 43 | char name[sizeof(ents[i].name)]; 44 | strcpy(name, target); 45 | strcat(name, " "); 46 | strcat(name, ents[i].name + (target-source)); 47 | LOG_INFO(NULL, "Rewrote entity (%d): %s => %s", i, ents[i].name, name); 48 | strcpy(ents[i].name, name); 49 | } 50 | } 51 | 52 | int ioctl (int fd, unsigned long int req, ...) 53 | { 54 | void *arg; 55 | va_list ap; 56 | va_start(ap, req); 57 | arg = va_arg(ap, void *); 58 | va_end(ap); 59 | int ret = syscall(SYS_ioctl, fd, req, arg); 60 | if (!ret && req == MEDIA_IOC_G_TOPOLOGY) { 61 | fake_camera_sensor(arg); 62 | } 63 | 64 | return ret; 65 | } 66 | -------------------------------------------------------------------------------- /device/libcamera/libcamera.cc: -------------------------------------------------------------------------------- 1 | #include "libcamera.hh" 2 | 3 | #ifdef USE_LIBCAMERA 4 | device_hw_t libcamera_device_hw = { 5 | .device_open = libcamera_device_open, 6 | .device_close = libcamera_device_close, 7 | .device_dump_options = libcamera_device_dump_options, 8 | .device_dump_options2 = libcamera_device_dump_options2, 9 | .device_set_fps = libcamera_device_set_fps, 10 | .device_set_rotation = libcamera_device_set_rotation, 11 | .device_set_option = libcamera_device_set_option, 12 | 13 | .buffer_open = libcamera_buffer_open, 14 | .buffer_close = libcamera_buffer_close, 15 | .buffer_enqueue = libcamera_buffer_enqueue, 16 | 17 | .buffer_list_dequeue = libcamera_buffer_list_dequeue, 18 | .buffer_list_pollfd = libcamera_buffer_list_pollfd, 19 | .buffer_list_open = libcamera_buffer_list_open, 20 | .buffer_list_close = libcamera_buffer_list_close, 21 | .buffer_list_alloc_buffers = libcamera_buffer_list_alloc_buffers, 22 | .buffer_list_free_buffers = libcamera_buffer_list_free_buffers, 23 | .buffer_list_set_stream = libcamera_buffer_list_set_stream 24 | }; 25 | 26 | extern "C" device_t *device_libcamera_open(const char *name, const char *path) 27 | { 28 | return device_open(name, path, &libcamera_device_hw); 29 | } 30 | #else // USE_LIBCAMERA 31 | extern "C" device_t *device_libcamera_open(const char *name, const char *path) 32 | { 33 | LOG_INFO(NULL, "libcamera is not supported"); 34 | return NULL; 35 | } 36 | #endif // USE_LIBCAMERA 37 | -------------------------------------------------------------------------------- /device/libcamera/libcamera.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "version.h" 11 | #include "device/device.h" 12 | #include "device/buffer_list.h" 13 | #include "device/buffer.h" 14 | #include "util/opts/log.h" 15 | #include "util/opts/fourcc.h" 16 | #include "util/opts/control.h" 17 | }; 18 | 19 | #ifdef USE_LIBCAMERA 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #ifdef LIBCAMERA_USES_ORIENTATION 34 | #include 35 | #else // LIBCAMERA_USES_ORIENTATION 36 | #include 37 | #endif // LIBCAMERA_USES_ORIENTATION 38 | 39 | typedef struct buffer_s buffer_t; 40 | typedef struct buffer_list_s buffer_list_t; 41 | typedef struct device_s device_t; 42 | struct pollfd; 43 | 44 | typedef struct device_libcamera_s { 45 | std::shared_ptr camera_manager; 46 | std::shared_ptr camera; 47 | std::shared_ptr configuration; 48 | std::shared_ptr allocator; 49 | libcamera::ControlList controls; 50 | libcamera::ControlList applied_controls; 51 | bool vflip, hflip; 52 | } device_libcamera_t; 53 | 54 | typedef struct buffer_list_libcamera_s { 55 | buffer_list_t *buf_list; 56 | int fds[2]; 57 | 58 | void libcamera_buffer_list_dequeued(libcamera::Request *request); 59 | } buffer_list_libcamera_t; 60 | 61 | typedef struct buffer_libcamera_s { 62 | std::shared_ptr request; 63 | } buffer_libcamera_t; 64 | 65 | int libcamera_device_open(device_t *dev); 66 | void libcamera_device_close(device_t *dev); 67 | void libcamera_device_dump_options(device_t *dev, FILE *stream); 68 | int libcamera_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque); 69 | int libcamera_device_set_fps(device_t *dev, int desired_fps); 70 | int libcamera_device_set_rotation(device_t *dev, bool vflip, bool hflip); 71 | int libcamera_device_set_option(device_t *dev, const char *key, const char *value); 72 | void libcamera_device_apply_controls(device_t *dev); 73 | 74 | int libcamera_buffer_open(buffer_t *buf); 75 | void libcamera_buffer_close(buffer_t *buf); 76 | int libcamera_buffer_enqueue(buffer_t *buf, const char *who); 77 | void libcamera_buffer_list_dequeued(buffer_list_t *buf_list, libcamera::Request *request); 78 | int libcamera_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp); 79 | int libcamera_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue); 80 | 81 | int libcamera_buffer_list_open(buffer_list_t *buf_list); 82 | void libcamera_buffer_list_close(buffer_list_t *buf_list); 83 | int libcamera_buffer_list_alloc_buffers(buffer_list_t *buf_list); 84 | void libcamera_buffer_list_free_buffers(buffer_list_t *buf_list); 85 | int libcamera_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on); 86 | #endif // USE_LIBCAMERA 87 | -------------------------------------------------------------------------------- /device/links.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LINKS_LOOP_INTERVAL 100 7 | #define MAX_OUTPUT_LISTS 10 8 | #define MAX_CALLBACKS 10 9 | 10 | typedef struct buffer_s buffer_t; 11 | typedef struct buffer_list_s buffer_list_t; 12 | typedef struct buffer_lock_s buffer_lock_t; 13 | typedef struct link_s link_t; 14 | 15 | typedef void (*link_on_buffer)(buffer_t *buf); 16 | typedef bool (*link_check_streaming)(); 17 | 18 | typedef struct link_callbacks_s { 19 | const char *name; 20 | link_on_buffer on_buffer; 21 | link_check_streaming check_streaming; 22 | buffer_lock_t *buf_lock; 23 | } link_callbacks_t; 24 | 25 | typedef struct link_s { 26 | buffer_list_t *capture_list; 27 | buffer_list_t *output_lists[MAX_OUTPUT_LISTS]; 28 | int n_output_lists; 29 | link_callbacks_t callbacks[MAX_CALLBACKS]; 30 | int n_callbacks; 31 | } link_t; 32 | 33 | int links_loop(link_t *all_links, bool force_active, bool *running); 34 | void links_dump(link_t *all_links); 35 | -------------------------------------------------------------------------------- /device/v4l2/buffer.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | #include "device/buffer.h" 3 | #include "device/buffer_list.h" 4 | #include "device/device.h" 5 | #include "util/opts/log.h" 6 | 7 | int v4l2_buffer_open(buffer_t *buf) 8 | { 9 | struct v4l2_buffer v4l2_buf = {0}; 10 | struct v4l2_plane v4l2_plane = {0}; 11 | 12 | buffer_list_t *buf_list = buf->buf_list; 13 | 14 | buf->v4l2 = calloc(1, sizeof(buffer_v4l2_t)); 15 | 16 | v4l2_buf.type = buf_list->v4l2->type; 17 | v4l2_buf.index = buf->index; 18 | 19 | if (buf_list->v4l2->do_mplanes) { 20 | v4l2_buf.length = 1; 21 | v4l2_buf.m.planes = &v4l2_plane; 22 | v4l2_plane.data_offset = 0; 23 | } 24 | 25 | if (buf_list->do_mmap) { 26 | v4l2_buf.memory = V4L2_MEMORY_MMAP; 27 | } else { 28 | v4l2_buf.memory = V4L2_MEMORY_DMABUF; 29 | } 30 | 31 | ERR_IOCTL(buf_list, buf_list->v4l2->dev_fd, VIDIOC_QUERYBUF, &v4l2_buf, "Cannot query buffer %d", buf->index); 32 | 33 | uint64_t mem_offset = 0; 34 | 35 | if (buf_list->v4l2->do_mplanes) { 36 | mem_offset = v4l2_plane.m.mem_offset; 37 | buf->length = v4l2_plane.length; 38 | } else { 39 | mem_offset = v4l2_buf.m.offset; 40 | buf->length = v4l2_buf.length; 41 | } 42 | 43 | if (buf_list->do_mmap) { 44 | buf->start = mmap(NULL, buf->length, PROT_READ | PROT_WRITE, MAP_SHARED, buf_list->v4l2->dev_fd, mem_offset); 45 | if (buf->start == MAP_FAILED) { 46 | goto error; 47 | } 48 | 49 | struct v4l2_exportbuffer v4l2_exp = {0}; 50 | v4l2_exp.type = v4l2_buf.type; 51 | v4l2_exp.index = buf->index; 52 | v4l2_exp.plane = 0; 53 | ERR_IOCTL(buf_list, buf_list->v4l2->dev_fd, VIDIOC_EXPBUF, &v4l2_exp, "Can't export queue buffer=%u to DMA", buf->index); 54 | buf->dma_fd = v4l2_exp.fd; 55 | } 56 | 57 | return 0; 58 | 59 | error: 60 | v4l2_buffer_close(buf); 61 | return -1; 62 | } 63 | 64 | void v4l2_buffer_close(buffer_t *buf) 65 | { 66 | if (buf->start && buf->start != MAP_FAILED) { 67 | munmap(buf->start, buf->length); 68 | buf->start = NULL; 69 | } 70 | if (buf->dma_fd >= 0) { 71 | close(buf->dma_fd); 72 | buf->dma_fd = -1; 73 | } 74 | 75 | free(buf->v4l2); 76 | buf->v4l2 = NULL; 77 | } 78 | 79 | int v4l2_buffer_enqueue(buffer_t *buf, const char *who) 80 | { 81 | struct v4l2_buffer v4l2_buf = {0}; 82 | struct v4l2_plane v4l2_plane = {0}; 83 | 84 | v4l2_buf.type = buf->buf_list->v4l2->type; 85 | v4l2_buf.index = buf->index; 86 | v4l2_buf.flags = 0; 87 | if (buf->flags.is_keyframe) 88 | v4l2_buf.flags |= V4L2_BUF_FLAG_KEYFRAME; 89 | 90 | if (buf->buf_list->do_mmap) { 91 | assert(buf->dma_source == NULL); 92 | v4l2_buf.memory = V4L2_MEMORY_MMAP; 93 | } else { 94 | assert(buf->dma_source != NULL); 95 | v4l2_buf.memory = V4L2_MEMORY_DMABUF; 96 | } 97 | 98 | // update used bytes 99 | if (buf->buf_list->v4l2->do_mplanes) { 100 | v4l2_buf.length = 1; 101 | v4l2_buf.m.planes = &v4l2_plane; 102 | v4l2_plane.bytesused = buf->used; 103 | v4l2_plane.length = buf->length; 104 | v4l2_plane.data_offset = 0; 105 | 106 | if (buf->dma_source) { 107 | assert(!buf->buf_list->do_mmap); 108 | v4l2_plane.m.fd = buf->dma_source->dma_fd; 109 | } 110 | } else { 111 | v4l2_buf.bytesused = buf->used; 112 | 113 | if (buf->dma_source) { 114 | assert(!buf->buf_list->do_mmap); 115 | v4l2_buf.m.fd = buf->dma_source->dma_fd; 116 | } 117 | } 118 | 119 | v4l2_buf.timestamp.tv_sec = buf->captured_time_us / (1000LL * 1000LL); 120 | v4l2_buf.timestamp.tv_usec = buf->captured_time_us % (1000LL * 1000LL); 121 | 122 | ERR_IOCTL(buf, buf->buf_list->v4l2->dev_fd, VIDIOC_QBUF, &v4l2_buf, "Can't queue buffer."); 123 | 124 | return 0; 125 | 126 | error: 127 | return -1; 128 | } 129 | 130 | int v4l2_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp) 131 | { 132 | struct v4l2_buffer v4l2_buf = {0}; 133 | struct v4l2_plane v4l2_plane = {0}; 134 | 135 | v4l2_buf.type = buf_list->v4l2->type; 136 | v4l2_buf.memory = V4L2_MEMORY_MMAP; 137 | 138 | if (buf_list->v4l2->do_mplanes) { 139 | v4l2_buf.length = 1; 140 | v4l2_buf.m.planes = &v4l2_plane; 141 | } 142 | 143 | ERR_IOCTL(buf_list, buf_list->v4l2->dev_fd, VIDIOC_DQBUF, &v4l2_buf, "Can't grab capture buffer (flags=%08x)", v4l2_buf.flags); 144 | 145 | buffer_t *buf = *bufp = buf_list->bufs[v4l2_buf.index]; 146 | if (buf_list->v4l2->do_mplanes) { 147 | buf->used = v4l2_plane.bytesused; 148 | } else { 149 | buf->used = v4l2_buf.bytesused; 150 | } 151 | 152 | buf->v4l2->flags = v4l2_buf.flags; 153 | buf->flags.is_keyframe = (v4l2_buf.flags & V4L2_BUF_FLAG_KEYFRAME) != 0; 154 | buf->flags.is_last = (v4l2_buf.flags & V4L2_BUF_FLAG_LAST) != 0; 155 | buf->captured_time_us = get_time_us(CLOCK_FROM_PARAMS, NULL, &v4l2_buf.timestamp, 0); 156 | return 0; 157 | 158 | error: 159 | return -1; 160 | } 161 | 162 | int v4l2_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue) 163 | { 164 | int count_enqueued = buffer_list_count_enqueued(buf_list); 165 | 166 | // Can something be dequeued? 167 | pollfd->fd = buf_list->v4l2->dev_fd; 168 | pollfd->events = POLLHUP; 169 | if (count_enqueued > 0 && can_dequeue) { 170 | if (buf_list->do_capture) 171 | pollfd->events |= POLLIN; 172 | else 173 | pollfd->events |= POLLOUT; 174 | } 175 | pollfd->revents = 0; 176 | return 0; 177 | } 178 | 179 | void v4l2_buffer_list_check_buffer(buffer_t *buf) 180 | { 181 | struct v4l2_buffer v4l2_buf = {0}; 182 | struct v4l2_plane v4l2_plane = {0}; 183 | 184 | v4l2_buf.type = buf->buf_list->v4l2->type; 185 | v4l2_buf.index = buf->index; 186 | v4l2_buf.flags = 0; 187 | 188 | if (buf->buf_list->v4l2->do_mplanes) { 189 | v4l2_buf.length = 1; 190 | v4l2_buf.m.planes = &v4l2_plane; 191 | v4l2_plane.data_offset = 0; 192 | } 193 | 194 | ERR_IOCTL(buf, buf->buf_list->v4l2->dev_fd, VIDIOC_QUERYBUF, &v4l2_buf, "Can't query buffer"); 195 | 196 | LOG_INFO(buf, "Buffer queried. Queued=%d/%d", 197 | (v4l2_buf.flags & V4L2_BUF_FLAG_QUEUED) != 0, 198 | buf->enqueued 199 | ); 200 | 201 | error:; 202 | } 203 | 204 | void v4l2_buffer_list_check_buffers(buffer_list_t *buf_list) 205 | { 206 | ARRAY_FOREACH(buffer_t*, buf, buf_list->bufs, buf_list->nbufs) { 207 | v4l2_buffer_list_check_buffer(*buf); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /device/v4l2/debug.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | #include "device/buffer_list.h" 3 | #include "device/device.h" 4 | #include "util/opts/log.h" 5 | 6 | int v4l2_buffer_list_refresh_states(buffer_list_t *buf_list) 7 | { 8 | if (!buf_list) { 9 | return -1; 10 | } 11 | 12 | struct v4l2_buffer v4l2_buf = {0}; 13 | struct v4l2_plane v4l2_plane = {0}; 14 | 15 | v4l2_buf.type = buf_list->v4l2->type; 16 | 17 | if (buf_list->v4l2->do_mplanes) { 18 | v4l2_buf.length = 1; 19 | v4l2_buf.m.planes = &v4l2_plane; 20 | } 21 | 22 | if (buf_list->do_mmap) { 23 | v4l2_buf.memory = V4L2_MEMORY_MMAP; 24 | } else { 25 | v4l2_buf.memory = V4L2_MEMORY_DMABUF; 26 | } 27 | 28 | for (int i = 0; i < buf_list->nbufs; i++) { 29 | v4l2_buf.index = i; 30 | 31 | ERR_IOCTL(buf_list, buf_list->v4l2->dev_fd, VIDIOC_QUERYBUF, &v4l2_buf, "Can't query buffer (flags=%08x)", i); 32 | LOG_INFO(buf_list, "Buffer: %d, Flags: %08x. Offset: %d", i, v4l2_buf.flags, 33 | buf_list->v4l2->do_mplanes ? v4l2_plane.m.mem_offset : v4l2_buf.m.offset); 34 | } 35 | 36 | error: 37 | return -1; 38 | } 39 | -------------------------------------------------------------------------------- /device/v4l2/device.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | #include "device/device.h" 3 | #include "util/opts/log.h" 4 | 5 | int v4l2_device_open(device_t *dev) 6 | { 7 | dev->v4l2 = calloc(1, sizeof(device_v4l2_t)); 8 | dev->v4l2->dev_fd = -1; 9 | dev->v4l2->subdev_fd = -1; 10 | 11 | dev->v4l2->dev_fd = open(dev->path, O_RDWR|O_NONBLOCK); 12 | if (dev->v4l2->dev_fd < 0) { 13 | LOG_ERROR(dev, "Can't open device: %s", dev->path); 14 | goto error; 15 | } 16 | 17 | LOG_INFO(dev, "Device path=%s fd=%d opened", dev->path, dev->v4l2->dev_fd); 18 | 19 | LOG_DEBUG(dev, "Querying device capabilities ..."); 20 | struct v4l2_capability v4l2_cap; 21 | ERR_IOCTL(dev, dev->v4l2->dev_fd, VIDIOC_QUERYCAP, &v4l2_cap, "Can't query device capabilities"); 22 | 23 | if (!(v4l2_cap.capabilities & V4L2_CAP_STREAMING)) { 24 | LOG_ERROR(dev, "Device doesn't support streaming IO"); 25 | } 26 | 27 | strcpy(dev->bus_info, (char *)v4l2_cap.bus_info); 28 | dev->v4l2->subdev_fd = v4l2_device_open_v4l2_subdev(dev, 0); 29 | 30 | v4l2_device_query_controls(dev, dev->v4l2->dev_fd); 31 | v4l2_device_query_controls(dev, dev->v4l2->subdev_fd); 32 | return 0; 33 | 34 | error: 35 | return -1; 36 | } 37 | 38 | void v4l2_device_close(device_t *dev) 39 | { 40 | if (dev->v4l2->subdev_fd >= 0) { 41 | close(dev->v4l2->subdev_fd); 42 | } 43 | 44 | if(dev->v4l2->dev_fd >= 0) { 45 | close(dev->v4l2->dev_fd); 46 | } 47 | 48 | free(dev->v4l2); 49 | dev->v4l2 = NULL; 50 | } 51 | 52 | int v4l2_device_video_force_key(device_t *dev) 53 | { 54 | struct v4l2_control ctl = {0}; 55 | ctl.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME; 56 | ctl.value = 1; 57 | LOG_DEBUG(dev, "Forcing keyframe ..."); 58 | ERR_IOCTL(dev, dev->v4l2->dev_fd, VIDIOC_S_CTRL, &ctl, "Can't force keyframe"); 59 | return 0; 60 | 61 | error: 62 | return -1; 63 | } 64 | 65 | int v4l2_device_set_fps(device_t *dev, int desired_fps) 66 | { 67 | struct v4l2_streamparm setfps = {0}; 68 | 69 | if (!dev) { 70 | return -1; 71 | } 72 | 73 | setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 74 | setfps.parm.output.timeperframe.numerator = 1; 75 | setfps.parm.output.timeperframe.denominator = desired_fps; 76 | LOG_DEBUG(dev, "Configuring FPS ..."); 77 | ERR_IOCTL(dev, dev->v4l2->dev_fd, VIDIOC_S_PARM, &setfps, "Can't set FPS"); 78 | return 0; 79 | error: 80 | return -1; 81 | } 82 | -------------------------------------------------------------------------------- /device/v4l2/device_list.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | #include "device/device_list.h" 3 | #include "util/opts/log.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static void device_list_read_formats(int fd, device_info_formats_t *formats, enum v4l2_buf_type buf_type) 11 | { 12 | for (int i = 0; ; ++i) { 13 | struct v4l2_fmtdesc format_desc; 14 | memset(&format_desc, 0, sizeof(format_desc)); 15 | format_desc.type = (enum v4l2_buf_type) buf_type; 16 | format_desc.index = i; 17 | 18 | if (-1 == ioctl(fd, VIDIOC_ENUM_FMT, &format_desc)) { 19 | break; 20 | } 21 | 22 | formats->n++; 23 | formats->formats = realloc(formats->formats, sizeof(formats->formats[0]) * formats->n); 24 | formats->formats[formats->n - 1] = format_desc.pixelformat; 25 | } 26 | } 27 | 28 | static bool device_list_read_dev(device_info_t *info, const char *name) 29 | { 30 | asprintf(&info->path, "/dev/%s", name); 31 | 32 | int fd = open(info->path, O_RDWR|O_NONBLOCK); 33 | if (fd < 0) { 34 | LOG_ERROR(NULL, "Can't open device: %s", info->path); 35 | } 36 | 37 | struct v4l2_capability v4l2_cap; 38 | ERR_IOCTL(info, fd, VIDIOC_QUERYCAP, &v4l2_cap, "Can't query device capabilities"); 39 | info->name = strdup((const char *)v4l2_cap.card); 40 | 41 | if (!(v4l2_cap.capabilities & V4L2_CAP_STREAMING)) { 42 | LOG_VERBOSE(info, "Device (%s) does not support streaming (skipping)", info->path); 43 | goto error; 44 | } else if ((v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) && !(v4l2_cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) { 45 | info->camera = true; 46 | } else if (!(v4l2_cap.capabilities & (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_M2M_MPLANE))) { 47 | LOG_VERBOSE(info, "Device (%s) does not support capture (skipping)", info->path); 48 | goto error; 49 | } else if (!(v4l2_cap.capabilities & (V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE | V4L2_CAP_VIDEO_M2M_MPLANE))) { 50 | LOG_VERBOSE(info, "Device (%s) does not support output (skipping)", info->path); 51 | goto error; 52 | } else if ((v4l2_cap.capabilities & V4L2_CAP_VIDEO_M2M) || (v4l2_cap.capabilities & V4L2_CAP_VIDEO_M2M_MPLANE)) { 53 | info->m2m = true; 54 | } 55 | 56 | device_list_read_formats(fd, &info->capture_formats, V4L2_BUF_TYPE_VIDEO_CAPTURE); 57 | device_list_read_formats(fd, &info->capture_formats, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); 58 | device_list_read_formats(fd, &info->output_formats, V4L2_BUF_TYPE_VIDEO_OUTPUT); 59 | device_list_read_formats(fd, &info->output_formats, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); 60 | close(fd); 61 | 62 | return true; 63 | 64 | error: 65 | free(info->name); 66 | free(info->path); 67 | close(fd); 68 | return false; 69 | } 70 | 71 | device_list_t *device_list_v4l2() 72 | { 73 | DIR *dev = opendir("/dev"); 74 | if (!dev) { 75 | return NULL; 76 | } 77 | 78 | device_list_t *list = calloc(1, sizeof(device_list_t)); 79 | struct dirent *ent; 80 | 81 | while ((ent = readdir(dev)) != NULL) { 82 | if (strstr(ent->d_name, "video") != ent->d_name) { 83 | continue; 84 | } 85 | 86 | device_info_t info = {NULL}; 87 | if (device_list_read_dev(&info, ent->d_name)) { 88 | list->ndevices++; 89 | list->devices = realloc(list->devices, sizeof(info) * list->ndevices); 90 | list->devices[list->ndevices-1] = info; 91 | } 92 | } 93 | 94 | closedir(dev); 95 | 96 | return list; 97 | } 98 | -------------------------------------------------------------------------------- /device/v4l2/device_media.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | 3 | #include "device/device.h" 4 | #include "util/opts/log.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | int v4l2_device_open_media_device(device_t *dev) 14 | { 15 | struct stat st; 16 | if (fstat(dev->v4l2->dev_fd, &st) < 0) { 17 | LOG_VERBOSE(dev, "Cannot get fstat"); 18 | return -1; 19 | } 20 | if (~st.st_mode & S_IFCHR) { 21 | LOG_VERBOSE(dev, "FD is not char"); 22 | return -1; 23 | } 24 | 25 | char path[300]; 26 | sprintf(path, "/sys/dev/char/%d:%d/device", major(st.st_rdev), minor(st.st_rdev)); 27 | 28 | struct dirent **namelist; 29 | int n = scandir(path, &namelist, NULL, NULL); 30 | if (n < 0) { 31 | LOG_VERBOSE(dev, "Cannot scan: %s", path); 32 | return -1; 33 | } 34 | 35 | int ret = -1; 36 | while (n--) { 37 | if (ret == -1 && strstr(namelist[n]->d_name, "media") == namelist[n]->d_name) { 38 | path[0] = 0; 39 | sprintf(path, "/dev/%s", namelist[n]->d_name); 40 | ret = open(path, O_RDWR); 41 | if (ret >= 0) { 42 | LOG_VERBOSE(dev, "Opened '%s' (fd=%d)", path, ret); 43 | } 44 | } 45 | 46 | free(namelist[n]); 47 | } 48 | free(namelist); 49 | 50 | return ret; 51 | } 52 | 53 | int v4l2_device_open_v4l2_subdev(device_t *dev, int subdev) 54 | { 55 | int media_fd = -1; 56 | unsigned int last_id = 0; 57 | int ret = -1; 58 | 59 | media_fd = v4l2_device_open_media_device(dev); 60 | if (media_fd < 0) { 61 | LOG_VERBOSE(dev, "Cannot find media controller"); 62 | return -1; 63 | } 64 | 65 | for (;;) { 66 | struct media_entity_desc entity = { 67 | .id = MEDIA_ENT_ID_FLAG_NEXT | last_id, 68 | }; 69 | 70 | int rc = ioctl(media_fd, MEDIA_IOC_ENUM_ENTITIES, &entity); 71 | if (rc < 0 && errno == EINVAL) { 72 | break; 73 | } 74 | 75 | if (rc < 0) { 76 | goto error; 77 | } 78 | 79 | last_id = entity.id; 80 | char path[256]; 81 | sprintf(path, "/sys/dev/char/%d:%d", entity.dev.major, entity.dev.minor); 82 | 83 | char link[256]; 84 | if ((rc = readlink(path, link, sizeof(link)-1)) < 0) { 85 | LOG_VERBOSE(dev, "Cannot readlink '%s'", path); 86 | } 87 | link[rc] = 0; 88 | 89 | char *last = strrchr(link, '/'); 90 | if (!last) { 91 | LOG_VERBOSE(dev, "Link '%s' for '%s' does not end with '/'", link, path); 92 | goto error; 93 | } 94 | 95 | if (strstr(last, "/v4l-subdev") != last) { 96 | LOG_VERBOSE(dev, "Link '%s' does not contain '/v4l-subdev'", link); 97 | goto error; 98 | } 99 | 100 | sprintf(path, "/dev%s", last); 101 | ret = open(path, O_RDWR); 102 | if (ret < 0) { 103 | LOG_ERROR(dev, "Cannot open '%s' (ret=%d)", path, ret); 104 | goto error; 105 | } 106 | 107 | LOG_VERBOSE(dev, "Opened '%s' (fd=%d)", path, ret); 108 | break; 109 | } 110 | 111 | error: 112 | close(media_fd); 113 | return ret; 114 | } 115 | 116 | int v4l2_device_set_pad_format(device_t *dev, unsigned width, unsigned height, unsigned format) 117 | { 118 | struct v4l2_subdev_format fmt = {0}; 119 | 120 | if (dev->v4l2->subdev_fd < 0) { 121 | return -1; 122 | } 123 | 124 | fmt.pad = 0; 125 | fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; 126 | fmt.format.code = format; 127 | fmt.format.width = width; 128 | fmt.format.height = height; 129 | fmt.format.colorspace = V4L2_COLORSPACE_RAW; 130 | fmt.format.field = V4L2_FIELD_ANY; 131 | 132 | LOG_DEBUG(dev, "Configuring mpad %d (subdev_fd=%d)...", fmt.pad, dev->v4l2->subdev_fd); 133 | ERR_IOCTL(dev, dev->v4l2->subdev_fd, VIDIOC_SUBDEV_S_FMT, &fmt, "Can't configure mpad %d (subdev_fd=%d)", fmt.pad, dev->v4l2->subdev_fd); 134 | return 0; 135 | 136 | error: 137 | return -1; 138 | } 139 | -------------------------------------------------------------------------------- /device/v4l2/v4l2.c: -------------------------------------------------------------------------------- 1 | #include "v4l2.h" 2 | #include "device/device.h" 3 | 4 | device_hw_t v4l2_device_hw = { 5 | .device_open = v4l2_device_open, 6 | .device_close = v4l2_device_close, 7 | .device_video_force_key = v4l2_device_video_force_key, 8 | .device_dump_options = v4l2_device_dump_options, 9 | .device_dump_options2 = v4l2_device_dump_options2, 10 | .device_set_fps = v4l2_device_set_fps, 11 | .device_set_option = v4l2_device_set_option, 12 | 13 | .buffer_open = v4l2_buffer_open, 14 | .buffer_close = v4l2_buffer_close, 15 | .buffer_enqueue = v4l2_buffer_enqueue, 16 | 17 | .buffer_list_dequeue = v4l2_buffer_list_dequeue, 18 | .buffer_list_pollfd = v4l2_buffer_list_pollfd, 19 | .buffer_list_open = v4l2_buffer_list_open, 20 | .buffer_list_close = v4l2_buffer_list_close, 21 | .buffer_list_set_stream = v4l2_buffer_list_set_stream 22 | }; 23 | 24 | device_t *device_v4l2_open(const char *name, const char *path) 25 | { 26 | return device_open(name, path, &v4l2_device_hw); 27 | } 28 | -------------------------------------------------------------------------------- /device/v4l2/v4l2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct buffer_s buffer_t; 11 | typedef struct buffer_list_s buffer_list_t; 12 | typedef struct device_option_s device_option_t; 13 | typedef struct device_s device_t; 14 | struct pollfd; 15 | 16 | typedef int device_option_fn(device_option_t *option, void *opaque); 17 | 18 | typedef struct device_v4l2_control_s { 19 | int fd; 20 | struct v4l2_query_ext_ctrl control; 21 | } device_v4l2_control_t; 22 | 23 | typedef struct device_v4l2_s { 24 | int dev_fd; 25 | int subdev_fd; 26 | device_v4l2_control_t *controls; 27 | int ncontrols; 28 | } device_v4l2_t; 29 | 30 | typedef struct buffer_list_v4l2_s { 31 | int dev_fd; 32 | bool do_mplanes; 33 | int type; 34 | } buffer_list_v4l2_t; 35 | 36 | typedef struct buffer_v4l2_s { 37 | unsigned flags; 38 | } buffer_v4l2_t; 39 | 40 | int v4l2_device_open(device_t *dev); 41 | void v4l2_device_close(device_t *dev); 42 | int v4l2_device_video_force_key(device_t *dev); 43 | void v4l2_device_dump_options(device_t *dev, FILE *stream); 44 | int v4l2_device_dump_options2(device_t *dev, device_option_fn fn, void *opaque); 45 | int v4l2_device_set_fps(device_t *dev, int desired_fps); 46 | int v4l2_device_set_option(device_t *dev, const char *key, const char *value); 47 | 48 | int v4l2_buffer_open(buffer_t *buf); 49 | void v4l2_buffer_close(buffer_t *buf); 50 | int v4l2_buffer_enqueue(buffer_t *buf, const char *who); 51 | int v4l2_buffer_list_dequeue(buffer_list_t *buf_list, buffer_t **bufp); 52 | int v4l2_buffer_list_refresh_states(buffer_list_t *buf_list); 53 | int v4l2_buffer_list_pollfd(buffer_list_t *buf_list, struct pollfd *pollfd, bool can_dequeue); 54 | 55 | int v4l2_buffer_list_open(buffer_list_t *buf_list); 56 | void v4l2_buffer_list_close(buffer_list_t *buf_list); 57 | int v4l2_buffer_list_set_stream(buffer_list_t *buf_list, bool do_on); 58 | 59 | int v4l2_device_open_media_device(device_t *dev); 60 | int v4l2_device_open_v4l2_subdev(device_t *dev, int subdev); 61 | int v4l2_device_set_pad_format(device_t *dev, unsigned width, unsigned height, unsigned format); 62 | void v4l2_device_query_controls(device_t *dev, int fd); 63 | -------------------------------------------------------------------------------- /docs/configure.md: -------------------------------------------------------------------------------- 1 | # Configure 2 | 3 | ## Resolution 4 | 5 | Camera capture and resolution exposed is controlled by three parameters: 6 | 7 | - `--camera-width` and `--camera-height` define the camera capture resolution 8 | - `--camera-snapshot.height` - define height for an aspect ratio scaled resolution for `/snapshot` (JPEG) output - this might require rescaller and might not always work 9 | - `--camera-video.height` - define height for an aspect ratio scaled resolution for `/video` and `/webrtc` (H264) output - this might require rescaller and might not always work, this is no larger than `--camera-snapshot.height` 10 | - `--camera-stream.height` - define height for an aspect ratio scaled resolution for `/stream` (MJPEG) output - this might require rescaller and might not always work, this is no larger than `--camera-video.height` 11 | 12 | The resolution scaling is following the `snapshot >= video >= stream`: 13 | 14 | - `snapshot` provides a high quality image for the purpose of timelapses (3Mbps-10Mbps) 15 | - `video` is an efficient high-quality H264 video stream (3-10Mbps) 16 | - `stream` is an inefficient MJPEG stream requiring significant amount of bandwidth (10-100Mbps) 17 | 18 | ### Example: Raspberry PI v3 Camera (best) 19 | 20 | ```text 21 | libcamera-still --list-cameras 22 | Available cameras 23 | ----------------- 24 | 0 : imx708_wide [4608x2592] (/base/soc/i2c0mux/i2c@1/imx708@1a) 25 | Modes: 'SRGGB10_CSI2P' : 1536x864 [120.13 fps - (0, 0)/4608x2592 crop] 26 | 2304x1296 [56.03 fps - (0, 0)/4608x2592 crop] 27 | 4608x2592 [14.35 fps - (0, 0)/4608x2592 crop] 28 | ``` 29 | 30 | To get the best resolution for the above camera, the `--camera-width` and `--camera-height` 31 | needs to be configured with the native resolution of camera sensor, only then the resolution 32 | can be downscaled. 33 | 34 | ```text 35 | --camera-width=2304 --camera-height=1296 --camera-snapshot.height=1080 --camera-video.height=720 --camera-stream.height=480 36 | ``` 37 | 38 | This will result in: 39 | 40 | - camera will capture `2304x1296` a full sensor 41 | - `snapshot` be ~1920x1080 42 | - `video` be ~1280x720 43 | - `stream` be ~640x480 44 | 45 | ### Example: Raspberry PI v3 Camera with cropped output (bad) 46 | 47 | If the camera capture is not configured properly the result image will be cropped. 48 | 49 | ```text 50 | --camera-width=1920 --camera-height=1080 --camera-snapshot.height=1080 --camera-video.height=720 --camera-stream.height=480 51 | ``` 52 | 53 | This will result in: 54 | 55 | - camera will capture the middle `1920x1080` from the `2304x1296` 56 | - `snapshot` be ~1920x1080 57 | - `video` be ~1280x720 58 | - `stream` be ~640x480 59 | 60 | ## List all available controls 61 | 62 | You can view all available configuration parameters by adding `--log-verbose` 63 | to one of the above commands, like: 64 | 65 | ```bash 66 | tools/libcamera_camera.sh --camera-list_options ... 67 | ``` 68 | 69 | Or navigate to `http://:8080/option` while running. 70 | 71 | Depending on control they have to be used for camera, ISP, or JPEG or H264 codec: 72 | 73 | ```bash 74 | # specify camera option 75 | --camera-options=brightness=1000 76 | 77 | # specify ISP option 78 | --camera-isp.options=digital_gain=1000 79 | 80 | # specify H264 option 81 | --camera-video.options=bitrate=10000000 82 | 83 | # specify snapshot option 84 | --camera-snapshot.options=compression_quality=60 85 | --camera-stream.options=compression_quality=60 86 | ``` 87 | 88 | ## List all available formats and use proper one 89 | 90 | You might list all available capture formats for your camera: 91 | 92 | ```bash 93 | v4l2-ctl -d /dev/video0 --list-formats-ext 94 | ``` 95 | 96 | Some of them might be specified to streamer: 97 | 98 | ```bash 99 | tools/*_camera.sh --camera-format=RG10 # Bayer 10 packed 100 | tools/*_camera.sh --camera-format=YUYV 101 | tools/*_camera.sh --camera-format=MJPEG 102 | tools/*_camera.sh --camera-format=H264 # This is unstable due to h264 key frames support 103 | ``` 104 | 105 | It is advised to always use: 106 | 107 | - for `libcamera` the `--camera-type=libcamera --camera-format=YUYV` (better image quality) or `--camera-format=YUV420` (better performance) 108 | - for `USB cameras` the `--camera-type=libcamera --camera-format=MJPEG` 109 | -------------------------------------------------------------------------------- /docs/install-manual.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | This describes a set of manual instructions to run camera streamer. 4 | 5 | ## Validate your system 6 | 7 | **This streamer does only use hardware, and does not support any software decoding or encoding**. 8 | It does require system to provide: 9 | 10 | 1. ISP (`/dev/video13`, `/dev/video14`, `/dev/video15`) 11 | 2. JPEG encoder (`/dev/video31`) 12 | 3. H264 encoder (`/dev/video11`) 13 | 4. JPEG/H264 decoder (for UVC cameras, `/dev/video10`) 14 | 5. At least LTS kernel (5.15, 6.1) for Raspberry PIs 15 | 16 | You can validate the presence of all those devices with: 17 | 18 | ```bash 19 | uname -a 20 | v4l2-ctl --list-devices 21 | ``` 22 | 23 | The `5.15 kernel` or `6.1 kernel` is easy to get since this is LTS kernel for Raspberry PI OS: 24 | 25 | ```bash 26 | apt-get update 27 | apt-get dist-upgrade 28 | reboot 29 | ``` 30 | 31 | Ensure that your `/boot/config.txt` has enough of GPU memory (required for JPEG re-encoding): 32 | 33 | ```text 34 | # Example for IMX519 35 | dtoverlay=vc4-kms-v3d,cma-128 36 | gpu_mem=128 # preferred 160 or 256MB 37 | dtoverlay=imx519 38 | 39 | # Example for Arducam 64MP 40 | gpu_mem=128 41 | dtoverlay=arducam_64mp,media-controller=1 42 | 43 | # Example for USB cam 44 | gpu_mem=128 45 | ``` 46 | 47 | ## Compile 48 | 49 | ```bash 50 | git clone https://github.com/ayufan-research/camera-streamer.git --recursive 51 | apt-get -y install libavformat-dev libavutil-dev libavcodec-dev libcamera-dev liblivemedia-dev v4l-utils pkg-config xxd build-essential cmake libssl-dev 52 | 53 | cd camera-streamer/ 54 | make 55 | sudo make install 56 | ``` 57 | 58 | ## Use it 59 | 60 | There are three modes of operation implemented offering different 61 | compatibility to performance. 62 | 63 | ### Use a preconfigured systemd services 64 | 65 | The simplest is to use preconfigured `service/camera-streamer*.service`. 66 | Those can be used as an example, and can be configured to fine tune parameters. 67 | 68 | Example: 69 | 70 | ```bash 71 | systemctl enable $PWD/service/camera-streamer-arducam-16MP.service 72 | systemctl start camera-streamer-arducam-16MP 73 | ``` 74 | 75 | If everything was OK, there will be web-server at `http://:8080/`. 76 | 77 | Error messages can be read `journalctl -xef -u camera-streamer-arducam-16MP`. 78 | -------------------------------------------------------------------------------- /docs/performance-analysis.md: -------------------------------------------------------------------------------- 1 | # Performance analysis 2 | 3 | ## Arducam 16MP 4 | 5 | The 16MP sensor is supported by default in Raspberry PI OS after adding to `/boot/config.txt`. 6 | However it will not support auto-focus nor manual focus due to lack of `ak7535` compiled 7 | and enabled in `imx519`. Focus can be manually controlled via `i2c-tools`: 8 | 9 | ```shell 10 | # /boot/config.txt 11 | dtoverlay=imx519,media-controller=0 12 | gpu_mem=160 # at least 128 13 | 14 | # /etc/modules-load.d/modules.conf 15 | i2c-dev 16 | 17 | # after starting camera execute to control the focus with `0xXX`, any value between `0x00` to `0xff` 18 | # RPI02W (and possible 2+, 3+): 19 | i2ctransfer -y 22 w4@0x0c 0x0 0x85 0x00 0x00 20 | 21 | # RPI4: 22 | i2ctransfer -y 11 w4@0x0c 0x0 0xXX 0x00 0x00 23 | ``` 24 | 25 | Latency according to my tests is due to the way how buffers are enqueued and processing delay, this is for triple buffering in a case where sensor is able to deliver frames quick enough. Depending on how you queue (on which slope) and when you enqueue buffer you might achieve significantly better latency as shown in the ISP-mode. The `libcamera` can still achieve 120fps, it is just slightly slower :) 26 | 27 | #### 2328x1748@30fps 28 | 29 | ```shell 30 | # libcamera 31 | $ ./camera_streamer -camera-path=/base/soc/i2c0mux/i2c@1/imx519@1a -camera-type=libcamera -camera-format=YUYV -camera-fps=120 -camera-width=2328 -camera-height=1748 -camera-high_res_factor=1.5 -log-filter=buffer_lock 32 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=63/0, processing_ms=101.1, frame_ms=33.1 33 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=64/0, processing_ms=99.2, frame_ms=31.9 34 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf1 (refs=2), frame=65/0, processing_ms=99.6, frame_ms=34.8 35 | 36 | # direct ISP-mode 37 | $ ./camera_streamer -camera-path=/dev/video0 -camera-format=RG10 -camera-fps=30 -camera-width=2328 -camera-height=1748 -camera-high_res_factor=1.5 -log-filter=buffer_lock 38 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf1 (refs=2), frame=32/0, processing_ms=49.7, frame_ms=33.3 39 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=33/0, processing_ms=49.7, frame_ms=33.3 40 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=34/0, processing_ms=49.7, frame_ms=33.4 41 | ``` 42 | 43 | #### 2328x1748@10fps 44 | 45 | ```shell 46 | # libcamera 47 | $ ./camera_streamer -camera-path=/base/soc/i2c0mux/i2c@1/imx519@1a -camera-type=libcamera -camera-format=YUYV -camera-fps=10 -camera-width=2328 -camera-height=1748 -camera-high_res_factor=1.5 -log-filter=buffer_lock 48 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=585/0, processing_ms=155.3, frame_ms=100.0 49 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=586/0, processing_ms=155.5, frame_ms=100.2 50 | 51 | # direct ISP-mode 52 | $ ./camera_streamer -camera-path=/dev/video0 -camera-format=RG10 -camera-fps=10 -camera-width=2328 -camera-height=1748 -camera-high_res_factor=1.5 -log-filter=buffer_lock 53 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf1 (refs=2), frame=260/0, processing_ms=57.5, frame_ms=99.7 54 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=261/0, processing_ms=57.6, frame_ms=100.0 55 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=262/0, processing_ms=58.0, frame_ms=100.4 56 | ``` 57 | 58 | #### 1280x720@120fps for Arducam 16MP 59 | 60 | ```shell 61 | # libcamera 62 | $ ./camera_streamer -camera-path=/base/soc/i2c0mux/i2c@1/imx519@1a -camera-type=libcamera -camera-format=YUYV -camera-fps=120 -camera-width=1280 -camera-height=720 -log-filter=buffer_lock 63 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=139/0, processing_ms=20.1, frame_ms=7.9 64 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf1 (refs=2), frame=140/0, processing_ms=20.6, frame_ms=8.8 65 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=141/0, processing_ms=19.8, frame_ms=8.1 66 | 67 | # direct ISP-mode 68 | $ ./camera_streamer -camera-path=/dev/video0 -camera-format=RG10 -camera-fps=120 -camera-width=1280 -camera-height=720 -log-filter=buffer_lock 69 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf0 (refs=2), frame=157/0, processing_ms=18.5, frame_ms=8.4 70 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf1 (refs=2), frame=158/0, processing_ms=18.5, frame_ms=8.3 71 | device/buffer_lock.c: http_jpeg: Captured buffer JPEG:capture:mplane:buf2 (refs=2), frame=159/0, processing_ms=18.5, frame_ms=8.3 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/raspi-libcamera.md: -------------------------------------------------------------------------------- 1 | # High-compatibility via `libcamera` on Raspberry PI 2 | 3 | This script uses `libcamera` to access camera and provide 4 | manual and automatic brightness and exposure controls. 5 | The settings for those are configurable via described below controls. 6 | 7 | This due to extra overhead has worse latency than direct decoding 8 | via ISP. 9 | 10 | ```bash 11 | tools/libcamera_camera.sh -help 12 | tools/libcamera_camera.sh -camera-format=YUYV 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/streaming.md: -------------------------------------------------------------------------------- 1 | # Streaming 2 | 3 | Camera Streamer exposes a number of streaming capabilities. 4 | 5 | ## HTTP web server 6 | 7 | All streams are exposed over very simple HTTP server, providing different streams for different purposes: 8 | 9 | - `http://:8080/` - index page 10 | - `http://:8080/snapshot` - provide JPEG snapshot (works well everywhere) 11 | - `http://:8080/stream` - provide MJPEG stream (works well everywhere) 12 | - `http://:8080/video` - provide automated video.mp4 or video.hls stream depending on browser used 13 | - `http://:8080/video.mp4` or `http://:8080/video.mkv` - provide remuxed `mkv` or `mp4` stream (uses `ffmpeg` to remux, works as of now only in Desktop Chrome and Safari) 14 | - `http://:8080/webrtc` - provide WebRTC feed 15 | 16 | ## WebRTC support 17 | 18 | The WebRTC is accessible via `http://:8080/webrtc` by default and is available when there's H264 output generated. 19 | 20 | WebRTC support is implemented using awesome [libdatachannel](https://github.com/paullouisageneau/libdatachannel/) library. 21 | 22 | The support will be compiled by default when doing `make`. 23 | 24 | ## RTSP server 25 | 26 | The camera-streamer implements RTSP server via `live555`. Enable it with: 27 | 28 | - adding `--rtsp-port`: will enable RTSP server on 8554 29 | - adding `--rtsp-port=1111`: will enable RTSP server on custom port 30 | 31 | The camera-streamer will expose single video stream: 32 | 33 | - `rtsp://:8554/stream.h264` - the resolution is configured with `--camera-video.height` 34 | -------------------------------------------------------------------------------- /docs/v4l2-isp-mode.md: -------------------------------------------------------------------------------- 1 | # High-performance mode via ISP for CSI 2 | 3 | This script uses high-performance implementation for directly 4 | accessing sensor feeds and passing it via DMA into bcm2385 ISP. 5 | As such it does not implement brightness control similarly to `libcamera`. 6 | 7 | ```bash 8 | # This script uses dumped IMX519 parametrs that are feed into bcm2385 ISP module 9 | # This does not provide automatic brightness control 10 | # Other sensors can be supported the same way as long as ISP parameters are adapted 11 | tools/csi_camera.sh -help 12 | tools/csi_camera.sh -camera-format=RG10 ... 13 | ``` 14 | 15 | This mode allows to provide significantly better performance for camera sensors 16 | than described in specs. For example for Arducam 16MP (using IMX519) it is possible to achieve: 17 | 18 | 1. 120fps on 1280x720, with latency of about 50ms, where-as documentation says 1280x720x60p 19 | 1. 60fps on 1920x1080, with latency of about 90ms, where-as documentation says 1920x1080x30p 20 | 1. 30fps on 2328x1748, with latency of about 140ms, where-as documentation says 2328x1748x15p 21 | 22 | The above was tested with camera connected to Raspberry PI Zero 2W, streaming `MJPEG` over WiFi 23 | with camera recording https://www.youtube.com/watch?v=e8ZtPSIfWPc from desktop monitor. So, it as 24 | well measured also a desktop rendering latency. 25 | -------------------------------------------------------------------------------- /docs/v4l2-usb-mode.md: -------------------------------------------------------------------------------- 1 | # High-performance mode via direct decoding for USB 2 | 3 | This script uses direct decoding or passthrough of MJPEG or H264 streams from UVC camera into 4 | re-encoded stream provided over web-interface. 5 | 6 | ```bash 7 | tools/usb_camera.sh -help 8 | tools/usb_camera.sh -camera-format=MJPEG ... 9 | ``` 10 | 11 | Currently the H264 is consider rather broken: 12 | 13 | ```bash 14 | tools/usb_camera.sh -camera-format=H264 ... 15 | ``` 16 | -------------------------------------------------------------------------------- /html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
    12 |
    13 |
  • 14 | /snapshot (JPEG image)
    15 |
    16 |
      17 |
    • Get a high-resolution snapshot image from the server.
    • 18 |
    • Uses resolution specified by -camera-snapshot.height=.
    • 19 |
      20 |
    • /snapshot?max_delay=0 to get a snapshot captured exactly now.
    • 21 |
    • /snapshot?max_delay=300 (default) to get a cached snapshot captured up-to 300 ms in the past.
    • 22 |
    23 |
  • 24 |
    25 |
  • 26 | /stream (MJPEG stream)
    27 |
    28 |
      29 |
    • Get a live stream. Works everywhere, but consumes a ton of bandwidth.
    • 30 |
    • Uses resolution specified by -camera-stream.height=.
    • 31 |
    32 |
  • 33 |
    34 |
  • 35 | /webrtc (HTTP page / iframe)
    36 |
    37 |
      38 |
    • Get a live video using WebRTC (low-latency streaming with latency of around 100ms).
    • 39 |
    • Uses resolution specified by -camera-video.height=.
    • 40 |
    41 |
  • 42 |
    43 |
  • 44 | /video (IP Camera)
    45 |
    46 |
      47 |
    • Get a live (H264) video stream best suited to current browser in a maximum compatibility mode choosing automatically between one of the below formats.
    • 48 |
    • Uses resolution specified by -camera-video.height=.
    • 49 |
      50 |
    • /video.mp4
      get a live video stream in MP4 format (Firefox, with latency of around 1s if FFMPEG enabled).
    • 51 |
      52 |
    • /video.mkv
      get a live video stream in MKV format (Chrome, with latency of around 2s if FFMPEG enabled).
    • 53 |
      54 |
    • /video.m3u8
      get a live video stream in HLS format (Safari, with latency of around 1s).
    • 55 |
    56 |
  • 57 |
    58 |
  • 59 | /control
    60 |
    61 |
      62 |
    • See all configurable camera options.
    • 63 |
      64 |
    • /option?device=CAMERA&key=AfMode&value=auto to set AfMode on CAMERA.
    • 65 |
    66 |
  • 67 |
    68 |
  • 69 | /status
    70 |
    71 |
      72 |
    • See the JSON status of camera-streamer.
    • 73 |
    74 |
  • 75 |
    76 |
  • 77 | The mjpg-streamer compatibility layer:
    78 |
    79 | 83 |
  • 84 |
85 |
86 |
87 | 88 | 89 | -------------------------------------------------------------------------------- /html/webrtc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 42 | 43 | 44 |
45 | 46 |
47 | 48 | 128 | 129 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /output/http_ffmpeg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "output.h" 5 | #include "util/opts/log.h" 6 | #include "util/http/http.h" 7 | #include "device/buffer.h" 8 | #include "device/buffer_lock.h" 9 | #include "device/buffer_list.h" 10 | #include "device/device.h" 11 | #include "util/ffmpeg/remuxer.h" 12 | 13 | static const char *const VIDEO_HEADER = 14 | "HTTP/1.0 200 OK\r\n" 15 | "Access-Control-Allow-Origin: *\r\n" 16 | "Connection: close\r\n" 17 | "Content-Type: %s\r\n" 18 | "\r\n"; 19 | 20 | typedef struct { 21 | const char *name; 22 | FILE *stream; 23 | const char *content_type; 24 | 25 | bool had_key_frame; 26 | bool requested_key_frame; 27 | bool wrote_header; 28 | 29 | buffer_t *buf; 30 | unsigned buf_offset; 31 | unsigned stream_offset; 32 | 33 | ffmpeg_remuxer_t *remuxer; 34 | } http_ffmpeg_status_t; 35 | 36 | static int http_ffmpeg_read_from_buf(void *opaque, uint8_t *buf, int buf_size) 37 | { 38 | http_ffmpeg_status_t *status = opaque; 39 | if (!status->buf) 40 | return FFMPEG_DATA_PACKET_EOF; 41 | 42 | buf_size = MIN(buf_size, status->buf->used - status->buf_offset); 43 | if (!buf_size) 44 | return FFMPEG_DATA_PACKET_EOF; 45 | 46 | LOG_DEBUG(status, "http_ffmpeg_read_from_buf: offset=%d, n=%d", status->buf_offset, buf_size); 47 | memcpy(buf, (char*)status->buf->start + status->buf_offset, buf_size); 48 | status->buf_offset += buf_size; 49 | return buf_size; 50 | } 51 | 52 | static int http_ffmpeg_write_to_stream(void *opaque, uint8_t *buf, int buf_size) 53 | { 54 | http_ffmpeg_status_t *status = opaque; 55 | if (!status->stream) 56 | return FFMPEG_DATA_PACKET_EOF; 57 | 58 | if (!status->wrote_header) { 59 | fprintf(status->stream, VIDEO_HEADER, status->content_type); 60 | status->wrote_header = true; 61 | } 62 | 63 | size_t n = fwrite(buf, 1, buf_size, status->stream); 64 | fflush(status->stream); 65 | 66 | LOG_DEBUG(status, "http_ffmpeg_write_to_stream: offset=%d, n=%zu, buf_size=%d, error=%d", 67 | status->stream_offset, n, buf_size, ferror(status->stream)); 68 | status->stream_offset += n; 69 | if (ferror(status->stream)) 70 | return FFMPEG_DATA_PACKET_EOF; 71 | 72 | return n; 73 | } 74 | 75 | static int http_ffmpeg_video_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, http_ffmpeg_status_t *status) 76 | { 77 | if (!status->had_key_frame) { 78 | status->had_key_frame = buf->flags.is_keyframe; 79 | } 80 | 81 | if (!status->had_key_frame) { 82 | if (!status->requested_key_frame) { 83 | device_video_force_key(buf->buf_list->dev); 84 | status->requested_key_frame = true; 85 | } 86 | return 0; 87 | } 88 | 89 | int ret = -1; 90 | 91 | status->buf = buf; 92 | status->buf_offset = 0; 93 | 94 | if ((ret = ffmpeg_remuxer_open(status->remuxer)) < 0) 95 | goto error; 96 | if ((ret = ffmpeg_remuxer_feed(status->remuxer, 0)) < 0) 97 | goto error; 98 | 99 | ret = 1; 100 | 101 | error: 102 | status->buf = NULL; 103 | return ret; 104 | } 105 | 106 | static void http_ffmpeg_video(http_worker_t *worker, FILE *stream, const char *content_type, const char *video_format) 107 | { 108 | http_ffmpeg_status_t status = { 109 | .name = worker->name, 110 | .stream = stream, 111 | .content_type = content_type, 112 | }; 113 | 114 | ffmpeg_remuxer_t remuxer = { 115 | .name = worker->name, 116 | .input_format = "h264", 117 | .video_format = video_format, 118 | .opaque = &status, 119 | .read_packet = http_ffmpeg_read_from_buf, 120 | .write_packet = http_ffmpeg_write_to_stream, 121 | }; 122 | 123 | status.remuxer = &remuxer; 124 | 125 | #ifdef USE_FFMPEG 126 | av_dict_set_int(&remuxer.output_opts, "direct", 1, 0); 127 | //av_dict_set_int(&remuxer.output_opts, "frag_duration", 1, 0); 128 | av_dict_set_int(&remuxer.output_opts, "frag_size", 4096, 0); 129 | av_dict_set_int(&remuxer.output_opts, "low_delay", 1, 0); 130 | av_dict_set_int(&remuxer.output_opts, "nobuffer", 1, 0); 131 | av_dict_set_int(&remuxer.output_opts, "flush_packets", 1, 0); 132 | av_dict_set(&remuxer.output_opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0); 133 | #endif 134 | 135 | int n = buffer_lock_write_loop( 136 | &video_lock, 137 | 0, 138 | 0, 139 | (buffer_write_fn)http_ffmpeg_video_buf_part, 140 | &status); 141 | ffmpeg_remuxer_close(&remuxer); 142 | 143 | if (status.wrote_header) { 144 | return; 145 | } 146 | 147 | http_500(stream, NULL); 148 | 149 | if (n == 0) { 150 | fprintf(stream, "No frames.\n"); 151 | } else if (n < 0) { 152 | fprintf(stream, "Interrupted. Received %d frames", -n); 153 | } 154 | } 155 | 156 | void http_mkv_video(http_worker_t *worker, FILE *stream) 157 | { 158 | http_ffmpeg_video(worker, stream, "video/mp4", "matroska"); 159 | } 160 | 161 | void http_mp4_video(http_worker_t *worker, FILE *stream) 162 | { 163 | http_ffmpeg_video(worker, stream, "video/mp4", "mp4"); 164 | } 165 | 166 | -------------------------------------------------------------------------------- /output/http_h264.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "output.h" 5 | #include "util/opts/log.h" 6 | #include "util/http/http.h" 7 | #include "device/buffer.h" 8 | #include "device/buffer_lock.h" 9 | #include "device/buffer_list.h" 10 | #include "device/device.h" 11 | 12 | static const char *const VIDEO_HEADER = 13 | "HTTP/1.0 200 OK\r\n" 14 | "Access-Control-Allow-Origin: *\r\n" 15 | "Connection: close\r\n" 16 | "Content-Type: application/octet-stream\r\n" 17 | "\r\n"; 18 | 19 | typedef struct { 20 | FILE *stream; 21 | bool wrote_header; 22 | bool had_key_frame; 23 | bool requested_key_frame; 24 | } http_video_status_t; 25 | 26 | int http_video_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, http_video_status_t *status) 27 | { 28 | if (!status->had_key_frame) { 29 | status->had_key_frame = buf->flags.is_keyframe; 30 | } 31 | 32 | if (!status->had_key_frame) { 33 | if (!status->requested_key_frame) { 34 | device_video_force_key(buf->buf_list->dev); 35 | status->requested_key_frame = true; 36 | } 37 | return 0; 38 | } 39 | 40 | if (!status->wrote_header) { 41 | fputs(VIDEO_HEADER, status->stream); 42 | status->wrote_header = true; 43 | } 44 | if (!fwrite(buf->start, buf->used, 1, status->stream)) { 45 | return -1; 46 | } 47 | fflush(status->stream); 48 | return 1; 49 | } 50 | 51 | void http_h264_video(http_worker_t *worker, FILE *stream) 52 | { 53 | http_video_status_t status = { stream }; 54 | 55 | int n = buffer_lock_write_loop(&video_lock, 0, 0, (buffer_write_fn)http_video_buf_part, &status); 56 | 57 | if (status.wrote_header) { 58 | return; 59 | } 60 | 61 | http_500(stream, NULL); 62 | 63 | if (n == 0) { 64 | fprintf(stream, "No frames.\n"); 65 | } else if (n < 0) { 66 | fprintf(stream, "Interrupted. Received %d frames", -n); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /output/http_hls.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "output.h" 5 | #include "util/opts/log.h" 6 | #include "util/http/http.h" 7 | #include "device/buffer.h" 8 | #include "device/buffer_lock.h" 9 | #include "device/buffer_list.h" 10 | #include "device/device.h" 11 | 12 | static const char *const CONTENT_TYPE = "application/x-mpegURL"; 13 | 14 | static const char *const STREAM_M3U8 = 15 | "#EXTM3U\r\n" \ 16 | "#EXT-X-TARGETDURATION:1\r\n" \ 17 | "#EXT-X-VERSION:4\r\n" \ 18 | "#EXTINF:1.0,\r\n" \ 19 | "video.mp4?ts=%zu\r\n"; 20 | 21 | static const char *const LOCATION_REDIRECT = 22 | "HTTP/1.0 307 Temporary Redirect\r\n" 23 | "Access-Control-Allow-Origin: *\r\n" 24 | "Connection: close\r\n" 25 | "Location: %s?%s\r\n" 26 | "\r\n"; 27 | 28 | void http_m3u8_video(struct http_worker_s *worker, FILE *stream) 29 | { 30 | uint64_t ts = get_monotonic_time_us(NULL, NULL) / 1000 / 1000; 31 | http_write_responsef(stream, "200 OK", CONTENT_TYPE, STREAM_M3U8, ts); 32 | } 33 | 34 | void http_detect_video(struct http_worker_s *worker, FILE *stream) 35 | { 36 | if (strstr(worker->user_agent, "Safari/") && !strstr(worker->user_agent, "Chrome/") && !strstr(worker->user_agent, "Chromium/")) { 37 | // Safari only supports m3u8 38 | fprintf(stream, LOCATION_REDIRECT, "video.m3u8", worker->request_params); 39 | } else if (strstr(worker->user_agent, "Firefox/")) { 40 | // Firefox only supports mp4 41 | fprintf(stream, LOCATION_REDIRECT, "video.mp4", worker->request_params); 42 | } else { 43 | // Chrome offers best latency with mkv 44 | fprintf(stream, LOCATION_REDIRECT, "video.mkv", worker->request_params); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /output/http_jpeg.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "output.h" 5 | #include "util/http/http.h" 6 | #include "util/opts/log.h" 7 | #include "device/buffer.h" 8 | #include "device/buffer_lock.h" 9 | 10 | #define SNAPSHOT_TIMEOUT_MS 3000 11 | #define SNAPSHOT_DEFAULT_DELAY_PARAM 300 12 | 13 | #define PART_BOUNDARY "123456789000000000000987654321" 14 | #define CONTENT_TYPE "image/jpeg" 15 | #define CONTENT_LENGTH "Content-Length" 16 | 17 | static const char *const STREAM_HEADER = "HTTP/1.0 200 OK\r\n" 18 | "Access-Control-Allow-Origin: *\r\n" 19 | "Connection: close\r\n" 20 | "Content-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY "\r\n" 21 | "\r\n" 22 | "--" PART_BOUNDARY "\r\n"; 23 | static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n"; 24 | static const char *const STREAM_BOUNDARY = "\r\n" 25 | "--" PART_BOUNDARY "\r\n"; 26 | 27 | typedef struct 28 | { 29 | FILE *stream; 30 | uint64_t start_time_us; 31 | } http_snapshot_t; 32 | 33 | int http_snapshot_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, http_snapshot_t *snapshot) 34 | { 35 | // Ignore frames that are captured 36 | if (buf->captured_time_us < snapshot->start_time_us) { 37 | return 0; 38 | } 39 | 40 | fprintf(snapshot->stream, "HTTP/1.1 200 OK\r\n"); 41 | fprintf(snapshot->stream, "Content-Type: image/jpeg\r\n"); 42 | fprintf(snapshot->stream, "Content-Length: %zu\r\n", buf->used); 43 | fprintf(snapshot->stream, "\r\n"); 44 | fwrite(buf->start, buf->used, 1, snapshot->stream); 45 | return 1; 46 | } 47 | 48 | void http_snapshot(http_worker_t *worker, FILE *stream) 49 | { 50 | int max_delay_value = SNAPSHOT_DEFAULT_DELAY_PARAM; 51 | 52 | // passing the max_delay=0 will ensure that frame is capture at this exact moment 53 | char *max_delay = http_get_param(worker, "max_delay"); 54 | if (max_delay) { 55 | max_delay_value = atoi(max_delay); 56 | free(max_delay); 57 | } 58 | 59 | http_snapshot_t snapshot = { 60 | .stream = stream, 61 | .start_time_us = get_monotonic_time_us(NULL, NULL) - max_delay_value * 1000 62 | }; 63 | 64 | int n = buffer_lock_write_loop(&snapshot_lock, 1, SNAPSHOT_TIMEOUT_MS, 65 | (buffer_write_fn)http_snapshot_buf_part, &snapshot); 66 | 67 | if (n <= 0) { 68 | http_500(stream, NULL); 69 | fprintf(stream, "No snapshot captured yet.\r\n"); 70 | } 71 | } 72 | 73 | int http_stream_buf_part(buffer_lock_t *buf_lock, buffer_t *buf, int frame, FILE *stream) 74 | { 75 | if (!frame && !fputs(STREAM_HEADER, stream)) { 76 | return -1; 77 | } 78 | if (!fprintf(stream, STREAM_PART, buf->used)) { 79 | return -1; 80 | } 81 | if (!fwrite(buf->start, buf->used, 1, stream)) { 82 | return -1; 83 | } 84 | if (!fputs(STREAM_BOUNDARY, stream)) { 85 | return -1; 86 | } 87 | 88 | return 1; 89 | } 90 | 91 | void http_stream(http_worker_t *worker, FILE *stream) 92 | { 93 | int n = buffer_lock_write_loop(&stream_lock, 0, 0, (buffer_write_fn)http_stream_buf_part, stream); 94 | 95 | if (n == 0) { 96 | http_500(stream, NULL); 97 | fprintf(stream, "No frames.\n"); 98 | } else if (n < 0) { 99 | fprintf(stream, "Interrupted. Received %d frames", -n); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /output/output.c: -------------------------------------------------------------------------------- 1 | #include "device/buffer_lock.h" 2 | 3 | DEFINE_BUFFER_LOCK(snapshot_lock, 0); 4 | DEFINE_BUFFER_LOCK(stream_lock, 0); 5 | DEFINE_BUFFER_LOCK(video_lock, 0); 6 | -------------------------------------------------------------------------------- /output/output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct http_worker_s; 6 | struct buffer_s; 7 | 8 | extern struct buffer_lock_s snapshot_lock; 9 | extern struct buffer_lock_s stream_lock; 10 | extern struct buffer_lock_s video_lock; 11 | 12 | // M-JPEG 13 | void http_snapshot(struct http_worker_s *worker, FILE *stream); 14 | void http_stream(struct http_worker_s *worker, FILE *stream); 15 | void http_option(struct http_worker_s *worker, FILE *stream); 16 | 17 | // H264 18 | void http_h264_video(struct http_worker_s *worker, FILE *stream); 19 | void http_mkv_video(struct http_worker_s *worker, FILE *stream); 20 | void http_mp4_video(struct http_worker_s *worker, FILE *stream); 21 | void http_mov_video(struct http_worker_s *worker, FILE *stream); 22 | 23 | // HLS 24 | void http_m3u8_video(struct http_worker_s *worker, FILE *stream); 25 | void http_detect_video(struct http_worker_s *worker, FILE *stream); 26 | 27 | #define HTTP_LOW_RES_PARAM "res=low" 28 | -------------------------------------------------------------------------------- /output/rtsp/rtsp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | typedef struct rtsp_options_s { 4 | bool running; 5 | bool allow_truncated; 6 | uint port; 7 | int clients; 8 | int frames; 9 | int truncated; 10 | int dropped; 11 | } rtsp_options_t; 12 | 13 | int rtsp_server(rtsp_options_t *options); 14 | -------------------------------------------------------------------------------- /output/webrtc/webrtc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define WEBRTC_OPTIONS_LENGTH 4096 6 | 7 | typedef struct http_worker_s http_worker_t; 8 | 9 | typedef struct webrtc_options_s { 10 | bool running; 11 | bool disabled; 12 | char ice_servers[WEBRTC_OPTIONS_LENGTH]; 13 | bool disable_client_ice; 14 | } webrtc_options_t; 15 | 16 | // WebRTC 17 | void http_webrtc_offer(http_worker_t *worker, FILE *stream); 18 | int webrtc_server(webrtc_options_t *options); 19 | -------------------------------------------------------------------------------- /service/camera-streamer-arducam-16MP.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=camera-streamer web camera for ArduCAM 16MP on Raspberry PI 3 | After=network.target 4 | ConditionPathExists=/sys/bus/i2c/drivers/imx519/10-001a/video4linux 5 | 6 | [Service] 7 | ExecStart=/usr/local/bin/camera-streamer \ 8 | -camera-path=/base/soc/i2c0mux/i2c@1/imx519@1a \ 9 | -camera-type=libcamera \ 10 | -camera-format=YUYV \ 11 | -camera-width=2328 -camera-height=1748 \ 12 | -camera-options=rotation=90 \ 13 | -camera-fps=15 \ 14 | ; use two memory buffers to optimise usage 15 | -camera-nbufs=2 \ 16 | ; the snapshot is 1438x1080 17 | -camera-snapshot.height=1080 \ 18 | ; the video/webrtc is 958x720 19 | -camera-video.height=720 \ 20 | ; the stream is 639x480 21 | -camera-stream.height=480 \ 22 | ; bump brightness slightly 23 | -camera-options=brightness=0.1 \ 24 | ; disable auto-focus 25 | -camera-auto_focus=1 \ 26 | --http-listen=0.0.0.0 \ 27 | --http-port=8080 \ 28 | -rtsp-port 29 | 30 | DynamicUser=yes 31 | SupplementaryGroups=video i2c 32 | Restart=always 33 | RestartSec=10 34 | Nice=10 35 | IOSchedulingClass=idle 36 | IOSchedulingPriority=7 37 | CPUWeight=20 38 | AllowedCPUs=1-2 39 | MemoryMax=250M 40 | 41 | [Install] 42 | WantedBy=multi-user.target 43 | -------------------------------------------------------------------------------- /service/camera-streamer-arducam-64MP.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=camera-streamer web camera for ArduCAM 16MP on Raspberry PI 3 | After=network.target 4 | ConditionPathExists=/sys/bus/i2c/drivers/arducam_64mp/10-001a/video4linux 5 | 6 | [Service] 7 | ; set fixed-focus 8 | ExecStartPre=-/usr/bin/v4l2-ctl -d /dev/v4l-subdev1 -c focus_absolute=2200 9 | 10 | ExecStart=/usr/local/bin/camera-streamer \ 11 | -camera-path=/base/soc/i2c0mux/i2c@1/arducam_64mp@1a \ 12 | -camera-type=libcamera \ 13 | -camera-format=YUYV \ 14 | -camera-width=2328 -camera-height=1748 \ 15 | -camera-fps=30 \ 16 | ; use two memory buffers to optimise usage 17 | -camera-nbufs=2 \ 18 | ; the snapshot is 1438x1080 19 | -camera-snapshot.height=1080 \ 20 | ; the video/webrtc is 958x720 21 | -camera-video.height=720 \ 22 | ; the stream is 639x480 23 | -camera-stream.height=480 \ 24 | ; bump brightness slightly 25 | -camera-options=brightness=0.1 \ 26 | ; disable auto-focus 27 | -camera-auto_focus=0 \ 28 | --http-listen=0.0.0.0 \ 29 | --http-port=8080 \ 30 | -rtsp-port 31 | 32 | DynamicUser=yes 33 | SupplementaryGroups=video i2c 34 | Restart=always 35 | RestartSec=10 36 | Nice=10 37 | IOSchedulingClass=idle 38 | IOSchedulingPriority=7 39 | CPUWeight=20 40 | AllowedCPUs=1-2 41 | MemoryMax=250M 42 | 43 | [Install] 44 | WantedBy=multi-user.target 45 | -------------------------------------------------------------------------------- /service/camera-streamer-generic-usb-cam.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=camera-streamer web camera for USB camera on Generic platform 3 | After=network.target 4 | ConditionPathExistsGlob=/dev/v4l/by-id/usb-*-video-index0 5 | 6 | [Service] 7 | ExecStart=/usr/local/bin/camera-streamer \ 8 | -camera-path=/dev/video0 \ 9 | -camera-format=JPEG \ 10 | -camera-width=1920 -camera-height=1080 \ 11 | -camera-fps=30 \ 12 | ; use two memory buffers to optimise usage 13 | -camera-nbufs=3 \ 14 | --http-listen=0.0.0.0 \ 15 | --http-port=8080 \ 16 | ; disable video streaming (WebRTC, RTSP, H264) 17 | ; on non-supported platforms 18 | -camera-video.disabled 19 | 20 | DynamicUser=yes 21 | SupplementaryGroups=video i2c 22 | Restart=always 23 | RestartSec=10 24 | Nice=10 25 | IOSchedulingClass=idle 26 | IOSchedulingPriority=7 27 | CPUWeight=20 28 | AllowedCPUs=1-2 29 | MemoryMax=250M 30 | 31 | [Install] 32 | WantedBy=multi-user.target 33 | -------------------------------------------------------------------------------- /service/camera-streamer-raspi-usb-cam.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=camera-streamer web camera for USB camera on Raspberry PI 3 | After=network.target 4 | ConditionPathExistsGlob=/dev/v4l/by-id/usb-*-video-index0 5 | 6 | [Service] 7 | ExecStart=/usr/local/bin/camera-streamer \ 8 | -camera-path=/dev/video0 \ 9 | -camera-format=JPEG \ 10 | -camera-width=1920 -camera-height=1080 \ 11 | -camera-fps=30 \ 12 | ; use two memory buffers to optimise usage 13 | -camera-nbufs=3 \ 14 | ; the high-res is 1920x1080 15 | -camera-snapshot.height=1080 \ 16 | ; the video/webrtc is 1280x720 17 | -camera-video.height=720 \ 18 | ; the stream is 853x480 19 | -camera-stream.height=480 \ 20 | --http-listen=0.0.0.0 \ 21 | --http-port=8080 \ 22 | -rtsp-port 23 | 24 | DynamicUser=yes 25 | SupplementaryGroups=video i2c 26 | Restart=always 27 | RestartSec=10 28 | Nice=10 29 | IOSchedulingClass=idle 30 | IOSchedulingPriority=7 31 | CPUWeight=20 32 | AllowedCPUs=1-2 33 | MemoryMax=250M 34 | 35 | [Install] 36 | WantedBy=multi-user.target 37 | -------------------------------------------------------------------------------- /service/camera-streamer-raspi-v2-8MP.service: -------------------------------------------------------------------------------- 1 | ; 2 | ; Official Raspberry Pi v2.1 8MP camera based on the Sony IMX219 chip 3 | ; https://www.raspberrypi.com/products/camera-module-v2/ 4 | ; 5 | [Unit] 6 | Description=camera-streamer web camera for Pi Camera v2.1 8MP on Raspberry PI 7 | After=network.target 8 | ConditionPathExists=/sys/bus/i2c/drivers/imx219/10-0010/video4linux 9 | 10 | [Service] 11 | ExecStart=/usr/local/bin/camera-streamer \ 12 | -camera-path=/base/soc/i2c0mux/i2c@1/imx219@10 \ 13 | -camera-type=libcamera \ 14 | -camera-format=YUYV \ 15 | -camera-width=3280 -camera-height=2464 \ 16 | -camera-fps=30 \ 17 | ; use two memory buffers to optimise usage 18 | -camera-nbufs=2 \ 19 | ; the snapshot is 1438x1080 20 | -camera-snapshot.height=1080 \ 21 | ; the video/webrtc is 958x720 22 | -camera-video.height=720 \ 23 | ; the stream is 639x480 24 | -camera-stream.height=480 \ 25 | ; bump brightness slightly 26 | -camera-options=brightness=0.1 \ 27 | --http-listen=0.0.0.0 \ 28 | --http-port=8080 \ 29 | -rtsp-port 30 | 31 | DynamicUser=yes 32 | SupplementaryGroups=video i2c 33 | Restart=always 34 | RestartSec=10 35 | Nice=10 36 | IOSchedulingClass=idle 37 | IOSchedulingPriority=7 38 | CPUWeight=20 39 | AllowedCPUs=1-2 40 | MemoryMax=250M 41 | 42 | [Install] 43 | WantedBy=multi-user.target 44 | -------------------------------------------------------------------------------- /service/camera-streamer-raspi-v3-12MP.service: -------------------------------------------------------------------------------- 1 | ; 2 | ; Official Raspberry Pi Camera Module 3 12MP camera based on the Sony IMX708 3 | ; https://www.raspberrypi.com/products/camera-module-3/ 4 | ; 5 | [Unit] 6 | Description=camera-streamer web camera for Pi Camera Module 3 12MP on Raspberry PI 7 | After=network.target 8 | ConditionPathExists=/sys/bus/i2c/drivers/imx708/10-001a/video4linux 9 | 10 | [Service] 11 | ExecStart=/usr/local/bin/camera-streamer \ 12 | -camera-path=/base/soc/i2c0mux/i2c@1/imx708@1a \ 13 | -camera-type=libcamera \ 14 | -camera-format=YUYV \ 15 | -camera-width=2304 -camera-height=1296 \ 16 | -camera-fps=30 \ 17 | ; use two memory buffers to optimise usage 18 | -camera-nbufs=2 \ 19 | ; the snapshot is 1920x1080 20 | -camera-snapshot.height=1080 \ 21 | ; the video/webrtc is 1280x720 22 | -camera-video.height=720 \ 23 | ; the stream is 853x480 24 | -camera-stream.height=480 \ 25 | ; enable continuous autofocus 26 | -camera-options=AfMode=2 \ 27 | -camera-options=AfRange=2 \ 28 | --http-listen=0.0.0.0 \ 29 | --http-port=8080 \ 30 | -rtsp-port 31 | 32 | DynamicUser=yes 33 | SupplementaryGroups=video i2c 34 | Restart=always 35 | RestartSec=10 36 | Nice=10 37 | IOSchedulingClass=idle 38 | IOSchedulingPriority=7 39 | CPUWeight=20 40 | AllowedCPUs=1-2 41 | MemoryMax=250M 42 | 43 | [Install] 44 | WantedBy=multi-user.target 45 | -------------------------------------------------------------------------------- /tests/broken.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayufan/camera-streamer/4203f89df1596cc349b0260f26bf24a3c446a56b/tests/broken.jpeg -------------------------------------------------------------------------------- /tests/capture.bg10p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayufan/camera-streamer/4203f89df1596cc349b0260f26bf24a3c446a56b/tests/capture.bg10p -------------------------------------------------------------------------------- /tests/capture.h264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayufan/camera-streamer/4203f89df1596cc349b0260f26bf24a3c446a56b/tests/capture.h264 -------------------------------------------------------------------------------- /tests/capture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayufan/camera-streamer/4203f89df1596cc349b0260f26bf24a3c446a56b/tests/capture.jpeg -------------------------------------------------------------------------------- /tests/capture.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | cd "$SCRIPT_DIR" 5 | 6 | set -xeo pipefail 7 | 8 | [[ -e capture.jpeg ]] || libcamera-jpeg --timeout 1000 --output capture.jpeg --width 1920 --height 1080 --encoding jpg 9 | [[ -e capture.yuv420 ]] || libcamera-jpeg --timeout 1000 --output capture.yuv420 --width 1920 --height 1080 --encoding yuv420 10 | [[ -e capture.h264 ]] || libcamera-vid --frames 1 --output capture.h264 --width 1920 --height 1080 --codec h264 --profile main --level 4.2 11 | 12 | # This is not ideal as `libcamera-raw` does not respect `--frames` 13 | [[ -e capture.bg10p ]] || libcamera-raw --frames 1 --timeout 300 --verbose 2 --flush --output capture.bg10p --width 2304 --height 1296 14 | -------------------------------------------------------------------------------- /tests/capture.yuv420: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayufan/camera-streamer/4203f89df1596cc349b0260f26bf24a3c446a56b/tests/capture.yuv420 -------------------------------------------------------------------------------- /tests/dummy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -lt 1 ]]; then 4 | echo "usage: $0 [camera-streamer options]" 5 | echo 6 | echo "examples:" 7 | echo " $0 tests/capture.jpeg" 8 | echo " $0 tests/capture.jpeg --video-height=720" 9 | echo " $0 tests/capture.jpeg --snapshot-height=720 --video-height=480" 10 | echo " $0 tests/capture.h264" 11 | exit 1 12 | fi 13 | 14 | INPUT=$(realpath "$1") 15 | shift 16 | 17 | case "$INPUT" in 18 | *.jpeg) 19 | set -- --camera-format=JPEG --camera-width=1920 --camera-height=1080 "$@" 20 | ;; 21 | 22 | *.yuv420) 23 | set -- --camera-format=YUV420 --camera-width=1920 --camera-height=1080 "$@" 24 | ;; 25 | 26 | *.h264) 27 | set -- --camera-format=H264 --camera-width=1920 --camera-height=1080 "$@" 28 | ;; 29 | 30 | *.bg10p) 31 | set -- --camera-format=BG10P --camera-width=2304 --camera-height=1296 "$@" 32 | ;; 33 | 34 | *) 35 | echo "$0: undefined format for $INPUT." 36 | exit 1 37 | esac 38 | 39 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 40 | cd "$SCRIPT_DIR/.." 41 | 42 | set -xeo pipefail 43 | make -j$(nproc) 44 | 45 | exec ./camera-streamer \ 46 | --camera-type=dummy \ 47 | --camera-path="$INPUT" \ 48 | --camera-snapshot.height=1080 \ 49 | "$@" 50 | -------------------------------------------------------------------------------- /tests/libcamera/orientation.cc: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /tools/csi_camera.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | cd "$SCRIPT_DIR/.." 5 | 6 | CAMERA_PATH=( $(echo /dev/v4l/by-path/*.csi-video-index0) ) 7 | 8 | if [[ "$1" == "dump" ]]; then 9 | shift 10 | libcamera-still "$@" 11 | echo 12 | v4l2-ctl -d /dev/v4l-subdev0 \ 13 | -C exposure -C vertical_blanking -C analogue_gain -C digital_gain | \ 14 | sed -e 's/ //g' -e 's/:/=/g' -e 's/^/-camera-options=/g' -e 's/$/ \\/g' 15 | v4l2-ctl -d /dev/video13 -C red_balance -C colour_correction_matrix \ 16 | -C black_level -C green_equalisation -C gamma -C denoise -C sharpen \ 17 | -C defective_pixel_correction -C colour_denoise | \ 18 | sed -e 's/ //g' -e 's/:/=/g' -e 's/^/-camera-isp.options=/g' -e 's/$/ \\/g' 19 | exit 0 20 | fi 21 | 22 | set -xeo pipefail 23 | make -j$(nproc) 24 | $GDB ./camera-streamer -camera-path="${CAMERA_PATH[0]}" \ 25 | --http-listen=0.0.0.0 \ 26 | -camera-options=vertical_blanking=728 \ 27 | -camera-options=exposure=2444 \ 28 | -camera-options=analogue_gain=600 \ 29 | -camera-options=digital_gain=256 \ 30 | -camera-isp.options=digital_gain=2015 \ 31 | -camera-isp.options=red_balance=1852 \ 32 | -camera-isp.options=blue_balance=2146 \ 33 | -camera-isp.options=colour_correction_matrix=1,0,0,0,146,4,0,0,232,3,0,0,211,255,255,255,232,3,0,0,132,255,255,255,232,3,0,0,91,255,255,255,232,3,0,0,34,5,0,0,232,3,0,0,107,255,255,255,232,3,0,0,37,0,0,0,232,3,0,0,226,254,255,255,232,3,0,0,225,4,0,0,232,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0 \ 34 | -camera-isp.options=black_level=1,0,0,0,0,16,0,16,0,16,38,134 \ 35 | -camera-isp.options=green_equalisation=1,0,0,0,89,2,0,0,47,0,0,0,232,3,0,0 \ 36 | -camera-isp.options=gamma=1,0,0,0,0,0,0,4,0,8,0,12,0,16,0,20,0,24,0,28,0,32,0,36,0,40,0,44,0,48,0,52,0,56,0,60,0,64,0,72,0,80,0,88,0,96,0,104,0,112,0,120,0,128,0,144,0,160,0,176,0,192,0,208,0,224,0,240,255,255,0,0,176,19,122,36,68,48,208,59,131,70,54,81,153,90,144,100,38,109,83,117,5,125,252,132,115,140,155,147,236,153,213,159,194,170,34,180,42,188,132,195,237,201,85,207,197,211,36,216,86,224,159,230,207,235,185,240,80,245,160,249,109,253,255,255 \ 37 | -camera-isp.options=denoise=1,0,0,0,0,0,0,0,240,30,0,0,232,3,0,0,238,2,0,0,232,3,0,0 \ 38 | -camera-isp.options=sharpen=1,0,0,0,208,7,0,0,232,3,0,0,244,1,0,0,232,3,0,0,244,1,0,0,232,3,0,0 \ 39 | -camera-isp.options=defective_pixel_correction=1,0,0,0,1,0,0,0 \ 40 | -camera-isp.options=colour_denoise=0,0,0,0,127,0,0,0 \ 41 | "$@" 42 | -------------------------------------------------------------------------------- /tools/dump_cameras.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec 2>&1 4 | 5 | prefix() { 6 | while IFS= read -r LINE; do 7 | printf "%s | %s\n" "$1" "$LINE" 8 | done 9 | } 10 | 11 | ( set -x ; echo "$(cat /sys/firmware/devicetree/base/model | tr -d '\0')" ) 12 | 13 | ( set -x ; uname -a ) 14 | 15 | ( set -x ; v4l2-ctl --list-devices ) | prefix "v4l2-ctl" 16 | 17 | ( set -x ; libcamera-hello --list-cameras ) | prefix "libcamera" 18 | 19 | for device in /dev/video*; do 20 | echo "====================================" 21 | echo "DEVICE: $device" 22 | echo "====================================" 23 | ( 24 | set -x 25 | v4l2-ctl -d "$device" --info \ 26 | --list-formats-ext --list-fields \ 27 | --list-formats-out --list-fields-out \ 28 | --list-ctrls 29 | ) | prefix "$device" 30 | echo 31 | done 32 | -------------------------------------------------------------------------------- /tools/libcamera_camera.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | cd "$SCRIPT_DIR/.." 5 | 6 | set -xeo pipefail 7 | make -j$(nproc) 8 | $GDB ./camera-streamer \ 9 | --http-listen=0.0.0.0 \ 10 | -camera-type=libcamera \ 11 | -camera-format=YUYV \ 12 | "$@" 13 | -------------------------------------------------------------------------------- /tools/rpi_debug.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# -ne 1 ]]; then 4 | echo "usage: $0 " 5 | exit 1 6 | fi 7 | 8 | debug="0" 9 | [[ "$1" != "on" ]] || debug=0xFFFFFF 10 | 11 | set -x 12 | 13 | for module in /sys/module/bcm2835_*; do 14 | echo $debug | tee $module/parameters/debug 15 | done 16 | -------------------------------------------------------------------------------- /tools/rpi_measure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Taken from: https://gist.github.com/TheRemote/10bda1ac790f959210db5789f5241436 4 | 5 | # Output current configuration 6 | #vcgencmd get_config int | egrep "(arm|core|gpu|sdram)_freq|over_volt" 7 | 8 | # Measure clock speeds 9 | for src in arm core h264 isp v3d; do echo -e "$src:\t$(vcgencmd measure_clock $src)"; done 10 | 11 | # Measure Volts 12 | for id in core sdram_c sdram_i sdram_p ; do echo -e "$id:\t$(vcgencmd measure_volts $id)"; done 13 | 14 | # Measure Temperature 15 | vcgencmd measure_temp 16 | 17 | # See if we are being throttled 18 | throttled="$(vcgencmd get_throttled)" 19 | echo -e "$throttled" 20 | if [[ $throttled != "throttled=0x0" ]]; then 21 | echo "WARNING: You are being throttled. This is likely because you are undervoltage. Please connect your PI to a better power supply!" 22 | fi 23 | -------------------------------------------------------------------------------- /tools/rpi_mem_usage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | get_mem() { 4 | local mem=$(vcgencmd get_mem "$1" | cut -d= -f2) 5 | shift 6 | echo -e "$@\t\t$mem" 7 | } 8 | 9 | ( 10 | get_mem arm "Total ARM" 11 | get_mem gpu "Total GPU" 12 | get_mem malloc_total "GPU Malloc" 13 | get_mem malloc "GPU Malloc Free" 14 | get_mem reloc_total "GPU Reloc" 15 | get_mem reloc "GPU Reloc Free" 16 | cat /sys/kernel/debug/vcsm-cma/state | grep "SIZE" | awk '{s+=$2} END {printf("CMA\t\t\t%.1fMB\n", s/1024/1024)}' 17 | ) 18 | 19 | echo "--------" 20 | vcdbg reloc | sed -s 's/^/reloc: /' 21 | 22 | echo "--------" 23 | vcdbg malloc | sed -s 's/^/maloc: /' 24 | echo "--------" 25 | cat /sys/kernel/debug/vcsm-cma/state | sed -s 's/^/cma: /' 26 | echo "--------" 27 | -------------------------------------------------------------------------------- /tools/run_all.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | COLOR_NC='\033[0m' # No Color 4 | COLOR_WHITE='\033[1;37m' 5 | COLOR_BLACK='\033[0;30m' 6 | COLOR_BLUE='\033[0;34m' 7 | COLOR_LIGHT_BLUE='\033[1;34m' 8 | COLOR_GREEN='\033[0;32m' 9 | COLOR_LIGHT_GREEN='\033[1;32m' 10 | COLOR_CYAN='\033[0;36m' 11 | COLOR_LIGHT_CYAN='\033[1;36m' 12 | COLOR_RED='\033[0;31m' 13 | COLOR_LIGHT_RED='\033[1;31m' 14 | COLOR_PURPLE='\033[0;35m' 15 | COLOR_LIGHT_PURPLE='\033[1;35m' 16 | COLOR_BROWN='\033[0;33m' 17 | COLOR_YELLOW='\033[1;33m' 18 | COLOR_GRAY='\033[1;30m' 19 | COLOR_LIGHT_GRAY='\033[0;37m' 20 | 21 | COLORS=( $COLOR_BLUE $COLOR_CYAN $COLOR_PURPLE $COLOR_BROWN $COLOR_YELLOW $COLOR_GRAY ) 22 | 23 | run_camera() { 24 | local name="$1" 25 | shift 26 | 27 | job_nb=$(jobs -p -r | wc -l) 28 | color_idx=$(($job_nb%${#COLORS[@]})) 29 | color=${COLORS[$color_idx]} 30 | port=$((8080+$job_nb)) 31 | 32 | ./camera-streamer --http-port=$port --camera-fps=20 --camera-force_active --log-stats "$@" |& 33 | ts "$(printf "$color%%H:%%M:%%S %10s |$COLOR_NC" "$name")" & 34 | } 35 | 36 | trap 'jobs -p -r | xargs -r kill; wait' EXIT 37 | 38 | export FAKE_CAMERA_SENSOR=arducam_64mp=imx519 39 | 40 | set -eo pipefail 41 | 42 | make -j$(nproc) 43 | 44 | run_camera csi --camera-type=libcamera --camera-format=YUYV --camera-path=i2c0mux --camera-fps=20 "$@" 45 | 46 | for usbcam in /dev/v4l/by-id/usb-*-video-index0; do 47 | name=$(basename "$usbcam") 48 | name="${name#usb-}" 49 | name="${name%%-video-index0}" 50 | name="${name:0:10}" 51 | run_camera "$name" --camera-path="$usbcam" --camera-format=MJPEG --camera-fps=30 "$@" 52 | done 53 | 54 | wait 55 | -------------------------------------------------------------------------------- /tools/usb_camera.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 4 | cd "$SCRIPT_DIR/.." 5 | 6 | CAMERA_PATH=( $(echo /dev/v4l/by-id/usb-*-video-index0) ) 7 | 8 | set -xeo pipefail 9 | make -j$(nproc) 10 | $GDB ./camera-streamer \ 11 | -camera-path="${CAMERA_PATH[${CAMERA_INDEX:-0}]}" \ 12 | --http-listen=0.0.0.0 \ 13 | "$@" 14 | -------------------------------------------------------------------------------- /util/ffmpeg/remuxer.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef USE_FFMPEG 4 | #include 5 | #include 6 | #include 7 | 8 | #define FFMPEG_DATA_PACKET_EOF AVERROR_EOF 9 | #else 10 | #define FFMPEG_DATA_PACKET_EOF -1 11 | #endif 12 | 13 | typedef int (*ffmpeg_data_packet)(void *opaque, uint8_t *buf, int buf_size); 14 | 15 | typedef struct ffmpeg_remuxer_s { 16 | const char *name; 17 | const char *input_format; 18 | const char *video_format; 19 | void *opaque; 20 | ffmpeg_data_packet read_packet; 21 | ffmpeg_data_packet write_packet; 22 | unsigned read_buffer_size; 23 | unsigned write_buffer_size; 24 | 25 | uint64_t start_time; 26 | unsigned frames; 27 | 28 | #ifdef USE_FFMPEG 29 | AVIOContext *input_avio; 30 | AVFormatContext *input_context; 31 | AVDictionary *input_opts; 32 | AVIOContext *output_avio; 33 | AVFormatContext *output_context; 34 | AVPacket *packet; 35 | AVDictionary *output_opts; 36 | int video_stream; 37 | #endif 38 | } ffmpeg_remuxer_t; 39 | 40 | int ffmpeg_remuxer_open(ffmpeg_remuxer_t *remuxer); 41 | int ffmpeg_remuxer_feed(ffmpeg_remuxer_t *remuxer, int nframes); 42 | int ffmpeg_remuxer_flush(ffmpeg_remuxer_t *remuxer); 43 | void ffmpeg_remuxer_close(ffmpeg_remuxer_t *remuxer); 44 | -------------------------------------------------------------------------------- /util/http/http.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct buffer_s buffer_t; 11 | typedef struct http_worker_s http_worker_t; 12 | 13 | typedef void (*http_method_fn)(struct http_worker_s *worker, FILE *stream); 14 | typedef void *(*http_param_fn)(struct http_worker_s *worker, FILE *stream, const char *key, const char *value, void *opaque); 15 | 16 | #define BUFSIZE 256 17 | 18 | typedef struct http_method_s { 19 | const char *method; 20 | const char *uri; 21 | http_method_fn func; 22 | const char *content_type; 23 | const void *content_body; 24 | unsigned content_length; 25 | unsigned *content_lengthp; 26 | } http_method_t; 27 | 28 | typedef struct http_server_options_s { 29 | char listen[512]; 30 | unsigned port; 31 | unsigned maxcons; 32 | } http_server_options_t; 33 | 34 | typedef struct http_worker_s { 35 | char *name; 36 | int listen_fd; 37 | http_method_t *methods; 38 | pthread_t thread; 39 | http_server_options_t options; 40 | 41 | int client_fd; 42 | int content_length; 43 | struct sockaddr_in client_addr; 44 | char *client_host; 45 | char client_method[BUFSIZE]; 46 | char range_header[BUFSIZE]; 47 | char user_agent[BUFSIZE]; 48 | char host[BUFSIZE]; 49 | char *request_method; 50 | char *request_uri; 51 | char *request_params; 52 | char *request_version; 53 | 54 | http_method_t *current_method; 55 | } http_worker_t; 56 | 57 | int http_server(http_server_options_t *options, http_method_t *methods); 58 | void http_content(http_worker_t *worker, FILE *stream); 59 | void http_write_response(FILE *stream, const char *status, const char *content_type, const char *body, unsigned content_length); 60 | void http_write_responsef(FILE *stream, const char *status, const char *content_type, const char *fmt, ...); 61 | void http_200(FILE *stream, const char *data); 62 | void http_400(FILE *stream, const char *data); 63 | void http_404(FILE *stream, const char *data); 64 | void http_500(FILE *stream, const char *data); 65 | void *http_enum_params(http_worker_t *worker, FILE *stream, http_param_fn fn, void *opaque); 66 | char *http_get_param(http_worker_t *worker, const char *key); 67 | -------------------------------------------------------------------------------- /util/http/http_methods.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "http.h" 6 | 7 | void http_write_response( 8 | FILE *stream, 9 | const char *status, 10 | const char *content_type, 11 | const char *body, 12 | unsigned content_length) 13 | { 14 | if (content_length == 0 && body) 15 | content_length = strlen(body); 16 | 17 | fprintf(stream, "HTTP/1.1 %s\r\n", status ? status : "200 OK"); 18 | fprintf(stream, "Content-Type: %s\r\n", content_type ? content_type : "text/plain"); 19 | if (content_length > 0) 20 | fprintf(stream, "Content-Length: %d\r\n", content_length); 21 | if (!status || strstr(status, "200 OK") == status) 22 | fprintf(stream, "Access-Control-Allow-Origin: *\r\n"); 23 | fprintf(stream, "\r\n"); 24 | if (body) { 25 | fwrite(body, 1, content_length, stream); 26 | } 27 | } 28 | 29 | void http_write_responsef(FILE *stream, const char *status, const char *content_type, const char *fmt, ...) 30 | { 31 | va_list arg; 32 | va_start(arg, fmt); 33 | 34 | char *body = NULL; 35 | size_t n = vasprintf(&body, fmt, arg); 36 | http_write_response(stream, status, content_type, body, n); 37 | free(body); 38 | } 39 | 40 | void http_content(http_worker_t *worker, FILE *stream) 41 | { 42 | if (worker->current_method) { 43 | if (worker->current_method->content_lengthp) { 44 | worker->current_method->content_length = *worker->current_method->content_lengthp; 45 | } 46 | 47 | http_write_response(stream, 48 | NULL, 49 | worker->current_method->content_type, 50 | worker->current_method->content_body, 51 | worker->current_method->content_length 52 | ); 53 | } else { 54 | http_write_response(stream, NULL, NULL, "No data", 0); 55 | } 56 | } 57 | 58 | void http_200(FILE *stream, const char *data) 59 | { 60 | http_write_response(stream, "200 OK", NULL, data ? data : "Nothing here.\n", 0); 61 | } 62 | 63 | void http_400(FILE *stream, const char *data) 64 | { 65 | http_write_response(stream, "400 Bad Request", NULL, data ? data : "Nothing here.\n", 0); 66 | } 67 | 68 | void http_404(FILE *stream, const char *data) 69 | { 70 | http_write_response(stream, "404 Not Found", NULL, data ? data : "Nothing here.\n", 0); 71 | } 72 | 73 | void http_500(FILE *stream, const char *data) 74 | { 75 | http_write_response(stream, "500 Server Error", NULL, data ? data : "Server Error\n", 0); 76 | } 77 | -------------------------------------------------------------------------------- /util/http/json.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" { 4 | #include "http.h" 5 | } 6 | 7 | #include 8 | #include 9 | 10 | inline nlohmann::json http_parse_json_body(http_worker_t *worker, FILE *stream, unsigned max_body_size) 11 | { 12 | std::string text; 13 | 14 | size_t i = 0; 15 | size_t n = (size_t)worker->content_length; 16 | if (n < 0 || n > max_body_size) 17 | n = max_body_size; 18 | 19 | text.resize(n); 20 | 21 | while (i < n && !feof(stream)) { 22 | i += fread(&text[i], 1, n-i, stream); 23 | } 24 | text.resize(i); 25 | 26 | return nlohmann::json::parse(text); 27 | } 28 | -------------------------------------------------------------------------------- /util/opts/control.c: -------------------------------------------------------------------------------- 1 | #include "control.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static bool isalnum_dot(char c) 8 | { 9 | return isalnum(c) || c == '.'; 10 | } 11 | 12 | int device_option_normalize_name(const char *in, char *outp) 13 | { 14 | // The output is always shorter, so `outp=in` 15 | // colour_correction_matrix => colourcorrectionmatrix 16 | // Colour Correction Matrix => colourcorrectionmatrix 17 | // ColourCorrectionMatrix => colourcorrectionmatrix 18 | // Colour.Correction.Matrix => colour.correction.matrix 19 | 20 | char *out = outp; 21 | 22 | while (*in) { 23 | if (isalnum_dot(*in)) { 24 | *out++ = tolower(*in++); 25 | } else { 26 | in++; 27 | } 28 | } 29 | 30 | *out++ = 0; 31 | return out - outp - 1; 32 | } 33 | 34 | bool device_option_is_equal(const char *a, const char *b) 35 | { 36 | // Similar to device_option_normalize_name 37 | // but ignore case sensitiveness 38 | 39 | while (*a || *b) { 40 | while (*a && !isalnum_dot(*a)) 41 | a++; 42 | while (*b && !isalnum_dot(*b)) 43 | b++; 44 | 45 | if (tolower(*a) != tolower(*b)) 46 | return 0; 47 | 48 | a++; 49 | b++; 50 | } 51 | 52 | return 1; 53 | } 54 | -------------------------------------------------------------------------------- /util/opts/control.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int device_option_normalize_name(const char *in, char *out); 4 | bool device_option_is_equal(const char *a, const char *b); 5 | -------------------------------------------------------------------------------- /util/opts/fourcc.c: -------------------------------------------------------------------------------- 1 | #include "fourcc.h" 2 | 3 | #include 4 | 5 | fourcc_string fourcc_to_string(unsigned format) 6 | { 7 | fourcc_string fourcc; 8 | char *ptr = fourcc.buf; 9 | *ptr++ = format & 0x7F; 10 | *ptr++ = (format >> 8) & 0x7F; 11 | *ptr++ = (format >> 16) & 0x7F; 12 | *ptr++ = (format >> 24) & 0x7F; 13 | if (format & ((unsigned)1 << 31)) { 14 | *ptr++ = '-'; 15 | *ptr++ = 'B'; 16 | *ptr++ = 'E'; 17 | *ptr++ = '\0'; 18 | } else { 19 | *ptr++ = '\0'; 20 | } 21 | *ptr++ = 0; 22 | return fourcc; 23 | } 24 | 25 | many_fourcc_string many_fourcc_to_string(unsigned formats[]) 26 | { 27 | many_fourcc_string fourcc = {0}; 28 | 29 | for (int i = 0; formats[i]; i++) { 30 | if (fourcc.buf[0]) { 31 | strcat(fourcc.buf, ", "); 32 | } 33 | strcat(fourcc.buf, fourcc_to_string(formats[i]).buf); 34 | } 35 | 36 | return fourcc; 37 | } 38 | -------------------------------------------------------------------------------- /util/opts/fourcc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | char buf[10]; 8 | } fourcc_string; 9 | 10 | typedef struct { 11 | char buf[250]; 12 | } many_fourcc_string; 13 | 14 | fourcc_string fourcc_to_string(unsigned format); 15 | many_fourcc_string many_fourcc_to_string(unsigned formats[]); 16 | -------------------------------------------------------------------------------- /util/opts/helpers.hh: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | inline std::vector str_split(const std::string& in, const char seperator) 8 | { 9 | std::vector output; 10 | std::istringstream stream(in); 11 | for (std::string s; std::getline(stream, s, seperator); ) { 12 | output.push_back(s); 13 | } 14 | return output; 15 | } 16 | -------------------------------------------------------------------------------- /util/opts/log.c: -------------------------------------------------------------------------------- 1 | #include "util/opts/log.h" 2 | #include "util/opts/opts.h" 3 | 4 | #include 5 | #include 6 | 7 | char * 8 | strstrn(const char *s, const char *find, size_t len) 9 | { 10 | char c, sc; 11 | if ((c = *find++) != 0) { 12 | len--; 13 | 14 | do { 15 | do { 16 | if ((sc = *s++) == 0) 17 | return (NULL); 18 | } while (sc != c); 19 | } while (strncmp(s, find, len) != 0); 20 | s--; 21 | } 22 | return ((char *)s); 23 | } 24 | 25 | bool filter_log(const char *filename) 26 | { 27 | if (!log_options.filter[0]) 28 | return false; 29 | 30 | const char *ptr = log_options.filter; 31 | do { 32 | const char *next = strchr(ptr, OPTION_VALUE_LIST_SEP[0]); 33 | if (!next) 34 | next = ptr + strlen(ptr); 35 | 36 | if(strstrn(filename, ptr, next - ptr)) 37 | return true; 38 | 39 | ptr = next; 40 | } while(*ptr++); 41 | 42 | return false; 43 | } 44 | 45 | int shrink_to_block(int size, int block) 46 | { 47 | return size / block * block; 48 | } 49 | 50 | uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us) 51 | { 52 | struct timespec now; 53 | 54 | if (clock != CLOCK_FROM_PARAMS) { 55 | clock_gettime(clock, &now); 56 | } else if (ts) { 57 | now = *ts; 58 | } else if (tv) { 59 | now.tv_sec = tv->tv_sec; 60 | now.tv_nsec = tv->tv_usec * 1000; 61 | } else { 62 | return -1; 63 | } 64 | 65 | if (delays_us > 0) { 66 | #define NS_IN_S (1000LL * 1000LL * 1000LL) 67 | int64_t nsec = now.tv_nsec + delays_us * 1000LL; 68 | now.tv_nsec = nsec % NS_IN_S; 69 | now.tv_sec += nsec / NS_IN_S; 70 | } 71 | 72 | if (ts) { 73 | *ts = now; 74 | } 75 | if (tv) { 76 | tv->tv_sec = now.tv_sec; 77 | tv->tv_usec = now.tv_nsec / 1000; 78 | } 79 | return now.tv_sec * 1000000LL + now.tv_nsec / 1000; 80 | } 81 | 82 | uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv) 83 | { 84 | return get_time_us(CLOCK_MONOTONIC, ts, tv, 0); 85 | } 86 | 87 | int ioctl_retried(const char *name, int fd, int request, void *arg) 88 | { 89 | #define MAX_RETRIES 10 90 | #define RETRY_INTERVAL_US 1000 91 | 92 | for (int try = 0; try <= MAX_RETRIES; try++) { 93 | int ret = ioctl(fd, request, arg); 94 | if (ret >= 0) 95 | return ret; 96 | if (errno != EINTR && errno != EAGAIN && errno != ETIMEDOUT) 97 | return ret; 98 | usleep(RETRY_INTERVAL_US); 99 | } 100 | 101 | LOG_INFO(NULL, "%s: ioctl(%08x, errno=%d) retried %u times", name, request, errno, MAX_RETRIES); 102 | return -1; 103 | } 104 | 105 | char *trim(char *s) 106 | { 107 | // skip left side white spaces 108 | while (isspace (*s)) 109 | s++; 110 | 111 | // skip right side white spaces 112 | char *e = s + strlen(s) - 1; 113 | while (e >= s && isspace(*e)) 114 | *e-- = 0; 115 | 116 | return s; 117 | } 118 | -------------------------------------------------------------------------------- /util/opts/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #define __FILENAME__ __FILE__ 25 | 26 | typedef struct log_options_s { 27 | bool debug; 28 | bool verbose; 29 | unsigned stats; 30 | char filter[256]; 31 | } log_options_t; 32 | 33 | extern log_options_t log_options; 34 | 35 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 36 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 37 | 38 | bool filter_log(const char *filename); 39 | 40 | // assumes that name is first item 41 | #define dev_name(dev) (dev ? *(const char**)dev : "?") 42 | #define LOG_ERROR(dev, _msg, ...) do { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); goto error; } while(0) 43 | #define LOG_PERROR(dev, _msg, ...) do { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); exit(-1); } while(0) 44 | #define LOG_INFO(dev, _msg, ...) do { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } while(0) 45 | #define LOG_VERBOSE(dev, _msg, ...) do { if (log_options.debug || log_options.verbose || filter_log(__FILENAME__)) { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } } while(0) 46 | #define LOG_DEBUG(dev, _msg, ...) do { if (log_options.debug || filter_log(__FILENAME__)) { fprintf(stderr, "%s: %s: " _msg "\n", __FILENAME__, dev_name(dev), ##__VA_ARGS__); } } while(0) 47 | 48 | #define CLOCK_FROM_PARAMS -1 49 | 50 | uint64_t get_monotonic_time_us(struct timespec *ts, struct timeval *tv); 51 | uint64_t get_time_us(clockid_t clock, struct timespec *ts, struct timeval *tv, int64_t delays_us); 52 | int shrink_to_block(int size, int block); 53 | char *trim(char *s); 54 | 55 | int ioctl_retried(const char *name, int fd, int request, void *arg); 56 | 57 | #define ERR_IOCTL(dev, _fd, _request, _value, _msg, ...) do { \ 58 | int ret; \ 59 | if ((ret = ioctl_retried(dev_name(dev), _fd, _request, _value)) < 0) { \ 60 | LOG_ERROR(dev, "ioctl(ret=%d, errno=%d): " _msg, ret, errno, ##__VA_ARGS__); \ 61 | } \ 62 | } while(0) 63 | 64 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 65 | #define ARRAY_APPEND(arr, n_arr, item) ((n_arr) < ARRAY_SIZE(arr) ? ((arr[n_arr++] = item), true) : false) 66 | #define ARRAY_FOREACH(type, key, arr, n_arr) \ 67 | for (type *key = &arr[0]; key < &arr[n_arr]; key++) 68 | -------------------------------------------------------------------------------- /util/opts/opts.c: -------------------------------------------------------------------------------- 1 | #include "opts.h" 2 | #include "version.h" 3 | #include "util/opts/log.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define OPT_LENGTH 30 10 | 11 | const char *opt_value_to_string(const option_value_t *values, int value, const char *def) 12 | { 13 | for (int i = 0; values[i].name; i++) { 14 | if (values[i].value == value) { 15 | return values[i].name; 16 | } 17 | } 18 | return def; 19 | } 20 | 21 | int opt_string_to_value(const option_value_t *values, const char *name, int def) 22 | { 23 | for (int i = 0; values[i].name; i++) { 24 | if (!strcmp(values[i].name, name)) { 25 | return values[i].value; 26 | } 27 | } 28 | return def; 29 | } 30 | 31 | static void print_version(const char *cmd) 32 | { 33 | printf("%s (%s)\n", GIT_VERSION, GIT_REVISION); 34 | } 35 | 36 | static void print_help(option_t *options, const char *cmd) 37 | { 38 | printf("Usage:\n"); 39 | printf("$ %s \n", cmd); 40 | printf("\n"); 41 | 42 | printf("Options:\n"); 43 | for (int i = 0; options[i].name; i++) { 44 | option_t *option = &options[i]; 45 | int len = 0; 46 | 47 | len += printf(" --%s", option->name); 48 | if (option->default_value) { 49 | len += printf("[=%s]", option->default_value); 50 | } else if (option->value_mapping) { 51 | len += printf("=arg"); 52 | } else { 53 | len += printf("=%s", option->format); 54 | } 55 | if (len < OPT_LENGTH) { 56 | printf("%*s", OPT_LENGTH-len, " "); 57 | } 58 | 59 | printf("- %s", option->description); 60 | if (option->value_mapping) { 61 | printf(" Values: "); 62 | for (int j = 0; option->value_mapping[j].name; j++) { 63 | if (j > 0) printf(", "); 64 | printf("%s", option->value_mapping[j].name); 65 | } 66 | printf("."); 67 | } 68 | printf("\n"); 69 | } 70 | printf("\n"); 71 | 72 | printf("Command line:\n\n"); 73 | 74 | printf("$ %s \\\n", cmd); 75 | for (int i = 0; options[i].name; i++) { 76 | option_t *option = &options[i]; 77 | int len = 0; 78 | 79 | if (option->value_list) { 80 | char *string = option->value_list; 81 | char *token; 82 | 83 | if (!*string) 84 | continue; 85 | 86 | while ((token = strsep(&string, OPTION_VALUE_LIST_SEP)) != NULL) { 87 | len = printf(" --%s=", option->name); 88 | printf("%s \\\n", token); 89 | } 90 | } else if (option->value_string) { 91 | len += printf(" --%s=", option->name); 92 | printf(option->format, option->value_string); 93 | printf(" \\\n"); 94 | } else { 95 | bool found = false; 96 | len += printf(" --%s=", option->name); 97 | if (option->value_mapping) { 98 | const char *name = opt_value_to_string(option->value_mapping, *option->value_uint, NULL); 99 | if (name) { 100 | len += printf("%s", name); 101 | found = true; 102 | } 103 | } 104 | 105 | if (!found) { 106 | unsigned mask = UINT_MAX >> ((sizeof(*option->value_uint) - option->size) * 8); 107 | printf(option->format, *option->value_uint & mask); 108 | } 109 | printf(" \\\n"); 110 | } 111 | } 112 | printf("\n"); 113 | } 114 | 115 | static int parse_opt(option_t *options, const char *key, int dash) 116 | { 117 | option_t *option = NULL; 118 | const char *value = strchr(key, '='); 119 | 120 | for (int i = 0; options[i].name; i++) { 121 | if (value) { 122 | if (!strncmp(key, options[i].name, value - key)) { 123 | option = &options[i]; 124 | value++; // ignore '=' 125 | break; 126 | } 127 | } else { 128 | // require exact match 129 | if (!strcmp(key, options[i].name)) { 130 | option = &options[i]; 131 | value = option->default_value; 132 | break; 133 | } 134 | } 135 | } 136 | 137 | if (dash == 2 && strlen(key) == 1) { 138 | LOG_INFO(NULL, "Usage of '--%s' is deprecated change to '-%s'.", key, key); 139 | } else if (dash == 1 && strlen(key) > 1) { 140 | LOG_INFO(NULL, "Usage of '-%s' is deprecated change to '--%s'.", key, key); 141 | } 142 | 143 | LOG_DEBUG(NULL, "Parsing '%s'. Got value='%s', and option='%s'", key, value, option ? option->name : NULL); 144 | 145 | if (!option || !value) { 146 | return -EINVAL; 147 | } 148 | 149 | int ret = 0; 150 | if (option->value_list) { 151 | char *ptr = option->value_list; 152 | 153 | if (*ptr) { 154 | strcat(ptr, OPTION_VALUE_LIST_SEP); 155 | ptr += strlen(ptr); 156 | } 157 | 158 | ret = sscanf(value, option->format, ptr); 159 | } else if (option->value_string) { 160 | ret = sscanf(value, option->format, option->value_string); 161 | } else if (option->value_mapping) { 162 | int ret = opt_string_to_value(option->value_mapping, value, -1); 163 | if (ret != -1) { 164 | *option->value_uint = ret; 165 | return 1; 166 | } 167 | 168 | ret = sscanf(value, option->format, option->value); 169 | } else { 170 | ret = sscanf(value, option->format, option->value); 171 | } 172 | return ret; 173 | } 174 | 175 | int parse_opts(option_t *options, int argc, char *argv[]) 176 | { 177 | int arg; 178 | 179 | for (arg = 1; arg < argc; arg++) { 180 | const char *key = argv[arg]; 181 | 182 | if (key[0] == '-') { 183 | key++; 184 | if (key[0] == '-') { 185 | key++; 186 | } 187 | } else { 188 | LOG_ERROR(NULL, "The '%s' is not option (should start with - or --).", key); 189 | } 190 | 191 | if (!strcmp(key, "help")) { 192 | print_help(options, argv[0]); 193 | exit(-1); 194 | return -1; 195 | } 196 | 197 | if (!strcmp(key, "version")) { 198 | print_version(argv[0]); 199 | exit(0); 200 | return 0; 201 | } 202 | 203 | int ret = parse_opt(options, key, key - argv[arg]); 204 | if (ret <= 0) { 205 | LOG_ERROR(NULL, "Parsing '%s' returned '%d'.", argv[arg], ret); 206 | } 207 | } 208 | 209 | return 0; 210 | 211 | error: 212 | return -1; 213 | } 214 | -------------------------------------------------------------------------------- /util/opts/opts.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct option_value_s { 6 | const char *name; 7 | unsigned value; 8 | } option_value_t; 9 | 10 | typedef struct options_s { 11 | const char *name; 12 | char *value_string; 13 | char *value_list; 14 | union { 15 | unsigned *value; 16 | unsigned *value_uint; 17 | unsigned *value_hex; 18 | bool *value_bool; 19 | float *value_float; 20 | }; 21 | const char *format; 22 | const char *help; 23 | option_value_t *value_mapping; 24 | unsigned size; 25 | const char *default_value; 26 | const char *description; 27 | } option_t; 28 | 29 | #define OPTION_VALUE_LIST_SEP_CHAR ';' 30 | #define OPTION_VALUE_LIST_SEP ";" 31 | 32 | #define OPTION_FORMAT_uint "%u" 33 | #define OPTION_FORMAT_hex "%08x" 34 | #define OPTION_FORMAT_bool "%d" 35 | #define OPTION_FORMAT_float "%f" 36 | #define OPTION_FORMAT_string "%s" 37 | #define OPTION_FORMAT_list "%s" 38 | 39 | #define DEFINE_OPTION(_section, _name, _type, _desc) \ 40 | { \ 41 | .name = #_section "-" #_name, \ 42 | .value_##_type = &_section##_options._name, \ 43 | .format = OPTION_FORMAT_##_type, \ 44 | .size = sizeof(_section##_options._name), \ 45 | .description = _desc, \ 46 | } 47 | 48 | #define DEFINE_OPTION_DEFAULT(_section, _name, _type, _default_value, _desc) \ 49 | { \ 50 | .name = #_section "-" #_name, \ 51 | .value_##_type = &_section##_options._name, \ 52 | .format = OPTION_FORMAT_##_type, \ 53 | .size = sizeof(_section##_options._name), \ 54 | .default_value = _default_value, \ 55 | .description = _desc, \ 56 | } 57 | 58 | #define DEFINE_OPTION_VALUES(_section, _name, _value_mapping, _desc) \ 59 | { \ 60 | .name = #_section "-" #_name, \ 61 | .value_hex = &_section##_options._name, \ 62 | .format = OPTION_FORMAT_hex, \ 63 | .value_mapping = _value_mapping, \ 64 | .size = sizeof(_section##_options._name), \ 65 | .description = _desc, \ 66 | } 67 | 68 | #define DEFINE_OPTION_PTR(_section, _name, _type, _desc) \ 69 | { \ 70 | .name = #_section "-" #_name, \ 71 | .value_##_type = _section##_options._name, \ 72 | .format = OPTION_FORMAT_##_type, \ 73 | .size = sizeof(_section##_options._name), \ 74 | .description = _desc, \ 75 | } 76 | 77 | const char *opt_value_to_string(const option_value_t *values, int value, const char *def); 78 | int opt_string_to_value(const option_value_t *values, const char *name, int def); 79 | 80 | int parse_opts(option_t *options, int argc, char *argv[]); 81 | --------------------------------------------------------------------------------