diff --git a/.github/scripts/Linux/arm/bootstrap.sh b/.github/scripts/Linux/arm/bootstrap.sh index 7518ea6690..1b6eed51c5 100755 --- a/.github/scripts/Linux/arm/bootstrap.sh +++ b/.github/scripts/Linux/arm/bootstrap.sh @@ -31,7 +31,8 @@ apt -y install libavcodec-dev libavformat-dev libswscale-dev libraspberrypi-dev apt -y install \ cmake \ libdrm-dev\ - libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev\ + libfluidsynth-dev\ + libsdl2-dev libsdl2-ttf-dev\ libva-dev\ libvulkan-dev\ diff --git a/.github/scripts/Linux/arm/build.sh b/.github/scripts/Linux/arm/build.sh index 6371095d27..45042f19f0 100755 --- a/.github/scripts/Linux/arm/build.sh +++ b/.github/scripts/Linux/arm/build.sh @@ -22,6 +22,7 @@ export CFLAGS CXXFLAGS # shellcheck disable=SC2086 # intentional set -- $FEATURES +set -- --enable-sdl=2 # use SDL2 (environment.sh sets sdl=3) set -- "$@" --enable-drm_disp ./autogen.sh "$@" diff --git a/.github/scripts/Linux/common.sh b/.github/scripts/Linux/common.sh new file mode 100644 index 0000000000..99ea2e6019 --- /dev/null +++ b/.github/scripts/Linux/common.sh @@ -0,0 +1,6 @@ +# $2 - pattern to exclude; separate packates with '\|' (BRE alternation) +get_build_deps_excl() { + apt-cache showsrc "$1" | sed -n "/^Build-Depends:/\ +{s/Build-Depends://;p;q}" | tr ',' '\n' | cut -f 2 -d\ | grep -v "$2" +} + diff --git a/.github/scripts/Linux/download_build_ffmpeg.sh b/.github/scripts/Linux/download_build_ffmpeg.sh deleted file mode 100755 index 40a31e9d53..0000000000 --- a/.github/scripts/Linux/download_build_ffmpeg.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash -eux - -install_aom() {( - git clone --depth 1 https://aomedia.googlesource.com/aom - mkdir -p aom/build - cd aom/build - cmake -DBUILD_SHARED_LIBS=1 .. - cmake --build . --parallel "$(nproc)" - sudo cmake --install . -)} - -install_libvpx() { - ( - git clone --depth 1 https://github.com/webmproject/libvpx.git - cd libvpx - ./configure --enable-pic --disable-examples --disable-install-bins --disable-install-srcs --enable-vp9-highbitdepth - make -j "$(nproc)" - sudo make install - ) -} - -FFMPEG_GIT_DEPTH=5000 # greater depth is useful for 3-way merges -install_svt() { - ( git clone --depth 1 https://github.com/OpenVisualCloud/SVT-HEVC && cd SVT-HEVC/Build/linux && ./build.sh release && cd Release && sudo cmake --install . || exit 1 ) - ( git clone --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git && cd SVT-AV1 && cd Build && cmake .. -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release && cmake --build . --parallel && sudo cmake --install . || exit 1 ) - ( git clone --depth 1 https://github.com/OpenVisualCloud/SVT-VP9.git && cd SVT-VP9/Build && cmake .. -DCMAKE_BUILD_TYPE=Release && cmake --build . --parallel && sudo cmake --install . || exit 1 ) - # libsvtav1 in FFmpeg upstream, for SVT-HEVC now our custom patch in ffmpeg-patches - # if patch apply fails, try increasing $FFMPEG_GIT_DEPTH - git am -3 SVT-VP9/ffmpeg_plugin/master-*.patch - # TOD TOREMOVE when not needed - sed 's/\* avctx->ticks_per_frame//' libavcodec/libsvt_vp9.c >fix - mv fix libavcodec/libsvt_vp9.c -} - -# The NV Video Codec SDK headers version 12.0 implies driver v520.56.06 in Linux -install_nv_codec_headers() { - git clone --depth 1 -b sdk/12.0 https://github.com/FFmpeg/nv-codec-headers - ( cd nv-codec-headers && make && sudo make install || exit 1 ) -} - -install_onevpl() {( - git clone --depth 1 https://github.com/oneapi-src/oneVPL - mkdir oneVPL/build - cd oneVPL/build - cmake -DBUILD_TOOLS=OFF .. - cmake --build . --config Release --parallel - sudo cmake --build . --config Release --target install -)} - -rm -rf /var/tmp/ffmpeg -git clone --depth $FFMPEG_GIT_DEPTH https://github.com/FFmpeg/FFmpeg.git \ - /var/tmp/ffmpeg -cd /var/tmp/ffmpeg -install_aom -install_libvpx -install_nv_codec_headers -install_onevpl -install_svt -# apply patches -find "$GITHUB_WORKSPACE/.github/scripts/Linux/ffmpeg-patches" -name '*.patch' -print0 | sort -z | xargs -0 -n 1 git am -3 -./configure --disable-static --enable-shared --enable-gpl --enable-libx264 --enable-libx265 --enable-libopus --enable-nonfree --enable-nvenc --enable-libaom --enable-libvpx --enable-libspeex --enable-libmp3lame \ - --enable-libdav1d \ - --enable-libde265 \ - --enable-libopenh264 \ - --enable-librav1e \ - --enable-libsvtav1 \ - --enable-libsvthevc \ - --enable-libsvtvp9 \ - --enable-libvpl \ - --disable-sdl2 \ - --enable-vulkan \ - -make -j "$(nproc)" -sudo make install -sudo ldconfig diff --git a/.github/scripts/Linux/ffmpeg-patches/0001-lavc-svt_hevc-add-libsvt-hevc-encoder-wrapper.patch b/.github/scripts/Linux/ffmpeg-patches/0001-lavc-svt_hevc-add-libsvt-hevc-encoder-wrapper.patch index 5ef50898f9..3357f93abb 100644 --- a/.github/scripts/Linux/ffmpeg-patches/0001-lavc-svt_hevc-add-libsvt-hevc-encoder-wrapper.patch +++ b/.github/scripts/Linux/ffmpeg-patches/0001-lavc-svt_hevc-add-libsvt-hevc-encoder-wrapper.patch @@ -1,4 +1,4 @@ -From 3d2c2bba486f11f7a55eaf8b51159dea19c03231 Mon Sep 17 00:00:00 2001 +From 19f4bd57e3d60dc6e40bc1ff96fbb91916dd7cc4 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Wed, 21 Nov 2018 11:33:04 +0800 Subject: [PATCH] lavc/svt_hevc: add libsvt hevc encoder wrapper @@ -21,18 +21,18 @@ UPDATED 2025-06-20 by Martin Pulec: rebased against 45a30e036 + fix compile create mode 100644 libavcodec/libsvt_hevc.c diff --git a/configure b/configure -index 077b87af..5dcc49ca 100755 +index e1809a3e58..936cea82ab 100755 --- a/configure +++ b/configure @@ -339,6 +339,7 @@ External library support: - --enable-vapoursynth enable VapourSynth demuxer [no] + --enable-whisper enable whisper filter [no] --disable-xlib disable xlib [autodetect] --disable-zlib disable zlib [autodetect] + --enable-libsvthevc enable HEVC encoding via svt [no] The following libraries provide various hardware acceleration features: --disable-amf disable AMF video encoding code [autodetect] -@@ -1981,6 +1982,7 @@ EXTERNAL_LIBRARY_LIST=" +@@ -1979,6 +1980,7 @@ EXTERNAL_LIBRARY_LIST=" libsrt libssh libsvtav1 @@ -40,7 +40,7 @@ index 077b87af..5dcc49ca 100755 libtensorflow libtesseract libtheora -@@ -3655,6 +3657,7 @@ vapoursynth_demuxer_deps="vapoursynth" +@@ -3678,6 +3680,7 @@ vapoursynth_demuxer_deps="vapoursynth" videotoolbox_suggest="coreservices" videotoolbox_deps="corefoundation coremedia corevideo VTDecompressionSessionDecodeFrame" videotoolbox_encoder_deps="videotoolbox VTCompressionSessionPrepareToEncodeFrames" @@ -48,19 +48,19 @@ index 077b87af..5dcc49ca 100755 # demuxers / muxers ac3_demuxer_select="ac3_parser" -@@ -7094,6 +7097,7 @@ enabled libssh && require_pkg_config libssh "libssh >= 0.6.0" libssh/ +@@ -7149,6 +7152,7 @@ enabled libssh && require_pkg_config libssh "libssh >= 0.6.0" libssh/ enabled libspeex && require_pkg_config libspeex speex speex/speex.h speex_decoder_init enabled libsrt && require_pkg_config libsrt "srt >= 1.3.0" srt/srt.h srt_socket enabled libsvtav1 && require_pkg_config libsvtav1 "SvtAv1Enc >= 0.9.0" EbSvtAv1Enc.h svt_av1_enc_init_handle +enabled libsvthevc && require_pkg_config libsvthevc SvtHevcEnc EbApi.h EbInitHandle - enabled libsvtvp9 && require_pkg_config libsvtvp9 SvtVp9Enc EbSvtVp9Enc.h eb_vp9_svt_init_handle enabled libtensorflow && require libtensorflow tensorflow/c/c_api.h TF_Version -ltensorflow enabled libtesseract && require_pkg_config libtesseract tesseract tesseract/capi.h TessBaseAPICreate + enabled libtheora && require libtheora theora/theoraenc.h th_info_init -ltheoraenc -ltheoradec -logg diff --git a/libavcodec/Makefile b/libavcodec/Makefile -index 805b0a07..49c644ca 100644 +index 35408949ac..12f534a1d7 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile -@@ -1188,6 +1188,7 @@ OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER) += libwebpenc_common.o libwebpenc_anim +@@ -1202,6 +1202,7 @@ OBJS-$(CONFIG_LIBWEBP_ANIM_ENCODER) += libwebpenc_common.o libwebpenc_anim OBJS-$(CONFIG_LIBX262_ENCODER) += libx264.o OBJS-$(CONFIG_LIBX264_ENCODER) += libx264.o OBJS-$(CONFIG_LIBX265_ENCODER) += libx265.o @@ -69,10 +69,10 @@ index 805b0a07..49c644ca 100644 OBJS-$(CONFIG_LIBXAVS2_ENCODER) += libxavs2.o OBJS-$(CONFIG_LIBXEVD_DECODER) += libxevd.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c -index fef337ca..8e67e0df 100644 +index f5ec2e01e8..7844040a00 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c -@@ -826,6 +826,7 @@ extern const FFCodec ff_libxavs_encoder; +@@ -827,6 +827,7 @@ extern const FFCodec ff_libxavs_encoder; extern const FFCodec ff_libxavs2_encoder; extern const FFCodec ff_libxvid_encoder; extern const FFCodec ff_libzvbi_teletext_decoder; @@ -82,7 +82,7 @@ index fef337ca..8e67e0df 100644 extern const FFCodec ff_bintext_decoder; diff --git a/libavcodec/libsvt_hevc.c b/libavcodec/libsvt_hevc.c new file mode 100644 -index 00000000..07f62fa7 +index 0000000000..07f62fa762 --- /dev/null +++ b/libavcodec/libsvt_hevc.c @@ -0,0 +1,584 @@ @@ -671,5 +671,5 @@ index 00000000..07f62fa7 + .p.wrapper_name = "libsvt_hevc", +}; -- -2.50.0 +2.50.1 diff --git a/.github/scripts/Linux/install_ffmpeg.sh b/.github/scripts/Linux/install_ffmpeg.sh index b14f135aaa..161ceb3e18 100755 --- a/.github/scripts/Linux/install_ffmpeg.sh +++ b/.github/scripts/Linux/install_ffmpeg.sh @@ -1,13 +1,132 @@ #!/bin/bash -eux -cd /var/tmp/ffmpeg -( cd libvpx && sudo make install ) -( cd nv-codec-headers && sudo make install ) -( cd aom/build && sudo cmake --install . ) -sudo cmake --install SVT-AV1/Build -sudo cmake --install SVT-HEVC/Build/linux/Release -sudo cmake --install SVT-VP9/Build -sudo cmake --build oneVPL/build --config Release --target install - -sudo make install -sudo ldconfig +dir=$(dirname "$0") +# shellcheck source=/dev/null +. "$dir/common.sh" # for get_build_deps_excl + +# build dir that will be restored from cache +cache_dir=/var/tmp/ffmpeg + +# install the deps - runs always (regardless the cache) +deps() { + ffmpeg_build_dep=$(get_build_deps_excl ffmpeg 'libsdl') + # shellcheck disable=SC2086 # intentional + sudo apt install $ffmpeg_build_dep libdav1d-dev libde265-dev \ + libopenh264-dev + sudo apt-get -y remove 'libavcodec*' 'libavutil*' 'libswscale*' \ + libvpx-dev nginx +} + +install_aom() {( + git clone --depth 1 https://aomedia.googlesource.com/aom + mkdir -p aom/build + cd aom/build + cmake -DBUILD_SHARED_LIBS=1 .. + cmake --build . --parallel "$(nproc)" + sudo cmake --install . +)} + +install_libvpx() {( + git clone --depth 1 https://github.com/webmproject/libvpx.git + cd libvpx + ./configure --enable-pic --disable-examples --disable-install-bins \ + --disable-install-srcs --enable-vp9-highbitdepth + make -j "$(nproc)" + sudo make install +)} + +install_svt() { + ( git clone --depth 1 https://github.com/OpenVisualCloud/SVT-HEVC && + cd SVT-HEVC/Build/linux && ./build.sh release && cd Release && + sudo cmake --install . || exit 1 ) + ( git clone --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git && + cd SVT-AV1 && cd Build && + cmake .. -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release && + cmake --build . --parallel && sudo cmake --install . || exit 1 ) + ( git clone --depth 1 https://github.com/OpenVisualCloud/SVT-VP9.git && + cd SVT-VP9/Build && cmake .. -DCMAKE_BUILD_TYPE=Release && + cmake --build . --parallel && sudo cmake --install . || exit 1 ) + # libsvtav1 in FFmpeg upstream, for SVT-HEVC now our custom patch in ffmpeg-patches + # if patch apply fails, try increasing $FFMPEG_GIT_DEPTH + git am -3 SVT-VP9/ffmpeg_plugin/master-*.patch + # TOD TOREMOVE when not needed + sed 's/\* avctx->ticks_per_frame//' libavcodec/libsvt_vp9.c >fix + mv fix libavcodec/libsvt_vp9.c +} + +# The NV Video Codec SDK headers version 12.0 implies driver v520.56.06 in Linux +install_nv_codec_headers() { + git clone --depth 1 -b sdk/12.0 https://github.com/FFmpeg/nv-codec-headers + ( cd nv-codec-headers && make && sudo make install || exit 1 ) +} + +install_onevpl() {( + git clone --depth 1 https://github.com/oneapi-src/oneVPL + mkdir oneVPL/build + cd oneVPL/build + cmake -DBUILD_TOOLS=OFF .. + cmake --build . --config Release --parallel + sudo cmake --build . --config Release --target install +)} + +# build FFmpeg deps + FFmpeg itself +build_install() { + rm -rf $cache_dir + FFMPEG_GIT_DEPTH=5000 # greater depth is useful for 3-way merges + git clone --depth $FFMPEG_GIT_DEPTH https://github.com/FFmpeg/FFmpeg.git \ + $cache_dir + cd $cache_dir + install_aom + install_libvpx + install_nv_codec_headers + install_onevpl + install_svt + # apply patches + find "$GITHUB_WORKSPACE/.github/scripts/Linux/ffmpeg-patches" \ + -name '*.patch' -print0 | sort -z | xargs -0 -n 1 git am -3 + ./configure --disable-static --enable-shared --enable-gpl --enable-nonfree \ + --disable-sdl2 \ + --enable-libaom \ + --enable-libdav1d \ + --enable-libde265 \ + --enable-libmp3lame \ + --enable-libopenh264 \ + --enable-libopus \ + --enable-librav1e \ + --enable-libspeex \ + --enable-libsvtav1 \ + --enable-libsvthevc \ + --enable-libsvtvp9 \ + --enable-libvpl \ + --enable-libvpx \ + --enable-libx264 \ + --enable-libx265 \ + --enable-nvenc \ + --enable-vulkan \ + + make -j "$(nproc)" + sudo make install + sudo ldconfig +} + +# if cache is successfully restored, just install the builds +install_cached() { + cd $cache_dir + ( cd libvpx && sudo make install ) + ( cd nv-codec-headers && sudo make install ) + ( cd aom/build && sudo cmake --install . ) + sudo cmake --install SVT-AV1/Build + sudo cmake --install SVT-HEVC/Build/linux/Release + sudo cmake --install SVT-VP9/Build + sudo cmake --build oneVPL/build --config Release --target install + + sudo make install + sudo ldconfig +} + +deps +if [ -d $cache_dir ]; then + install_cached +else + build_install +fi diff --git a/.github/scripts/Linux/install_glfw.sh b/.github/scripts/Linux/install_glfw.sh new file mode 100755 index 0000000000..04459d4a8a --- /dev/null +++ b/.github/scripts/Linux/install_glfw.sh @@ -0,0 +1,38 @@ +#!/bin/sh -eu + +dir=$(dirname "$0") +# shellcheck source=/dev/null +. "$dir/common.sh" # for get_build_deps_excl + +# build dir that will be restored from cache +cache_dir=/var/tmp/glfw + +# install the deps - runs always (regardless the cache) +deps() { + sudo apt build-dep libglfw3 +} + +# build SDL, SDL_ttf and fluidsynth and also install them +build_install() { + mkdir -p $cache_dir + cd $cache_dir + + git clone --depth 1 https://github.com/glfw/glfw.git + cmake -S glfw -B glfw/build \ + -DGLFW_BUILD_WAYLAND=ON -DGLFW_BUILD_X11=ON + cmake --build glfw/build -j "$(nproc)" + sudo cmake --install glfw/build +} + +# if cache is successfully restored, just install the builds +install_cached() { + cd $cache_dir + sudo cmake --install glfw/build +} + +deps +if [ -d $cache_dir ]; then + install_cached +else + build_install +fi diff --git a/.github/scripts/Linux/install_others.sh b/.github/scripts/Linux/install_others.sh index f74d17da8c..22a7027df2 100755 --- a/.github/scripts/Linux/install_others.sh +++ b/.github/scripts/Linux/install_others.sh @@ -66,6 +66,7 @@ install_rav1e() {( # FFmpeg master needs at least v1.3.277 as for 6th Mar '25 install_vulkan() {( + sudo apt build-dep libvulkan1 git clone --depth 1 https://github.com/KhronosGroup/Vulkan-Headers mkdir Vulkan-Headers/build cd Vulkan-Headers/build diff --git a/.github/scripts/Linux/install_sdl.sh b/.github/scripts/Linux/install_sdl.sh new file mode 100755 index 0000000000..40ef6b586e --- /dev/null +++ b/.github/scripts/Linux/install_sdl.sh @@ -0,0 +1,60 @@ +#!/bin/sh -eu + +dir=$(dirname "$0") +# shellcheck source=/dev/null +. "$dir/common.sh" # for get_build_deps_excl + +# build dir that will be restored from cache +cache_dir=/var/tmp/sdl +features="-DSDL_KMSDRM=ON\ + -DSDL_OPENGL=ON\ + -DSDL_VULKAN=ON\ + -DSDL_WAYLAND=ON\ + -DSDL_X11=ON" + +# install the deps - runs always (regardless the cache) +deps() { + sudo apt build-dep libsdl2 + fluidsynth_build_dep=$(get_build_deps_excl libfluidsynth3 libsdl2-dev) + sdl2_ttf_build_dep=$(get_build_deps_excl libsdl2-ttf libsdl2-dev) + # shellcheck disable=SC2086 # intentional + sudo apt install $fluidsynth_build_dep $sdl2_ttf_build_dep +} + +# build SDL, SDL_ttf and fluidsynth and also install them +build_install() { + mkdir -p $cache_dir + cd $cache_dir + + git clone --depth 1 https://github.com/libsdl-org/SDL + cmake -S SDL -B SDL/build + cmake --build SDL/build -j "$(nproc)" + sudo cmake --install SDL/build + + git clone --depth 1 https://github.com/libsdl-org/SDL_ttf + cmake -S SDL_ttf -B SDL_ttf/build + cmake --build SDL_ttf/build -j "$(nproc)" + sudo cmake --install SDL_ttf/build + + git clone --recurse-submodules --depth 1\ + https://github.com/Fluidsynth/fluidsynth + # shellcheck disable=SC2086 # intentional + cmake $features -S fluidsynth -B fluidsynth/build + cmake --build fluidsynth/build -j "$(nproc)" + sudo cmake --install fluidsynth/build +} + +# if cache is successfully restored, just install the builds +install_cached() { + cd $cache_dir + sudo cmake --install SDL/build + sudo cmake --install SDL_ttf/build + sudo cmake --install fluidsynth/build +} + +deps +if [ -d $cache_dir ]; then + install_cached +else + build_install +fi diff --git a/.github/scripts/Linux/prepare.sh b/.github/scripts/Linux/prepare.sh index 872278ada3..4b47edc5fa 100755 --- a/.github/scripts/Linux/prepare.sh +++ b/.github/scripts/Linux/prepare.sh @@ -1,9 +1,13 @@ #!/bin/bash -eux +dir=$(dirname "$0") + export PKG_CONFIG_PATH=/usr/local/qt/lib/pkgconfig:/usr/local/lib/pkgconfig +export LIBRARY_PATH=/usr/local/lib:/usr/local/qt/lib printf "%b" "\ CPATH=/usr/local/qt/include\n\ -LIBRARY_PATH=/usr/local/qt/lib\n\ +LIBRARY_PATH=$LIBRARY_PATH\n\ +LD_LIBRARY_PATH=$LIBRARY_PATH\n\ PKG_CONFIG_PATH=$PKG_CONFIG_PATH\n" >> "$GITHUB_ENV" printf "/usr/local/qt/bin\n" >> "$GITHUB_PATH" @@ -15,15 +19,15 @@ else # one-line-style (old) format sed -n '/^deb /s/^deb /deb-src /p' /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/sources.list fi +sudo apt-mark hold libsdl2-2.0-0 sudo apt update sudo apt install appstream `# appstreamcli for mkappimage AppStream validation` \ asciidoc sudo apt install fonts-dejavu-core sudo apt --no-install-recommends install nvidia-cuda-toolkit -sudo apt install libglew-dev libglfw3-dev +sudo apt install libglew-dev sudo apt install libglm-dev sudo apt install imagemagick libmagickwand-dev -sudo apt install libsdl2-dev libsdl2-mixer-dev libsdl2-ttf-dev sudo apt install libsoxr-dev libspeexdsp-dev sudo apt install libssl-dev sudo apt install libasound-dev libcaca-dev libjack-jackd2-dev libnatpmp-dev libv4l-dev portaudio19-dev @@ -31,16 +35,7 @@ sudo apt install libopencv-core-dev libopencv-imgproc-dev sudo apt install libcurl4-openssl-dev # for RTSP client (vidcap) sudo apt install i965-va-driver-shaders libva-dev # instead of i965-va-driver -get_build_deps_excl() { # $2 - pattern to exclude; separate packates with '\|' (BRE alternation) - apt-cache showsrc "$1" | sed -n '/^Build-Depends:/{s/Build-Depends://;p;q}' | tr ',' '\n' | cut -f 2 -d\ | grep -v "$2" -} - -ffmpeg_build_dep=$(get_build_deps_excl ffmpeg 'nonexistent-placeholder') -# shellcheck disable=SC2086 # intentional -sudo apt install $ffmpeg_build_dep libdav1d-dev libde265-dev libopenh264-dev -sudo apt-get -y remove 'libavcodec*' 'libavutil*' 'libswscale*' libvpx-dev nginx - -sudo apt install qt6-base-dev +sudo apt install qt6-base-dev qt6-wayland . /etc/os-release # source ID and VERSION_ID if [ "$ID" = ubuntu ] && [ "$VERSION_ID" = 22.04 ]; then # https://bugs.launchpad.net/ubuntu/+source/qtchooser/+bug/1964763 bug @@ -55,3 +50,7 @@ qt6.conf" "/usr/lib/$(uname -m)-linux-gnu/qt-default/qtchooser/default.conf" "$GITHUB_WORKSPACE/.github/scripts/Linux/install_others.sh" +"$dir"/install_sdl.sh +"$dir"/install_ffmpeg.sh +"$dir"/install_glfw.sh + diff --git a/.github/scripts/Windows/prepare_msys.sh b/.github/scripts/Windows/prepare_msys.sh index 0892b21726..0d66807d3f 100644 --- a/.github/scripts/Windows/prepare_msys.sh +++ b/.github/scripts/Windows/prepare_msys.sh @@ -46,16 +46,19 @@ PACMAN_INSTALL='pacman -Sy --needed --noconfirm --disable-download-timeout' MINGW_PACKAGE_PREFIX=mingw-w64-clang-x86_64 m=$MINGW_PACKAGE_PREFIX $PACMAN_INSTALL automake autoconf git make pkgconf \ - $m-clang $m-winpthreads-git \ + $m-clang $m-lld $m-winpthreads \ $m-gcc-compat \ unzip zip $PACMAN_INSTALL $m-asciidoc \ + $m-libcaca\ $m-ffmpeg \ + $m-fluidsynth\ + $m-glew $m-glfw\ $m-libnatpmp \ $m-vulkan-headers $m-vulkan-loader \ $PACMAN_INSTALL $m-libsoxr $m-speexdsp -$PACMAN_INSTALL $m-glew $m-libcaca $m-SDL2 $m-SDL2_mixer $m-SDL2_ttf $m-glfw +$PACMAN_INSTALL $m-sdl3 $m-sdl3-ttf $PACMAN_INSTALL $m-glm $PACMAN_INSTALL $m-portaudio # in case of problems build PA with --with-winapi=wmme,directx,wasapi $PACMAN_INSTALL $m-curl # RTSP capture @@ -96,17 +99,10 @@ https://github.com/CESNET/GPUJPEG/releases/download/continuous/"$fname" cp -r GPUJPEG/* /usr/local/ )} -install_soundfont() { - sf_dir="$GITHUB_WORKSPACE/data/Windows/share/soundfonts" - mkdir -p "$sf_dir" - cp "$GITHUB_WORKSPACE/data/default.sf3" "$sf_dir" -} - # Install cross-platform deps "$GITHUB_WORKSPACE/.github/scripts/install-common-deps.sh" build_aja_wrapper install_deltacast install_gpujpeg -install_soundfont diff --git a/.github/scripts/environment.sh b/.github/scripts/environment.sh index 1093379c19..59846c633d 100644 --- a/.github/scripts/environment.sh +++ b/.github/scripts/environment.sh @@ -9,6 +9,8 @@ ## - **apple_key_p12_b64** - [mac only] base64-encoded $KEY_FILE (using ## password $KEY_FILE_PASS) +set -eu + if expr "$GITHUB_REF" : 'refs/tags/' >/dev/null; then TAG=${GITHUB_REF#refs/tags/} VERSION=${TAG#v} @@ -45,6 +47,7 @@ export FEATURES="\ --enable-caca\ --enable-decklink\ --enable-file\ + --enable-fluidsynth\ --enable-gl\ --enable-gl-display\ --enable-holepunch\ @@ -63,8 +66,7 @@ export FEATURES="\ --enable-rtsp-server\ --enable-scale\ --enable-screen\ - --enable-sdl=2\ - --enable-sdl_mixer\ + --enable-sdl=3\ --enable-sdp-http\ --enable-soxr\ --enable-speexdsp\ @@ -81,8 +83,13 @@ export FEATURES="\ CUDA_FEATURES="--enable-cuda_dxt --enable-gpujpeg --enable-ldgm-gpu --enable-uyvy" case "$RUNNER_OS" in Linux) - FEATURES="$FEATURES --enable-plugins --enable-alsa \ ---enable-pipewire-audio --enable-v4l2 --enable-lavc-hw-accel-vaapi" + FEATURES="$FEATURES --enable-plugins \ +--enable-alsa \ +--enable-lavc-hw-accel-vaapi \ +--enable-libbacktrace \ +--enable-pipewire-audio \ +--enable-v4l2 \ +" if is_arm; then FEATURES="$FEATURES --disable-qt" else @@ -91,7 +98,12 @@ case "$RUNNER_OS" in fi ;; macOS) - FEATURES="$FEATURES --enable-avfoundation --enable-coreaudio --enable-syphon" + FEATURES="$FEATURES \ +--enable-avfoundation \ +--enable-coreaudio \ +--enable-libbacktrace \ +--enable-syphon \ +" ;; Windows) FEATURES="$FEATURES $CUDA_FEATURES --enable-dshow --enable-spout --enable-wasapi" diff --git a/.github/scripts/install-common-deps.sh b/.github/scripts/install-common-deps.sh index 8044c6a7a5..281d424823 100755 --- a/.github/scripts/install-common-deps.sh +++ b/.github/scripts/install-common-deps.sh @@ -49,6 +49,9 @@ download_build_aja() { } install_aja() {( + if [ "$(uname -s)" = Linux ]; then + sudo apt install libudev-dev + fi if [ ! -d libajantv2 ]; then download_build_aja fi @@ -79,6 +82,12 @@ install_juice() { ) } +# fixes broken live555 test +live555_rm_tests() { + sed -e '/TESTPROGS_DIR.*MAKE/d' Makefile > Makefile.fix + mv -f Makefile.fix Makefile +} + download_build_live555() {( git clone --depth 1 https://github.com/xanview/live555/ cd live555 @@ -95,6 +104,7 @@ download_build_live555() {( make -j "$(nproc)" CPLUSPLUS_COMPILER="c++ -DNO_STD_LIB" else ./genMakefiles macosx-no-openssl + live555_rm_tests make -j "$(nproc)" CPLUSPLUS_COMPILER="c++ -std=c++11" fi )} diff --git a/.github/scripts/macOS/install_others.sh b/.github/scripts/macOS/install_others.sh index f97501d832..6b7b07d685 100755 --- a/.github/scripts/macOS/install_others.sh +++ b/.github/scripts/macOS/install_others.sh @@ -52,6 +52,14 @@ install_glfw() {( sudo cmake --install . )} +install_libbacktrace() {( + git clone --depth 1 https://github.com/ianlancetaylor/libbacktrace + cd libbacktrace + ./configure + make -j "$(getconf NPROCESSORS_ONLN)" + sudo make install +)} + # Install NDI install_ndi() {( # installer downloaed by cache step @@ -63,12 +71,6 @@ install_ndi() {( printf '%b' "CPATH=$CPATH\n" >> "$GITHUB_ENV" } -install_soundfont() {( - sf_dir="$srcroot/data/template/macOS-bundle/Contents/share/soundfonts" - mkdir -p "$sf_dir" - cp "$GITHUB_WORKSPACE/data/default.sf3" "$sf_dir" -)} - install_syphon() { syphon_dst=/Library/Frameworks ( @@ -90,7 +92,7 @@ if [ $# -eq 1 ] && { [ "$1" = -h ] || [ "$1" = --help ] || [ "$1" = help ]; }; t fi if [ $# -eq 0 ] || [ $show_help ]; then - set -- deltacast glfw ndi soundfont syphon ximea + set -- deltacast glfw libbacktrace ndi syphon ximea fi if [ $show_help ]; then diff --git a/.github/scripts/macOS/prepare.sh b/.github/scripts/macOS/prepare.sh index efdb674a13..4f8ea32737 100755 --- a/.github/scripts/macOS/prepare.sh +++ b/.github/scripts/macOS/prepare.sh @@ -36,6 +36,7 @@ set -- \ autoconf \ automake \ ffmpeg \ + fluidsynth \ glm \ imagemagick \ jack \ @@ -49,9 +50,8 @@ set -- \ pkg-config \ portaudio \ qt \ - sdl2 \ - sdl2_mixer \ - sdl2_ttf \ + sdl3 \ + sdl3_ttf \ speexdsp \ vulkan-headers \ wolfssl \ diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 59a2d75a34..d8c5cd02c6 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -75,22 +75,25 @@ jobs: if: steps.cache-ndi.outputs.cache-hit != 'true' run: "curl -Lf https://downloads.ndi.tv/SDK/NDI_SDK_Linux/\ Install_NDI_SDK_v6_Linux.tar.gz -o /var/tmp/Install_NDI_SDK_Linux.tar.gz" + - name: Cache FFmpeg + uses: actions/cache@main + with: + path: '/var/tmp/ffmpeg' + key: cache-ffmpeg-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_ffmpeg.sh', '.github/scripts/Linux/install_other.sh', '.github/scripts/Linux/ffmpeg-patches/*') }} + - name: Cache SDL + uses: actions/cache@main + with: + path: '/var/tmp/sdl' + key: cache-sdl-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_sdl.sh' ) }} + - name: Cache GLFW + uses: actions/cache@main + with: + path: '/var/tmp/glfw' + key: cache-glfw-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_glfw.sh' ) }} - name: bootstrap run: | . .github/scripts/environment.sh .github/scripts/Linux/prepare.sh - - name: Run actions/cache for FFmpeg - id: cache-ffmpeg - uses: actions/cache@main - with: - path: '/var/tmp/ffmpeg' - key: cache-ffmpeg-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/prepare.sh', '.github/scripts/Linux/download_build_ffmpeg.sh', '.github/scripts/Linux/ffmpeg-patches/*') }} - - name: Build FFmpeg - if: steps.cache-ffmpeg.outputs.cache-hit != 'true' - run: .github/scripts/Linux/download_build_ffmpeg.sh - - name: Install Cached FFmpeg - if: steps.cache-ffmpeg.outputs.cache-hit == 'true' - run: .github/scripts/Linux/install_ffmpeg.sh - name: configure run: "./autogen.sh $FEATURES || { RC=$?; cat config.log; exit $RC; }" - name: make diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml index faff1aa757..4b94e3b0d9 100644 --- a/.github/workflows/coverity-scan.yml +++ b/.github/workflows/coverity-scan.yml @@ -16,8 +16,13 @@ jobs: coverity_token: ${{ secrets.coverity_token }} steps: + - name: Check Coverity token presence + if: env.coverity_token == '' + run: | + echo "secrects.coverity_token not present, skipping the analysis!" + exit 1 + - name: Get Coverity tool name # the file name contains version and is used as the cache key - if: ${{ env.coverity_token }} id: tool run: | FILENAME=$(curl -LIf "https://scan.coverity.com/download/linux64\ @@ -25,7 +30,6 @@ jobs: sed -n '/content-disposition/s/.*\"\(.*\)\"/\1/p') echo "filename=$FILENAME" >> $GITHUB_OUTPUT - name: Run actions/cache for Coverity build tool - if: ${{ env.coverity_token }} id: cache-coverity-tool uses: actions/cache@main with: @@ -36,48 +40,54 @@ jobs: run: | wget --no-verbose https://scan.coverity.com/download/linux64 --post-data "token=$coverity_token&project=UltraGrid" -O ~/coverity_tool.tgz - name: Extract Coverity build tool - if: ${{ env.coverity_token }} run: | tar xaf ~/coverity_tool.tgz mv cov-analysis* /tmp/cov-analysis - uses: actions/checkout@v4 - if: ${{ env.coverity_token }} - name: Fetch SDKs ETags - if: ${{ env.coverity_token }} id: etags run: | $GITHUB_WORKSPACE/.github/scripts/get-etag.sh ndi\ - https://downloads.ndi.tv/SDK/NDI_SDK_Linux/Install_NDI_SDK_v6_Linux.\ - tar.gz >> $GITHUB_OUTPUT + https://downloads.ndi.tv/SDK/NDI_SDK_Linux/\ + Install_NDI_SDK_v6_Linux.tar.gz >> $GITHUB_OUTPUT - name: Run actions/cache for NDI - if: ${{ env.coverity_token }} id: cache-ndi uses: actions/cache@main with: path: /var/tmp/Install_NDI_SDK_Linux.tar.gz key: cache-ndi-${{ runner.os }}-${{ steps.etags.outputs.ndi }} - name: Download NDI - if: ${{ env.coverity_token }} && steps.cache-ndi.outputs.cache-hit != 'true' + if: steps.cache-ndi.outputs.cache-hit != 'true' run: "curl -Lf https://downloads.ndi.tv/SDK/NDI_SDK_Linux/\ Install_NDI_SDK_v6_Linux.tar.gz -o /var/tmp/Install_NDI_SDK_Linux.tar.gz" - + - name: Cache FFmpeg + uses: actions/cache@main + with: + path: '/var/tmp/ffmpeg' + key: cache-${{ github.workflow }}-ffmpeg-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_ffmpeg.sh', '.github/scripts/Linux/install_other.sh', '.github/scripts/Linux/ffmpeg-patches/*') }} + - name: Cache SDL + uses: actions/cache@main + with: + path: '/var/tmp/sdl' + key: cache-${{ github.workflow }}-sdl-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_sdl.sh' ) }} + - name: Cache GLFW + uses: actions/cache@main + with: + path: '/var/tmp/glfw' + key: cache-${{ github.workflow }}-glfw-${{ runner.os }}-${{ hashFiles( '.github/scripts/Linux/install_glfw.sh' ) }} - name: bootstrap run: | . .github/scripts/environment.sh .github/scripts/Linux/prepare.sh - sudo apt install libavcodec-dev libavformat-dev libswscale-dev libsdl2-mixer-dev libsdl2-ttf-dev - name: configure - if: ${{ env.coverity_token }} run: ./autogen.sh $FEATURES - name: Build with cov-build - if: ${{ env.coverity_token }} run: | /tmp/cov-analysis/bin/cov-build --dir cov-int make -j2 - name: Submit the result to Coverity Scan - if: ${{ env.coverity_token }} run: | tar caf ultragrid.tar.xz cov-int result=$(curl -Sf --form token=$coverity_token \ diff --git a/Makefile.in b/Makefile.in index 85368cd8f4..d225a0341d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -241,16 +241,19 @@ src/dir-stamp: $(TARGET): src/dir-stamp $(ULTRAGRID_OBJS) $(GENERATED_HEADERS) $(BIN_DEPS) $(MKDIR_P) $$(dirname $@) - $(LINKER) $(LDFLAGS) $(ULTRAGRID_OBJS) $(LIBS) $(ULTRAGRID_LIBS) -o $(TARGET) + $(LINKER) $(LDFLAGS) @UV_LDFLAGS@ $(ULTRAGRID_OBJS) $(LIBS) $(ULTRAGRID_LIBS)\ + -o $(TARGET) @if [ -n '@DLL_LIBS@' ]; then $(INSTALL) -m 644 @DLL_LIBS@ bin; fi + @if [ "$$(uname -s)" = Darwin ]; then dsymutil $(TARGET); fi -$(REFLECTOR_TARGET): src/dir-stamp $(REFLECTOR_OBJS) $(GENERATED_HEADERS) bin/hd-rum-av +$(REFLECTOR_TARGET): src/dir-stamp $(REFLECTOR_OBJS) $(GENERATED_HEADERS) bin/hd-rum-av.sh $(MKDIR_P) $$(dirname $@) $(LINKER) $(LDFLAGS) $(REFLECTOR_OBJS) $(LIBS) -o $@ + @if [ "$$(uname -s)" = Darwin ]; then dsymutil $(REFLECTOR_TARGET); fi -bin/hd-rum-av: $(srcdir)/data/template/bin/hd-rum-av +bin/hd-rum-av.sh: $(srcdir)/data/template/bin/hd-rum-av.sh $(MKDIR_P) $$(dirname $@) - $(CP) $(srcdir)/data/template/bin/hd-rum-av $@ + $(CP) $(srcdir)/data/template/bin/hd-rum-av.sh $@ -include $(DEP_FILES) @@ -487,7 +490,7 @@ dxt_compress/dxt_glsl.h:dxt_compress/compress_vp.glsl \ gui/QT/Makefile: gui/QT/uv-qt.pro @if test -z "$(QMAKE)"; then echo "Reconfigure with '--enable-qt'"; exit 1; fi - CFLAGS="$(CFLAGS_ORIG)" CXXFLAGS="$(CXXFLAGS_ORIG)" $(QMAKE) -makefile gui/QT/uv-qt.pro "DESTDIR+=./" -o $@ $(QMAKE_FLAGS) + CFLAGS="$(CFLAGS_ORIG)" CXXFLAGS="$(CXXFLAGS_ORIG)" $(QMAKE) -makefile $(srcdir)/gui/QT/uv-qt.pro "DESTDIR+=./" -o $@ $(QMAKE_FLAGS) .PHONY: $(GUI_TARGET) @@ -550,10 +553,11 @@ clean: $(COND_SILENCE)-rm -f $(TEST_OBJS) bin/run_tests $(COND_SILENCE)-rm -f data/ag_plugin/uvReceiverService.zip data/ag_plugin/uvSenderService.zip $(COND_SILENCE)-rm -rf $(BUNDLE) $(GUI_BUNDLE) $(GUI_BUNDLE_DEP) - $(COND_SILENCE)-rm -rf $(REFLECTOR_TARGET) bin/hd-rum-av $(REFLECTOR_OBJS) + $(COND_SILENCE)-rm -rf $(REFLECTOR_TARGET) bin/hd-rum-av.sh $(REFLECTOR_OBJS) $(COND_SILENCE)-rm -rf @TOREMOVE@ @MODULES@ @LIB_GENERATED_HEADERS@ $(COND_SILENCE)-rm -rf $(DEP_FILES) $(COND_SILENCE)-rm -rf bin/shaders + $(COND_SILENCE)-rm -f bin/uv.pdb $(COND_SILENCE)if [ -f "gui/QT/Makefile" ]; then make -C gui/QT/ distclean; fi distclean: clean @@ -594,6 +598,7 @@ $(BUNDLE): $(TARGET) $(REFLECTOR_TARGET) @MANPAGES@ hd-rum-multi/hd-rum rm -rf $(BUNDLE) $(MKDIR_P) $(BUNDLE)/Contents/MacOS $(BUNDLE)/Contents/libs $(CP) $(REFLECTOR_TARGET) $(TARGET) $(BUNDLE)/Contents/MacOS/ + $(CP) -r bin/*.dSYM $(BUNDLE)/Contents/MacOS/ $(CP) -r data/template/macOS-bundle/* $(BUNDLE)/ $(CP) hd-rum-multi/hd-rum $(BUNDLE)/Contents/MacOS/ $(CP) $(srcdir)/data/template/bin/* $(BUNDLE)/Contents/MacOS @@ -615,11 +620,13 @@ $(GUI_BUNDLE): $(BUNDLE) $(GUI_BUNDLE_DEP) # add Qt frameworks command -v macdeployqt && macdeployqt $(GUI_BUNDLE) -verbose=2 $(CP) -nR $(BUNDLE)/* $(GUI_BUNDLE)/ || true + rm -rf $(GUI_BUNDLE)/Contents/MacOS/*.dSYM if [ $(MACOS_LEGACY) = no ]; then \ for n in $(GUI_BUNDLE)/Contents/MacOS/*; do \ - if expr $$n : '.*-real$$' >/dev/null || expr $$n : '.*\.sh$$' >/dev/null; \ - then continue; fi; \ - mv -f $$n $$n-real; $(CP) -f $(srcdir)/data/scripts/macos-wrapper $$n; \ + if expr $$n : '.*-real$$' >/dev/null || expr $$n : '.*\.sh$$' >/dev/null || \ + [ !-f $$n ]; then continue; fi; \ + mv -f $$n $$n-real; dsymutil $$n-real; \ + $(CP) -f $(srcdir)/data/scripts/macos-wrapper $$n; \ done; \ fi @@ -687,9 +694,14 @@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) if [ -n '@MANPAGES@' ]; then $(INSTALL) -m 644 @MANPAGES@ $(DESTDIR)$(man1dir); fi if [ -n '@DLL_LIBS@' ]; then $(INSTALL) -m 644 @DLL_LIBS@ $(DESTDIR)$(bindir); fi + $(INSTALL) -D -m 644 "$(srcdir)"/share/ultragrid/TimGM6mb_but_fixed__piano_.sf3 -t "$(DESTDIR)$(datarootdir)/ultragrid" if [ -n "@VULKAN@" ]; then\ $(INSTALL) -D -m 644 "$(srcdir)/share/ultragrid/vulkan_shaders/"* -t "$(DESTDIR)$(datadir)/ultragrid/vulkan_shaders"; \ fi + if [ -f bin/uv.pdb ]; then\ + $(INSTALL) -m 644 bin/uv.pdb $(DESTDIR)$(bindir); \ + fi + uninstall: $(RM) $(DESTDIR)$(bindir)/uv $(RM) $(DESTDIR)$(bindir)/hd-rum-transcode @@ -705,10 +717,12 @@ uninstall: $(RM) $(DESTDIR)$(datadir)/metainfo/cz.cesnet.ultragrid.appdata.xml;\ $(RM) $(DESTDIR)$(datadir)/pixmaps/ultragrid.png;\ fi + $(RM) $(DESTDIR)$(datarootdir)/ultragrid/TimGM6mb_but_fixed__piano_.sf3 if [ -n "@VULKAN@" ]; then\ $(RM) "$(DESTDIR)$(datadir)/ultragrid/vulkan_shaders/"*;\ rmdir $(DESTDIR)$(datadir)/ultragrid/vulkan_shaders;\ rmdir $(DESTDIR)$(datadir)/ultragrid;\ fi + $(RM) $(DESTDIR)$(bindir)/uv.pdb # vim: set noexpandtab diff --git a/configure.ac b/configure.ac index 420d9bc30f..fdfe91b366 100644 --- a/configure.ac +++ b/configure.ac @@ -32,7 +32,7 @@ fi # ------------------------------------------------------------------------------------------------- POST_COMPILE_MSG="" -CXXFLAGS="${CXXFLAGS+$CXXFLAGS }-std=gnu++17" +CXXFLAGS="${CXXFLAGS:+$CXXFLAGS }-std=gnu++17" CUDA_FLAGS="$CUDA_FLAGS${CUDA_FLAGS:+${CUDAFLAGS:+ }}$CUDAFLAGS" INC="${INC-}" LDFLAGS="${LDFLAGS-}" @@ -126,7 +126,7 @@ if test "$host_vendor" = "apple"; then AC_DEFINE([HAVE_MACOSX], [1], [This is Mac X OS]) APPEXT=.app elif expr "x$host_os" : "x.*mingw32.*" > /dev/null || -expr "x$host_os" : "x.*msys.*" > /dev/null; then + expr "x$host_os" : "x.*msys.*" > /dev/null || test "$host_os" = cygwin; then system=Windows APPEXT=.exe AC_DEFINE([WIN32], [1], [This is an Windows OS]) @@ -150,7 +150,7 @@ fi if test $system = Linux; then AC_CHECK_HEADERS([linux/version.h]) LDFLAGS="$LDFLAGS -Wl,--dynamic-list-data" - COMMON_FLAGS="${COMMON_FLAGS+$COMMON_FLAGS }-D_GNU_SOURCE" + COMMON_FLAGS="${COMMON_FLAGS:+$COMMON_FLAGS }-D_GNU_SOURCE" fi if test $system = MacOSX; then @@ -174,12 +174,23 @@ if test $system = Windows; then LIBS="$LIBS -lsetupapi -lws2_32 -liphlpapi -lole32 -loleaut32" LIBS="$LIBS -ldbghelp" AC_CHECK_FUNCS(SetThreadDescription) + if $CXX -dM -E - /dev/null; then + AC_CHECK_PROG([LLD], [lld], [yes]) + if test "$LLD" = yes; then + COMMON_FLAGS="${COMMON_FLAGS:+$COMMON_FLAGS }-gcodeview" + UV_LDFLAGS="-fuse-ld=lld -g -Wl,--pdb=bin/uv.pdb" + fi + fi + if ! expr "x$COMMON_FLAGS" : '.*gcodeview' >/dev/null; then + UG_MSG_WARN([Windows stacktrace will not be available.]) + fi fi LINKER=$CXX AC_SUBST(LINKER) AC_SUBST(LDFLAGS) +AC_SUBST(UV_LDFLAGS) AC_SUBST(CXXFLAGS) AC_ARG_ENABLE(depends-version-check, @@ -214,9 +225,9 @@ test $ac_cv_sizeof_int_p -eq 4; } then LIBS="${LIBS:+$LIBS }-latomic" # needed for atomic_uint64_t fi -CFLAGS="$CFLAGS${ARCH+ $ARCH}" -CXXFLAGS="$CXXFLAGS${ARCH+ $ARCH}" -LDFLAGS="$LDFLAGS${ARCH+ $ARCH}" +CFLAGS="$CFLAGS${ARCH:+ $ARCH}" +CXXFLAGS="$CXXFLAGS${ARCH:+ $ARCH}" +LDFLAGS="$LDFLAGS${ARCH:+ $ARCH}" if expr "x$CFLAGS" : 'x.*-O' >/dev/null; then OFAST="" @@ -443,6 +454,30 @@ AC_ARG_VAR([GENICAM_GENTL64_PATH], [XIMEA SDK library path]) AC_ARG_VAR([NTV2_ROOT], [Directory to AJA NTV2 SDK (ends with ntv2projects).]) AC_ARG_VAR([SAGE_DIRECTORY], [Directory of your SAGE installation.]) +# ------------------------------------------------ +# libbacktrace +# ----------------------------------------------- +libbacktrace=no +AC_ARG_ENABLE(libbacktrace, + AS_HELP_STRING([--disable-libbacktrace], [disable libbacktrace]), + [libbacktrace_req=$enableval], + [libbacktrace_req=$build_default]) + +if test "${libbacktrace_req?}" != no; then + AC_CHECK_HEADER(backtrace.h) + AC_CHECK_LIB(backtrace, backtrace_pcinfo) + if test "${ac_cv_header_backtrace_h?}" = yes && \ + test "${ac_cv_lib_backtrace_backtrace_pcinfo?}" = yes; + then + LIBS="$LIBS -lbacktrace" + AC_DEFINE([HAVE_LIBBACKTRACE], [1], [we have libbacktrace]) + libbacktrace=yes + fi +fi + +ENSURE_FEATURE_PRESENT([$libbacktrace_req], [$libbacktrace], +[libbacktrace not found]) + # ------------------------------------------------------------------------------------------------- # CUDA stuff # @@ -503,7 +538,7 @@ then fi DEFAULT_LIB_P=$($LINKER --verbose 2>&1 | awk \ 'BEGIN { ORS="" } /InstalledDir: / { print $2 }')/../lib - CUDA_LIB="${CUDA_LIB+$CUDA_LIB }-L$DEFAULT_LIB_P" + CUDA_LIB="${CUDA_LIB:+$CUDA_LIB }-L$DEFAULT_LIB_P" CUDA_LIB="$CUDA_LIB -L\"$cl_lib_path\"" FOUND_CUDA=yes fi @@ -534,7 +569,7 @@ then fi fi - CUDA_LIB="${CUDA_LIB+$CUDA_LIB }-L$CUDA_LIB_PATH -lcudart" + CUDA_LIB="${CUDA_LIB:+$CUDA_LIB }-L$CUDA_LIB_PATH -lcudart" CUDA_INC="-I$CUDA_PATH/include" CUDA_COMPILER="$NVCC" INC="$INC $CUDA_INC" @@ -645,6 +680,8 @@ AC_CHECK_LIB(glew32, glewInit) AC_CHECK_LIB(X11, XCreateWindow) LIBS=$SAVED_LIBS CFLAGS=$SAVED_CFLAGS +HAVE_GL=no +HAVE_GLEW=no GL_COMMON_OBJ="src/gl_context.o" @@ -1284,12 +1321,7 @@ if test "$sdl_req" != no; then sdl2_is_compat=yes fi if test "$sdl_req" = yes || test "$sdl_req" = auto; then - # temporarily prefer 2 over 3 - sdl_req_ver=231 - # but only if sdl2 is not sdl2_compat - if test "$sdl2_is_compat"; then - sdl_req_ver=321 - fi + sdl_req_ver=321 fi while test -n "$sdl_req_ver"; do @@ -1305,9 +1337,6 @@ if test "$sdl_req" != no; then sdl_req_ver=$(printf "$sdl_req_ver" | cut -c2-) done - if test "$sdl_version" = 3 && test ! "$sdl2_is_compat"; then - UG_MSG_WARN([Using SDL 3, which is experimental. SDL 2 can be enforced with --enable-sdl=2]) - fi if test "$sdl_version" = 2 && test "$sdl2_is_compat" ; then UG_MSG_WARN([Using SDL3 sdl2_compat, which is not recommended (use 3 directly)]) fi @@ -1331,7 +1360,7 @@ fi sdl_version=${sdl_version:-0} -ENSURE_FEATURE_PRESENT([$sdl_req], [$sdl], [SDL requested but found any version]) +ENSURE_FEATURE_PRESENT([$sdl_req], [$sdl], [SDL requested but not found]) # ------------------------------------------------------------------------------ # Vulkan @@ -2431,7 +2460,7 @@ ENSURE_FEATURE_PRESENT([$cuda_dxt_req], [$cuda_dxt], [CUDA DXT not found]) # ------------------------------------------------------------------------------------------------- gpujpeg_to_dxt=no AC_ARG_ENABLE(gpujpeg_to_dxt, -[ --disable-jpeg-to-dxt disable GPUJPEG DXT transcoder (default is disable)] +[ --disable-gpujpeg_to_dxt disable GPUJPEG DXT transcoder (default is disable)] [ Requires: CUDA libgpujpeg], [gpujpeg_to_dxt_req=$enableval], [gpujpeg_to_dxt_req=no]) @@ -3192,8 +3221,8 @@ then if test "${found_cl?}" = yes && test "${found_cl_h?}" = yes; then AC_DEFINE([HAVE_OPENCL], [1], [OpenCL is supported]) cmpto_libs="$cmpto_libs $OpenCL_LIBS" - CFLAGS="$CFLAGS${OpenCL_CFLAGS+ $OPENCL_CFLAGS}" - CFLAGS="$CFLAGS${OpenCL_Headers_CFLAGS+ $OPENCL_Headers_CFLAGS}" + CFLAGS="$CFLAGS${OpenCL_CFLAGS:+ $OPENCL_CFLAGS}" + CFLAGS="$CFLAGS${OpenCL_Headers_CFLAGS:+ $OPENCL_Headers_CFLAGS}" fi add_module vcompress_cmpto_j2k "src/video_compress/cmpto_j2k.o \ @@ -3378,35 +3407,26 @@ fi ENSURE_FEATURE_PRESENT([$pcp_req], [$pcp], [PCP not found]) # --------------------------------------------------------------------- -# SDL_mixer audio capture +# fluidsynth audio capture synthesizer # --------------------------------------------------------------------- -AC_ARG_ENABLE(sdl_mixer, - AS_HELP_STRING([--disable-sdl_mixer], [disable SDL_mixer audio capture (default is auto)] - [Requires: SDL[2]_mixer]), - [sdl_mixer_req=$enableval], - [sdl_mixer_req=$build_default]) -found_sdl_mixer=no -sdl_mixer=no - -if test $sdl = yes && test $sdl_mixer_req != no -then - if test "$sdl_version" -lt 3; then - PKG_CHECK_MODULES([SDL_MIXER], [SDL${sdl_ver_suffix}_mixer], - [found_sdl_mixer=yes], [found_sdl_mixer=no]) - else - PKG_CHECK_MODULES([SDL_MIXER], [sdl${sdl_ver_suffix}-mixer], - [found_sdl_mixer=yes], [found_sdl_mixer=no]) - fi - if test "$found_sdl_mixer" = yes; then - SDL_MIXER_LIBS=$(remove_mwindows "$SDL_MIXER_LIBS") - SDL_MIXER_CFLAGS=$($PKG_CONFIG --cflags-only-I SDL${sdl_ver_suffix}_mixer) - add_module acap_sdl_mixer src/audio/capture/sdl_mixer.o "$SDL_MIXER_LIBS" - INC="$INC${SDL_MIXER_CFLAGS+ $SDL_MIXER_CFLAGS}" - sdl_mixer=yes +AC_ARG_ENABLE(fluidsynth, + AS_HELP_STRING([--disable-fluidsynth], +[disable fluidtynth audio capture (default is auto)]), + [fluidsynth_req=$enableval], + [fluidsynth_req=$build_default]) +fluidsynth=no + +if test "${fluidsynth_req?}" != no; then + PKG_CHECK_MODULES([FLUIDSYNTH], [fluidsynth], [found_fluidsynth=yes], + [found_fluidsynth=no]) + if test "${found_fluidsynth?}" = yes; then + add_module acap_fluidsynth src/audio/capture/fluidsynth.o "$FLUIDSYNTH_LIBS" + INC="$INC${FLUIDSYNTH_CFLAGS:+ $FLUIDSYNTH_CFLAGS}" + fluidsynth=yes fi fi -ENSURE_FEATURE_PRESENT([$sdl_mixer_req], [$sdl_mixer], [SDL_mixer deps not found!]) +ENSURE_FEATURE_PRESENT([$fluidsynth_req], [$fluidsynth], [fluidsynth not found!]) # ----------------------------------------------------------------------------- # Reflector @@ -3464,6 +3484,7 @@ if test "$build_default" != no || test "$req_files" = all; then src/capture_filter/preview.o src/capture_filter/ratelimit.o src/capture_filter/split.o + src/capture_filter/temporal_3d.o src/video_capture/aggregate.o src/video_capture/import.o src/video_capture/switcher.o @@ -3486,6 +3507,7 @@ if test "$build_default" != no || test "$req_files" = all; then src/vo_postprocess/interlace.o src/vo_postprocess/split.o src/vo_postprocess/temporal-deint.o + src/vo_postprocess/temporal_3d.o " fi @@ -3566,6 +3588,7 @@ RESULT=`add_column "$RESULT" "Debug output" $debug_output $?` RESULT=`add_column "$RESULT" "iHDTV support" $ihdtv $?` RESULT=`add_column "$RESULT" "IPv6 support" $ipv6 $?` RESULT=`add_column "$RESULT" "Library live555" $livemedia $?` +RESULT=`add_column "$RESULT" "Libbacktrace" $libbacktrace $?` RESULT=`add_column "$RESULT" "Manual pages" $man $?` RESULT=`add_column "$RESULT" "OpenCV" $opencv $?` RESULT=`add_column "$RESULT" "Profiling support" $profile $?` @@ -3581,9 +3604,9 @@ RESULT=`end_section "$RESULT"` RESULT=`start_section "$RESULT" "Audio"` RESULT=`add_column "$RESULT" "ALSA" $alsa $?` RESULT=`add_column "$RESULT" "CoreAudio" $coreaudio $?` +RESULT=`add_column "$RESULT" "FluidSynth" $fluidsynth $?` RESULT=`add_column "$RESULT" "JACK" $jack $?` RESULT=`add_column "$RESULT" "JACK transport" $jack_trans $?` -RESULT=`add_column "$RESULT" "SDL_mixer" $sdl_mixer $?` RESULT=`add_column "$RESULT" "Pipewire" $pipewire_audio $?` RESULT=`add_column "$RESULT" "Portaudio" $portaudio $?` RESULT=`add_column "$RESULT" "WASAPI" $wasapi $?` diff --git a/data/scripts/Linux-AppImage/AppRun b/data/scripts/Linux-AppImage/AppRun index 428f8f385a..439a717c95 100755 --- a/data/scripts/Linux-AppImage/AppRun +++ b/data/scripts/Linux-AppImage/AppRun @@ -337,7 +337,7 @@ setup_firejail() { if firejail --help | grep -q -- --keep-var-tmp; then FIREJAIL_OPTS="$FIREJAIL_OPTS --keep-var-tmp" fi - if [ "$tool" = hd-rum-av ]; then + if [ "$tool" = hd-rum-av.sh ]; then FIREJAIL_OPTS="$FIREJAIL_OPTS --private-bin=basename,dirname,\ expr,kill,ps,sed,seq,sh,tput,tr,tty,uname" fi diff --git a/data/scripts/Linux-AppImage/create-appimage.sh b/data/scripts/Linux-AppImage/create-appimage.sh index 0343bd9f9e..18fdcf590f 100755 --- a/data/scripts/Linux-AppImage/create-appimage.sh +++ b/data/scripts/Linux-AppImage/create-appimage.sh @@ -92,10 +92,6 @@ add_fonts() { # for GUI+testcard2 cp "$FONT_PATH" $APPPREFIX/share/fonts done done - if ls $APPPREFIX/lib/*mixer* >/dev/null 2>&1; then - mkdir -p $APPPREFIX/share/soundfonts - cp "$srcdir/data/default.sf3" $APPPREFIX/share/soundfonts/ - fi } # copy dependencies @@ -117,13 +113,13 @@ fi add_fonts -if command -v wget >/dev/null && wget -V | grep -q https; then +if command -v curl >/dev/null; then dl() { - wget -O - ${GITHUB_TOKEN+--header "Authorization: token $GITHUB_TOKEN"} "$1" + curl --fail -sSL ${GITHUB_TOKEN+-H "Authorization: token $GITHUB_TOKEN"} "$1" } -elif command -v curl >/dev/null; then +elif command -v wget >/dev/null && wget -V | grep -q https; then dl() { - curl --fail -sSL ${GITHUB_TOKEN+-H "Authorization: token $GITHUB_TOKEN"} "$1" + wget -O - ${GITHUB_TOKEN+--header "Authorization: token $GITHUB_TOKEN"} "$1" } else echo "Neither wget nor curl was found - if one needed later, it will " \ @@ -135,7 +131,7 @@ fi DIRNAME=$(dirname "$0") uname_m=$(uname -m) excl_list_arch=x86 -if expr "$uname_m" : arm >/dev/null || expr "uname_m" : aarch64 > /dev/null; then +if expr "$uname_m" : arm >/dev/null || expr "$uname_m" : aarch64 > /dev/null; then excl_list_arch=arm fi cat "$DIRNAME/excludelist.local.$excl_list_arch" >> excludelist diff --git a/data/scripts/Linux-AppImage/excludelist.local.x86 b/data/scripts/Linux-AppImage/excludelist.local.x86 index 851a1f9e33..783418a500 100644 --- a/data/scripts/Linux-AppImage/excludelist.local.x86 +++ b/data/scripts/Linux-AppImage/excludelist.local.x86 @@ -1,4 +1,4 @@ # Causes crash on U22.04 (see https://github.com/CESNET/UltraGrid/releases/download/v1.7/UltraGrid-1.7-x86_64.AppImage) -libgdk-3.so.0 +# libgdk-3.so.0 # Rocky Linux 9 -libgdk_pixbuf-2.0.so.0 +# libgdk_pixbuf-2.0.so.0 diff --git a/data/scripts/macos_bundle_libs.sh b/data/scripts/macos_bundle_libs.sh index dd634382a1..e54e97489f 100755 --- a/data/scripts/macos_bundle_libs.sh +++ b/data/scripts/macos_bundle_libs.sh @@ -16,7 +16,7 @@ starts_with_shebang() { } for n in "$bundle"/Contents/MacOS/*; do - if starts_with_shebang "$n"; then + if [ ! -f "$n" ] || starts_with_shebang "$n"; then continue fi # shellcheck disable=SC2086 # intentional, even $dylibbundler diff --git a/data/template/bin/hd-rum-av b/data/template/bin/hd-rum-av.sh similarity index 100% rename from data/template/bin/hd-rum-av rename to data/template/bin/hd-rum-av.sh diff --git a/data/default.sf3 b/share/ultragrid/TimGM6mb_but_fixed__piano_.sf3 similarity index 100% rename from data/default.sf3 rename to share/ultragrid/TimGM6mb_but_fixed__piano_.sf3 diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 1dc922b07e..1bbb922171 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -50,6 +50,7 @@ #include #include #include +#include // for log10 #include #include #include diff --git a/src/audio/audio_playback.c b/src/audio/audio_playback.c index 5071627205..a598dbfca2 100644 --- a/src/audio/audio_playback.c +++ b/src/audio/audio_playback.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2015-2021 CESNET, z. s. p. o. + * Copyright (c) 2015-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,25 +35,20 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif - -#include -#include -#include - #include "audio/audio_playback.h" -#include "audio/playback/sdi.h" -#include "audio/types.h" -#include "debug.h" -#include "host.h" -#include "lib_common.h" -#include "tv.h" -#include "utils/misc.h" // for fmt_number_with_delim -#include "video_display.h" /* flags */ + +#include // for printf +#include // for free, calloc +#include // for strncpy +#include // for strcasecmp + +#include "audio/types.h" // for audio_frame, AC_PCM, audio_desc +#include "debug.h" // for log_msg, LOG_LEVEL_ERROR, LOG_LEVEL_INFO +#include "host.h" // for INIT_NOERR +#include "lib_common.h" // for library_class, list_modules, load_library +#include "tv.h" // for tv_diff +#include "utils/misc.h" // for fmt_number_with_delim +#include "video_display.h" // for DISPLAY_FLAG_AUDIO_AESEBU, DISPLAY_FLAG_A... struct state_audio_playback { char name[128]; diff --git a/src/audio/audio_playback.h b/src/audio/audio_playback.h index ee5ba05bad..06048d62f4 100644 --- a/src/audio/audio_playback.h +++ b/src/audio/audio_playback.h @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2012-2023 CESNET, z. s. p. o. + * Copyright (c) 2012-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,8 +38,11 @@ #ifndef AUDIO_AUDIO_PLAYBACK_H_316AA23B_3EFF_4150_83D2_24A2295CB74A #define AUDIO_AUDIO_PLAYBACK_H_316AA23B_3EFF_4150_83D2_24A2295CB74A -#ifndef __cplusplus +#ifdef __cplusplus +#include // for size_t +#else #include +#include // for size_t #endif // ! defined __cplusplus #include "../types.h" diff --git a/src/audio/capture/fluidsynth.c b/src/audio/capture/fluidsynth.c new file mode 100644 index 0000000000..52c1e763e0 --- /dev/null +++ b/src/audio/capture/fluidsynth.c @@ -0,0 +1,357 @@ +/** + * @file audio/capture/fluidsynth.c + * @author Martin Pulec + */ +/* + * Copyright (c) 2025 CESNET + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include // for assert +#include // for fluid_player_play, fluid_synth_writ... +#include // for fluid_player_t, fluid_settings_t +#include // for bool +#include // for NULL, fclose, size_t, FILE, fopen +#include // for free, getenv, malloc, calloc +#include // for strdup, strlen, strncat, strcmp +#include // for unlink + +#include "audio/audio_capture.h" // for AUDIO_CAPTURE_ABI_VERSION, audio_ca... +#include "audio/types.h" // for audio_frame +#include "audio/utils.h" // for mux_channel +#include "compat/usleep.h" // for usleep +#include "debug.h" // for LOG_LEVEL_ERROR, MSG, log_msg, LOG_... +#include "host.h" // for audio_capture_sample_rate, INIT_NOERR +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "song1.h" // for song1 +#include "tv.h" // for get_time_in_ns, time_ns_t, NS_IN_SE... +#include "types.h" // for device_info +#include "utils/color_out.h" // for color_printf, TBOLD, TRED +#include "utils/fs.h" // for get_install_root, get_temp_file +#include "utils/macros.h" // for ARR_COUNT, IS_KEY_PREFIX + +struct module; + +enum { + FLUIDSYNTH_BPS = 2, + DEFAULT_FLUIDSYNTH_SAMPLE_RATE = 48000, + CHUNK_SIZE = 480, +}; + +#define MOD_NAME "[fluidsynth] " + +struct state_fluidsynth_capture { + struct audio_frame audio; + unsigned char *left; + unsigned char *right; + + char *req_filename; + const char *tmp_filename; + + time_ns_t next_frame_time; + time_ns_t frame_interval; + ; + + fluid_settings_t *settings; + fluid_synth_t *synth; + fluid_player_t *player; +}; + +static void audio_cap_fluidsynth_done(void *state); + +static void +audio_cap_fluidsynth_probe(struct device_info **available_devices, int *count, + void (**deleter)(void *)) +{ + *deleter = free; + *count = 1; + *available_devices = calloc(1, sizeof **available_devices); + strncat((*available_devices)[0].dev, "fluidsynth", + sizeof(*available_devices)[0].dev - 1); + strncat((*available_devices)[0].name, "Sample midi song", + sizeof(*available_devices)[0].name - 1); +} + +static void +usage() +{ + color_printf( + TBOLD("fluidsynth") " is a capture device capable playing MIDI.\n\n" + "The main functional difference to " TBOLD( + "file") " video capture (that is able to " + "play audio\n" + "files as well) is the support " + "for " TBOLD( + "MIDI") " (and also having one " + "song bundled).\n\n"); + color_printf("Usage:\n"); + color_printf(TBOLD(TRED("\t-s fluidsynth") "[:file=]") "\n"); + color_printf("where\n"); + color_printf(TBOLD("\t") " - name of file to be used\n"); + color_printf("\n"); + color_printf(TBOLD( + "FLUIDSYNTH_SF") " - environment variable with path to " + "sound fonts for MIDI playback (eg. freepats)\n"); + color_printf( + TBOLD("ULTRAGRID_BUNDLED_SF") " - set this environment variable to " + "1 to skip loading system default " + "sound font\n\n"); +} + +static int +parse_opts(struct state_fluidsynth_capture *s, char *cfg) +{ + char *save_ptr = NULL; + char *item = NULL; + while ((item = strtok_r(cfg, ":", &save_ptr)) != NULL) { + cfg = NULL; + if (strcmp(item, "help") == 0) { + usage(); + return 1; + } + if (IS_KEY_PREFIX(item, "file")) { + s->req_filename = strdup(strchr(item, '=') + 1); + } else { + log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong option: %s!\n", + item); + color_printf("Use " TBOLD( + "-s fluidsynth:help") " to see available " + "options.\n"); + return -1; + } + } + return 0; +} + +static const char * +load_song1() +{ + const char *filename = NULL; + FILE *f = get_temp_file(&filename); + if (f == NULL) { + perror("fopen audio"); + return NULL; + } + size_t nwritten = fwrite(song1, sizeof song1, 1, f); + fclose(f); + if (nwritten != 1) { + unlink(filename); + return NULL; + } + return filename; +} + +/** + * Try to preload a sound font. + * + * This is mainly intended to allow loading sound fonts from application bundle + * on various platforms (get_install_root is relative to executable). But use + * to system default font, if available. + */ +static char * +get_soundfont() +{ + const char *env_fs = getenv("FLUIDSYNTH_SF"); + if (env_fs != NULL) { + return strdup(env_fs); + } + char bundled[MAX_PATH_SIZE]; + snprintf_ch(bundled, "%s/%s", get_data_path(), "TimGM6mb_but_fixed__piano_.sf3"); + const char *sf_candidates[] = { + "/usr/share/soundfonts/default.sf2", + "/usr/share/soundfonts/default.sf3", + "/usr/share/sounds/sf2/default-GM.sf2", + "/usr/share/sounds/sf3/default-GM.sf3", // Ubuntu + bundled, + }; + + const char *force_bundled_sf = getenv("ULTRAGRID_BUNDLED_SF"); + if (force_bundled_sf != NULL && strcmp(force_bundled_sf, "1") == 0) { + for (size_t i = ARR_COUNT(sf_candidates) - 1; i > 0; --i) { + sf_candidates[i] = sf_candidates[i - 1]; + } + sf_candidates[0] = bundled; + } + + for (size_t i = 0; i < ARR_COUNT(sf_candidates); ++i) { + const char *path = sf_candidates[i]; + FILE *f = fopen(path, "rb"); + debug_msg(MOD_NAME + "Trying to open sound font '%s': %s\n", + path, f ? "success, setting" : "failed"); + if (!f) { + continue; + } + fclose(f); + return strdup(path); + } + MSG(ERROR, "Cannot find any suitable sound font!\n"); + return NULL; +} + +static void * +audio_cap_fluidsynth_init(struct module *parent, const char *cfg) +{ + (void) parent; + struct state_fluidsynth_capture *s = calloc(1, sizeof *s); + char *ccfg = strdup(cfg); + int ret = parse_opts(s, ccfg); + free(ccfg); + if (ret != 0) { + audio_cap_fluidsynth_done(s); + return ret < 0 ? NULL : INIT_NOERR; + } + + char *sf = get_soundfont(); + if (sf == NULL) { + audio_cap_fluidsynth_done(s); + return NULL; + } + + /// @todo add other if some-one needs that... + if (audio_capture_bps != 0 && audio_capture_bps != FLUIDSYNTH_BPS) { + MSG(ERROR, "Only %d bits-per-second supported so far...\n", FLUIDSYNTH_BPS); + goto error; + } + if (audio_capture_channels > 2) { + MSG(ERROR, "Only 1 or 2 channels currently supported...\n"); + goto error; + } + + s->audio.bps = FLUIDSYNTH_BPS; + s->audio.ch_count = audio_capture_channels < 2 ? 1 : 2; + s->audio.sample_rate = audio_capture_sample_rate > 0 + ? audio_capture_sample_rate + : DEFAULT_FLUIDSYNTH_SAMPLE_RATE; + + const char *filename = s->req_filename; + if (!filename) { + filename = s->tmp_filename = load_song1(); + if (!filename) { + goto error; + } + } + + s->settings = new_fluid_settings(); + fluid_settings_setnum(s->settings, "synth.sample-rate", + s->audio.sample_rate); + + s->synth = new_fluid_synth(s->settings); + if (fluid_synth_sfload(s->synth, sf, 1) < 0) { + MSG(ERROR, "Failed to load SF2: %s\n", sf); + goto error; + } + s->player = new_fluid_player(s->synth); + if (fluid_player_add(s->player, filename) != FLUID_OK) { + MSG(ERROR, "Failed to add MIDI: %s\n", s->req_filename); + goto error; + } + fluid_player_play(s->player); + + s->audio.max_size = s->audio.data_len = + s->audio.ch_count * s->audio.bps * CHUNK_SIZE; + s->audio.data = malloc(s->audio.data_len); + s->left = malloc(s->audio.data_len / s->audio.ch_count); + s->right = malloc(s->audio.data_len / s->audio.ch_count); + + s->frame_interval = CHUNK_SIZE * NS_IN_SEC_DBL / s->audio.sample_rate; + s->next_frame_time = get_time_in_ns() + s->frame_interval; + + log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Initialized fluidsynth\n"); + + free(sf); + return s; +error: + audio_cap_fluidsynth_done(s); + free(sf); + return NULL; +} + +static struct audio_frame * +audio_cap_fluidsynth_read(void *state) +{ + struct state_fluidsynth_capture *s = state; + + if (fluid_player_get_status(s->player) == FLUID_PLAYER_DONE) { + MSG(VERBOSE, "Rewinding...\n"); + fluid_player_play(s->player); + } + + if (s->audio.ch_count == 1) { + fluid_synth_write_s16(s->synth, CHUNK_SIZE, s->audio.data, 0, 1, + s->right, 0, 1); + } else { // drop right channel, keep the left + assert(s->audio.ch_count == 2); + fluid_synth_write_s16(s->synth, CHUNK_SIZE, s->left, 0, 1, + s->right, 0, 1); + mux_channel(s->audio.data, (char *) s->left, s->audio.bps, + s->audio.bps * CHUNK_SIZE, s->audio.ch_count, 0, + 1.); + mux_channel(s->audio.data, (char *) s->right, s->audio.bps, + s->audio.bps * CHUNK_SIZE, s->audio.ch_count, 1, + 1.); + } + + time_ns_t t = get_time_in_ns(); + if (t > s->next_frame_time + s->frame_interval) { + MSG(WARNING, "Some data missed!\n"); + t = s->next_frame_time; + } else if (t < s->next_frame_time){ + usleep((s->next_frame_time - t) / US_IN_NS); + } + s->next_frame_time += s->frame_interval; + + return &s->audio; +} + +static void +audio_cap_fluidsynth_done(void *state) +{ + struct state_fluidsynth_capture *s = state; + free(s->audio.data); + free(s->req_filename); + free(s->left); + free(s->right); + if (s->tmp_filename) { + unlink(s->tmp_filename); + } + free(s); +} + +static const struct audio_capture_info acap_fluidsynth_info = { + audio_cap_fluidsynth_probe, audio_cap_fluidsynth_init, + audio_cap_fluidsynth_read, audio_cap_fluidsynth_done +}; + +REGISTER_MODULE(fluidsynth, &acap_fluidsynth_info, LIBRARY_CLASS_AUDIO_CAPTURE, + AUDIO_CAPTURE_ABI_VERSION); +REGISTER_MODULE_WITH_FLAG(sdl_mixer, &acap_fluidsynth_info, LIBRARY_CLASS_AUDIO_CAPTURE, + AUDIO_CAPTURE_ABI_VERSION, MODULE_FLAG_ALIAS); diff --git a/src/audio/capture/none.c b/src/audio/capture/none.c index 23df3d6681..29941da2b0 100644 --- a/src/audio/capture/none.c +++ b/src/audio/capture/none.c @@ -8,7 +8,7 @@ * Dalibor Matura <255899@mail.muni.cz> * Ian Wesley-Smith * - * Copyright (c) 2005-2023 CESNET z.s.p.o. + * Copyright (c) 2005-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -46,18 +46,14 @@ * */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif - -#include "audio/audio_capture.h" -#include "debug.h" -#include "lib_common.h" -#include -#include -#include +#include // for assert +#include // for uint32_t +#include // for free, NULL, malloc +#include "audio/audio_capture.h" // for AUDIO_CAPTURE_ABI_VERSION, audio_ca... +#include "debug.h" // for UNUSED +#include "lib_common.h" // for REGISTER_MODULE, library_class +struct device_info; +struct module; #define AUDIO_CAPTURE_NONE_MAGIC 0x43fb99ccu diff --git a/src/audio/capture/pipewire.cpp b/src/audio/capture/pipewire.cpp index 90f21ca2fe..732082c4c5 100644 --- a/src/audio/capture/pipewire.cpp +++ b/src/audio/capture/pipewire.cpp @@ -194,7 +194,7 @@ const static pw_stream_events stream_events = { static void audio_cap_pw_help(){ color_printf("Pipewire audio capture.\n"); color_printf("Usage\n"); - color_printf(TERM_BOLD TERM_FG_RED "\t-r pipewire" TERM_FG_RESET "[:target=][:buffer-len=][:channels=][:sample-rate=]\n" TERM_RESET); + color_printf(TERM_BOLD TERM_FG_RED "\t-s pipewire" TERM_FG_RESET "[:target=][:buffer-len=][:channels=][:sample-rate=]\n" TERM_RESET); color_printf("\n"); color_printf("Devices:\n"); diff --git a/src/audio/capture/sdi.cpp b/src/audio/capture/sdi.cpp index ecbc2837a8..3f58a6fe9d 100644 --- a/src/audio/capture/sdi.cpp +++ b/src/audio/capture/sdi.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2023 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,27 +35,24 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif // HAVE_CONFIG_H - -#include "audio/audio_capture.h" #include "audio/capture/sdi.h" -#include "audio/types.h" -#include "debug.h" -#include "host.h" -#include "lib_common.h" -#include "types.h" - -#include -#include -#include -#include -#include -#include -#include + +#include // for milliseconds +#include // for condition_variable +#include // for printf, snprintf, NULL +#include // for free, calloc, malloc +#include // for memcpy, strcmp, strncpy +#include // for mutex, unique_lock +#include // for basic_ostream, operator<<, basic_ios +#include // for basic_string, char_traits, hash +#include // for unordered_map, operator!= + +#include "audio/audio_capture.h" // for AUDIO_CAPTURE_ABI_VERSION, audio_ca... +#include "audio/types.h" // for audio_frame +#include "debug.h" // for LOG, LOG_LEVEL_WARNING, UNUSED +#include "host.h" // for commandline_params, INIT_NOERR +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "types.h" // for device_info, frame_flags_common #define DEFAULT_BUF_SIZE_MS 100L diff --git a/src/audio/capture/sdl_mixer.c b/src/audio/capture/sdl_mixer.c deleted file mode 100644 index 7360ffd95a..0000000000 --- a/src/audio/capture/sdl_mixer.c +++ /dev/null @@ -1,341 +0,0 @@ -/** - * @file audio/capture/sdl_mixer.c - * @author Martin Pulec - */ -/* - * Copyright (c) 2011-2025 CESNET - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, is permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of CESNET nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, - * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO - * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -/** - * @file - * @todo errata (SDL3 vs SDL2) - * 1. 1 channel capture (-a ch=1) seem no longer work but there is a workaround - * 2. insufficient performance (generates overflow even in default config) - */ - -#include "config.h" // for HAVE_SDL3 - -#ifdef HAVE_SDL3 -#include // for SDL_Init, SDL_INIT_AUDIO -#include // for AUDIO_S16LSB, AUDIO_S32LSB, AUDIO_S8 -#include // for MIX_MAX_VOLUME, Mix_GetError, Mix_C... -#else -#include // for SDL_Init, SDL_INIT_AUDIO -#include // for AUDIO_S16LSB, AUDIO_S32LSB, AUDIO_S8 -#include // for MIX_MAX_VOLUME, Mix_GetError, Mix_C... -#endif -#include // for NULL, fclose, fopen, size_t, FILE -#include // for free, calloc, getenv, atoi, malloc -#include // for strlen, strncat, strchr, strcmp -#include // for unlink - -#include "audio/audio_capture.h" -#include "audio/types.h" -#include "debug.h" -#include "host.h" -#include "lib_common.h" -#include "song1.h" -#include "types.h" -#include "utils/color_out.h" -#include "utils/fs.h" -#include "utils/macros.h" -#include "utils/ring_buffer.h" - -#define DEFAULT_SDL_MIXER_BPS 2 -#define DEFAULT_MIX_MAX_VOLUME (MIX_MAX_VOLUME / 4) -#define SDL_MIXER_SAMPLE_RATE 48000 -#define MOD_NAME "[SDL_mixer] " - -#ifdef HAVE_SDL3 -#define Mix_GetError SDL_GetError -#define SDL_ERR false -#else -#define SDL_AUDIO_S8 AUDIO_S8 -#define SDL_AUDIO_S16LE AUDIO_S16LSB -#define SDL_AUDIO_S32LE AUDIO_S32LSB -#define SDL_ERR (-1) -#endif - -struct state_sdl_mixer_capture { - Mix_Music *music; - struct audio_frame audio; - struct ring_buffer *sdl_mixer_buf; - int volume; - char *req_filename; -}; - -static void audio_cap_sdl_mixer_done(void *state); - -static void audio_cap_sdl_mixer_probe(struct device_info **available_devices, int *count, void (**deleter)(void *)) -{ - *deleter = free; - *count = 1; - *available_devices = calloc(1, sizeof **available_devices); - strncat((*available_devices)[0].dev, "sdl_mixer", sizeof (*available_devices)[0].dev - 1); - strncat((*available_devices)[0].name, "Sample midi song", sizeof (*available_devices)[0].name - 1); -} - -static void sdl_mixer_audio_callback(int chan, void *stream, int len, void *udata) -{ - UNUSED(chan); - struct state_sdl_mixer_capture *s = udata; - - ring_buffer_write(s->sdl_mixer_buf, stream, len); - memset(stream, 0, len); // do not playback anything to PC output -} - -static int parse_opts(struct state_sdl_mixer_capture *s, char *cfg) { - char *save_ptr = NULL; - char *item = NULL; - while ((item = strtok_r(cfg, ":", &save_ptr)) != NULL) { - cfg = NULL; - if (strcmp(item, "help") == 0) { - color_printf(TBOLD("sdl_mixer") " is a capture device capable playing various audio files like FLAC,\n" - "MIDI, mp3, Vorbis or WAV.\n\n" - "The main functional difference to " TBOLD("file") " video capture (that is able to play audio\n" - "files as well) is the support for " TBOLD("MIDI") " (and also having one song bundled).\n\n"); - color_printf("Usage:\n"); - color_printf(TBOLD(TRED("\t-s sdl_mixer") "[:file=][:volume=]") "\n"); - color_printf("where\n"); - color_printf(TBOLD("\t") " - name of file to be used\n"); - color_printf(TBOLD("\t ") " - volume [0..%d], default %d\n", MIX_MAX_VOLUME, DEFAULT_MIX_MAX_VOLUME); - color_printf("\n"); - color_printf(TBOLD("SDL_SOUNDFONTS") " - environment variable with path to sound fonts for MIDI playback (eg. freepats)\n"); - color_printf(TBOLD("ULTRAGRID_BUNDLED_SF") " - set this environment variable to 1 to skip loading system default sound font\n\n"); - return 1; - } - if (strstr(item, "file=") == item) { - s->req_filename = strdup(strchr(item, '=') + 1); - } else if (strstr(item, "volume=") == item) { - s->volume = atoi(strchr(item, '=') + 1); - } else { - log_msg(LOG_LEVEL_ERROR, MOD_NAME "Wrong option: %s!\n", item); - color_printf("Use " TBOLD("-s sdl_mixer:help") " to see available options.\n"); - return -1; - } - } - return 0; -} - -static const char *load_song1() { - const char *filename = NULL; - FILE *f = get_temp_file(&filename); - if (f == NULL) { - perror("fopen audio"); - return NULL; - } - size_t nwritten = fwrite(song1, sizeof song1, 1, f); - fclose(f); - if (nwritten != 1) { - unlink(filename); - return NULL; - } - return filename; -} - -/** - * Try to preload a sound font. - * - * This is mainly intended to allow loading sound fonts from application bundle - * on various platforms (get_install_root is relative to executable). But use - * to system default font, if available. - */ -static void try_open_soundfont() { - const _Bool force_bundled_sf = getenv("ULTRAGRID_BUNDLED_SF") != NULL && strcmp(getenv("ULTRAGRID_BUNDLED_SF"), "1") == 0; - if (!force_bundled_sf) { - const char *default_soundfont = Mix_GetSoundFonts(); - if (default_soundfont) { - FILE *f = fopen(default_soundfont, "rb"); - if (f) { - debug_msg(MOD_NAME "Default sound font '%s' seems usable, not trying to load additional fonts.\n", default_soundfont); - fclose(f); - return; - } - } - debug_msg(MOD_NAME "Unable to open default sound font '%s'\n", default_soundfont ? default_soundfont : "(no font)"); - } - const char *roots[] = { get_install_root(), "/usr" }; - const char *sf_candidates[] = { // without install prefix - "/share/soundfonts/default.sf2", "/share/soundfonts/default.sf3", - "/share/sounds/sf2/default-GM.sf2", "/share/sounds/sf2/default-GM.sf3", // Ubuntu - }; - for (size_t i = 0; i < sizeof roots / sizeof roots[0]; ++i) { - for (size_t j = 0; j < sizeof sf_candidates / sizeof sf_candidates[0]; ++j) { - const char *root = roots[i]; - const size_t len = strlen(root) + strlen(sf_candidates[j]) + 1; - char path[len]; - strncpy(path, root, len - 1); - strncat(path, sf_candidates[j], len - strlen(path) - 1); - FILE *f = fopen(path, "rb"); - debug_msg(MOD_NAME "Trying to open sound font '%s': %s\n", path, f ? "success, setting" : "failed"); - if (!f) { - continue; - } - fclose(f); - Mix_SetSoundFonts(path); - return; - } - } -} - -/// handle SDL 3.0.0 mixer not being able to capture mono -static void -adjust_ch_count(struct state_sdl_mixer_capture *s) -{ - int frequency = 0; - SDL_AudioFormat format = { 0 }; - int channels = 0; - Mix_QuerySpec(&frequency, &format, &channels); - if (audio_capture_channels > 0 && - channels != (int) audio_capture_channels) { - MSG(WARNING, - "%d channel capture seem to be broken with SDL3 mixer - " - "capturing %d channels and dropping the excessive later.\n", - s->audio.ch_count, channels); - s->audio.ch_count = channels; - } -} - -static void * audio_cap_sdl_mixer_init(struct module *parent, const char *cfg) -{ - UNUSED(parent); - SDL_Init(SDL_INIT_AUDIO); - - struct state_sdl_mixer_capture *s = calloc(1, sizeof *s); - s->volume = DEFAULT_MIX_MAX_VOLUME; - char *ccfg = strdup(cfg); - int ret = parse_opts(s, ccfg); - free(ccfg); - if (ret != 0) { - audio_cap_sdl_mixer_done(s); - return ret < 0 ? NULL : INIT_NOERR; - } - - s->audio.bps = audio_capture_bps ? audio_capture_bps : DEFAULT_SDL_MIXER_BPS; - s->audio.ch_count = audio_capture_channels > 0 ? audio_capture_channels - : MIX_DEFAULT_CHANNELS; - s->audio.sample_rate = SDL_MIXER_SAMPLE_RATE; - - int audio_format = 0; - switch (s->audio.bps) { - case 1: audio_format = SDL_AUDIO_S8; break; - case 2: audio_format = SDL_AUDIO_S16LE; break; - case 4: audio_format = SDL_AUDIO_S32LE; break; - default: UG_ASSERT(0 && "BPS can be only 1, 2 or 4"); - } - -#ifdef HAVE_SDL3 - SDL_AudioSpec spec = { - .format = audio_format, - .channels = s->audio.ch_count, - .freq = s->audio.sample_rate, - }; - if (!Mix_OpenAudio(0, &spec)) { -#else - if( Mix_OpenAudio(SDL_MIXER_SAMPLE_RATE, audio_format, - s->audio.ch_count, 4096 ) == -1 ) { -#endif - log_msg(LOG_LEVEL_ERROR, MOD_NAME "error initializing sound: %s\n", Mix_GetError()); - goto error; - } - adjust_ch_count(s); - const char *filename = s->req_filename; - if (!filename) { - filename = load_song1(); - if (!filename) { - goto error; - } - } - try_open_soundfont(); - s->music = Mix_LoadMUS(filename); - if (filename != s->req_filename) { - unlink(filename); - } - if (s->music == NULL) { - log_msg(LOG_LEVEL_ERROR, MOD_NAME "error loading file: %s\n", Mix_GetError()); - goto error; - } - - s->audio.max_size = - s->audio.data_len = s->audio.ch_count * s->audio.bps * s->audio.sample_rate /* 1 sec */; - s->audio.data = malloc(s->audio.data_len); - s->sdl_mixer_buf = ring_buffer_init(s->audio.data_len); - - // register grab as a postmix processor - if (!Mix_RegisterEffect(MIX_CHANNEL_POST, sdl_mixer_audio_callback, NULL, s)) { - log_msg(LOG_LEVEL_ERROR, MOD_NAME "Mix_RegisterEffect: %s\n", Mix_GetError()); - goto error; - } - - Mix_VolumeMusic(s->volume); - if (Mix_PlayMusic(s->music, -1) == SDL_ERR) { - log_msg(LOG_LEVEL_ERROR, MOD_NAME "error playing file: %s\n", Mix_GetError()); - goto error; - } - - log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Initialized SDL_mixer\n"); - - return s; -error: - audio_cap_sdl_mixer_done(s); - return NULL; -} - -static struct audio_frame *audio_cap_sdl_mixer_read(void *state) -{ - struct state_sdl_mixer_capture *s = state; - s->audio.data_len = ring_buffer_read(s->sdl_mixer_buf, s->audio.data, s->audio.max_size); - if (s->audio.data_len == 0) { - return NULL; - } - return &s->audio; -} - -static void audio_cap_sdl_mixer_done(void *state) -{ - struct state_sdl_mixer_capture *s = state; - Mix_HaltMusic(); - Mix_FreeMusic(s->music); - Mix_CloseAudio(); - free(s->audio.data); - free(s->req_filename); - free(s); -} - -static const struct audio_capture_info acap_sdl_mixer_info = { - audio_cap_sdl_mixer_probe, - audio_cap_sdl_mixer_init, - audio_cap_sdl_mixer_read, - audio_cap_sdl_mixer_done -}; - -REGISTER_MODULE(sdl_mixer, &acap_sdl_mixer_info, LIBRARY_CLASS_AUDIO_CAPTURE, AUDIO_CAPTURE_ABI_VERSION); - diff --git a/src/audio/capture/song1.h b/src/audio/capture/song1.h index 862c447649..e28cd72a8c 100644 --- a/src/audio/capture/song1.h +++ b/src/audio/capture/song1.h @@ -1,5 +1,5 @@ /* THIS CHUNK OF BYTES IS AUTOMATICALLY GENERATED */ -unsigned char song1[] = +static const unsigned char song1[] = { 0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x0d, 0x00, 0x60, 0x4d, 0x54, 0x72, 0x6b, 0x00, 0x00, 0x00, 0x1a, 0x00, 0xff, 0x7f, 0x03, 0x00, 0x00, 0x41, 0x00, 0xff, 0x58, diff --git a/src/audio/codec/dummy_pcm.c b/src/audio/codec/dummy_pcm.c index 2000e32267..279fcfb3aa 100644 --- a/src/audio/codec/dummy_pcm.c +++ b/src/audio/codec/dummy_pcm.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013-2015 CESNET, z. s. p. o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,17 +35,15 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for assert +#include // for bool +#include // for uint32_t +#include // for free, malloc, NULL -#include "audio/audio.h" -#include "audio/codec.h" - -#include "debug.h" -#include "lib_common.h" +#include "audio/codec.h" // for audio_codec_direction_t, AUDIO_COMPRESS_ABI... +#include "audio/types.h" // for audio_channel, AC_PCM, AC_NONE, audio_codec_t +#include "debug.h" // for UNUSED +#include "lib_common.h" // for REGISTER_MODULE, library_class #define MAGIC 0x552bca11 diff --git a/src/audio/filter/controlport_stats.cpp b/src/audio/filter/controlport_stats.cpp index afc2d3958f..0cd8d149c2 100644 --- a/src/audio/filter/controlport_stats.cpp +++ b/src/audio/filter/controlport_stats.cpp @@ -53,11 +53,9 @@ #include "lib_common.h" struct state_controlport_stats{ - state_controlport_stats(struct module *mod) : mod(MODULE_CLASS_DATA, mod, this) + explicit state_controlport_stats(struct module *mod) + : mod(MODULE_CLASS_DATA, mod, this), control(get_control_state(mod)) { - control = (control_state *) (get_module(get_root_module(mod), - "control")) - ->priv_data; } module_raii mod; diff --git a/src/audio/utils.cpp b/src/audio/utils.cpp index e527455856..0e388d93ea 100644 --- a/src/audio/utils.cpp +++ b/src/audio/utils.cpp @@ -4,7 +4,7 @@ * @author Martin Piatka */ /* - * Copyright (c) 2011-2024 CESNET + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -368,6 +368,15 @@ void remux_channel(char *out, const char *in, int bps, int in_len, int in_stream } } +/** + * @param out output buffer (base) pointner + * @param in input channel pointer + * @param bps bytes per second + * @param in_len in len in bytes + * @param out_stream_channels number of channels that will be muxed + * @param pos_in_stream position of muxed channels in output (0-indexed) + * @param scale scale the input channel (1.0 to keep original volume) + */ void mux_channel(char *out, const char *in, int bps, int in_len, int out_stream_channels, int pos_in_stream, double scale) { int samples = in_len / bps; diff --git a/src/capture_filter.cpp b/src/capture_filter.cpp index adcc29e063..c8f599ec34 100644 --- a/src/capture_filter.cpp +++ b/src/capture_filter.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2014-2021 CESNET, z. s. p. o. + * Copyright (c) 2014-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,19 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ - #include "capture_filter.h" -#include "debug.h" -#include "lib_common.h" -#include "module.h" -#include "utils/color_out.h" -#include "utils/list.h" -#include "video.h" + +#include // for assert +#include // for printf, fprintf, stderr +#include // for free, NULL, atoi, calloc, malloc +#include // for strchr, strcmp, strdup, strlen, strncmp + +#include "compat/strings.h" // for strcasecmp +#include "lib_common.h" // for get_libraries_for_class, library_class +#include "messaging.h" // for msg_universal, new_response, RESPONSE_I... +#include "module.h" // for module, module_done, module_init_default +#include "utils/color_out.h" // for color_printf, TERM_BOLD, TERM_RESET +#include "utils/list.h" // for simple_linked_list_pop, simple_linked_l... using namespace std; diff --git a/src/capture_filter/blank.c b/src/capture_filter/blank.c index 27acc8a3d4..d42af3752a 100644 --- a/src/capture_filter/blank.c +++ b/src/capture_filter/blank.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013-2023 CESNET, z. s. p. o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,23 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for assert +#include // for bool, false, true +#include // for uint8_t +#include // for printf, fprintf, stderr +#include // for free, atof, atoi, calloc, malloc +#include // for memset, strtok_r, memcpy, strchr +#include "libavutil/pixfmt.h" // for AVPixelFormat +#include "types.h" // for tile, video_frame, video_desc, BGR +#include "video_frame.h" // for video_desc_from_frame, video_desc_eq #include "capture_filter.h" +#include "compat/strings.h" // for strcasecmp #include "lib_common.h" #include "libavcodec/utils.h" #include "messaging.h" #include "module.h" -#include "video.h" #include "video_codec.h" #include diff --git a/src/capture_filter/color.c b/src/capture_filter/color.c index 814bd96dde..c52fe96d9c 100644 --- a/src/capture_filter/color.c +++ b/src/capture_filter/color.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2022 CESNET, z. s. p. o. + * Copyright (c) 2022-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,21 +35,22 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ + +#include // for NULL, calloc, free +#include // for memcpy, strcmp #include "capture_filter.h" #include "debug.h" #include "lib_common.h" #include "utils/color_out.h" #include "pixfmt_conv.h" -#include "video.h" +#include "types.h" // for tile, video_frame #include "video_codec.h" +#include "video_frame.h" // for vf_alloc_desc #include "vo_postprocess/capture_filter_wrapper.h" +struct module; + #define MOD_NAME "[color] " struct state_capture_filter_color { diff --git a/src/capture_filter/display.c b/src/capture_filter/display.c index b1aa72fca7..ba9059a572 100644 --- a/src/capture_filter/display.c +++ b/src/capture_filter/display.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2022-2023 CESNET z.s.p.o. + * Copyright (c) 2022-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,16 +39,17 @@ * * the data is 2x copied */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for pthread_mutex_unlock, pthread_mutex_lock +#include // for NULL, calloc, free, size_t +#include // for strchr, strcmp, strlen, strdupa #include "capture_filter.h" #include "compat/strings.h" // strdupa #include "debug.h" +#include "host.h" // for exit_uv #include "lib_common.h" +#include "pixfmt_conv.h" // for get_best_decoder_from, get_decoder_from_to +#include "types.h" // for tile, video_desc, video_frame, codec_t #include "utils/color_out.h" #include "utils/list.h" #include "utils/text.h" diff --git a/src/capture_filter/disrupt.c b/src/capture_filter/disrupt.c index 7bba1f344a..6f2f99510a 100644 --- a/src/capture_filter/disrupt.c +++ b/src/capture_filter/disrupt.c @@ -6,7 +6,7 @@ * occur during regular use like jitter between frames. */ /* - * Copyright (c) 2021 CESNET, z. s. p. o. + * Copyright (c) 2021-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,11 +38,10 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ + +#include // for printf, NULL +#include // for free, atof, calloc +#include // for strchr, strcmp, strlen, strstr #include "capture_filter.h" @@ -51,12 +50,11 @@ #include "lib_common.h" #include "utils/color_out.h" #include "utils/random.h" -#include "video.h" -#include "video_codec.h" #define MOD_NAME "[disrupt c. f.] " struct module; +struct video_frame; static int init(struct module *parent, const char *cfg, void **state); static void done(void *state); diff --git a/src/capture_filter/every.c b/src/capture_filter/every.c index 83cee40e2b..ba658f2fbe 100644 --- a/src/capture_filter/every.c +++ b/src/capture_filter/every.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013-2019 CESNET, z. s. p. o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,18 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for printf +#include // for atoi, calloc, free +#include // for strchr, memcpy, strlen +#include // for strcasecmp #include "capture_filter.h" #include "debug.h" #include "lib_common.h" +#include "types.h" // for video_frame, video_frame_callbacks, tile #include "utils/color_out.h" -#include "video.h" -#include "video_codec.h" +#include "video_frame.h" // for VIDEO_FRAME_DISPOSE, vf_alloc_desc, vf_... struct module; diff --git a/src/capture_filter/flip.c b/src/capture_filter/flip.c index b8e62c7b46..af4aa6b2f0 100644 --- a/src/capture_filter/flip.c +++ b/src/capture_filter/flip.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2015-2023 CESNET, z. s. p. o. + * Copyright (c) 2015-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,11 +35,12 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for assert +#include // for bool, false, true +#include // for uint8_t +#include // for printf, fprintf, NULL, stderr, size_t +#include // for free, atof, atoi, calloc, malloc +#include // for memset, strtok_r, memcpy, strchr #include "capture_filter.h" #include "debug.h" diff --git a/src/capture_filter/gamma.cpp b/src/capture_filter/gamma.cpp index 8aef303bf4..12ce46c3fc 100644 --- a/src/capture_filter/gamma.cpp +++ b/src/capture_filter/gamma.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2020 CESNET, z. s. p. o. + * Copyright (c) 2020-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,14 +35,15 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ - +#include // for errno +#include // for CHAR_BIT +#include // for pow +#include // for uint16_t, uint8_t +#include // for size_t, malloc +#include // for strcmp, strlen +#include // for exception #include -#include +#include // for numeric_limits #include #include @@ -51,8 +52,9 @@ #include "lib_common.h" #include "utils/color_out.h" #include "utils/worker.h" -#include "video.h" +#include "types.h" // for tile, video_frame #include "video_codec.h" +#include "video_frame.h" // for vf_free, VIDEO_FR... #include "vo_postprocess/capture_filter_wrapper.h" constexpr const char *MOD_NAME = "[gamma cap. f.] "; diff --git a/src/capture_filter/grayscale.c b/src/capture_filter/grayscale.c index 8fd22dfb1c..b5982de0d6 100644 --- a/src/capture_filter/grayscale.c +++ b/src/capture_filter/grayscale.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2015-2023 CESNET, z. s. p. o. + * Copyright (c) 2015-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,17 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for strcmp, strlen, NULL +#include // for calloc, free, malloc #include "capture_filter.h" #include "debug.h" #include "lib_common.h" +#include "types.h" // for tile, video_frame #include "utils/color_out.h" -#include "video.h" -#include "video_codec.h" +#include "video_frame.h" // for vf_alloc_desc #include "vo_postprocess/capture_filter_wrapper.h" +struct module; static int init(struct module *parent, const char *cfg, void **state); static void done(void *state); diff --git a/src/capture_filter/mirror.c b/src/capture_filter/mirror.c index 192fa92c24..d093e5e3b4 100644 --- a/src/capture_filter/mirror.c +++ b/src/capture_filter/mirror.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2015-2023 CESNET, z. s. p. o. + * Copyright (c) 2015-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,18 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for calloc, free, malloc +#include // for strcmp, strlen #include "capture_filter.h" #include "debug.h" #include "lib_common.h" +#include "types.h" // for tile, video_frame #include "utils/color_out.h" -#include "video.h" #include "video_codec.h" +#include "video_frame.h" // for vf_alloc_desc #include "vo_postprocess/capture_filter_wrapper.h" +struct module; static int init(struct module *parent, const char *cfg, void **state); static void done(void *state); diff --git a/src/capture_filter/override_prop.c b/src/capture_filter/override_prop.c index 8b6613426b..d392ae154a 100644 --- a/src/capture_filter/override_prop.c +++ b/src/capture_filter/override_prop.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2023 CESNET, z. s. p. o. + * Copyright (c) 2023-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,21 +35,20 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for printf +#include // for atoi, calloc, free +#include // for strchr, NULL, strstr #include "capture_filter.h" #include "compat/strings.h" // for strdupa #include "debug.h" #include "lib_common.h" +#include "types.h" // for video_frame, vide... #include "utils/color_out.h" #include "utils/text.h" -#include "video.h" #include "video_codec.h" +#include "video_frame.h" // for parse_fps, vf_all... #include "vo_postprocess/capture_filter_wrapper.h" #define MOD_NAME "[override_prop] " diff --git a/src/capture_filter/preview.cpp b/src/capture_filter/preview.cpp index fad3121905..4b84532423 100644 --- a/src/capture_filter/preview.cpp +++ b/src/capture_filter/preview.cpp @@ -3,7 +3,7 @@ * @author Martin Piatka */ /* - * Copyright (c) 2022 CESNET, z. s. p. o. + * Copyright (c) 2022-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,31 +35,31 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ - -#include -#include -#include -#include -#include -#include +#include // for sleep + +#include // for max +#include // for atomic +#include // for assert #include +#include // for condition_variable +#include // for make_unique, unique_ptr +#include // for mutex, lock_guard, unique_lock +#include // for queue +#include // for char_traits, basic_string +#include // for operator==, basic_string_view +#include // for thread +#include // for move +#include // for vector #include "capture_filter.h" #include "debug.h" #include "lib_common.h" +#include "types.h" // for tile, video_frame, RGB, codec_t #include "utils/color_out.h" #include "utils/fs.h" -#include "utils/misc.h" #include "utils/macros.h" #include "utils/string_view_utils.hpp" -#include "video.h" -#include "video_codec.h" #include "../tools/ipc_frame.h" #include "../tools/ipc_frame_unix.h" #include "../tools/ipc_frame_ug.h" diff --git a/src/capture_filter/ratelimit.c b/src/capture_filter/ratelimit.c index f77c5624fc..2eec706400 100644 --- a/src/capture_filter/ratelimit.c +++ b/src/capture_filter/ratelimit.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2023 CESNET, z. s. p. o. + * Copyright (c) 2023-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,20 +35,18 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include // for NULL, printf +#include // for calloc, free, strtod +#include // for strlen, memcpy #include "capture_filter.h" - +#include "compat/strings.h" // for strcasecmp #include "debug.h" #include "lib_common.h" #include "tv.h" +#include "types.h" // for video_frame, video_frame_callbacks, tile #include "utils/color_out.h" -#include "video.h" -#include "video_codec.h" +#include "video_frame.h" // for VIDEO_FRAME_DISPOSE, vf_alloc_desc, vf_... struct module; diff --git a/src/capture_filter/split.c b/src/capture_filter/split.c index b9c77661f7..cb615ffd8d 100644 --- a/src/capture_filter/split.c +++ b/src/capture_filter/split.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2019 CESNET, z. s. p. o. + * Copyright (c) 2019-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,20 +35,20 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ -#include "capture_filter.h" +#include // for assert +#include // for printf, NULL +#include // for atoi, calloc, free +#include // for strchr +#include "capture_filter.h" +#include "compat/strings.h" // for strcasecmp #include "debug.h" #include "lib_common.h" +#include "types.h" // for video_desc, video_frame, video_frame_ca... #include "utils/color_out.h" #include "utils/vf_split.h" -#include "video.h" -#include "video_codec.h" +#include "video_frame.h" // for vf_alloc_desc_data, video_desc_from_frame #define MAX_TILES 16 diff --git a/src/capture_filter/temporal_3d.c b/src/capture_filter/temporal_3d.c new file mode 100644 index 0000000000..2c8d30ad30 --- /dev/null +++ b/src/capture_filter/temporal_3d.c @@ -0,0 +1,139 @@ +/** + * @file capture_filter/temporal_3d.c + * @author Martin Pulec + */ +/* + * Copyright (c) 2025 CESNET + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include // for assert +#include // for uint32_t +#include // for printf, NULL +#include // for free, calloc, malloc +#include // for memcpy, strlen + +#include "capture_filter.h" // for CAPTURE_FILTER_ABI_VERSION, capture_fil... +#include "compat/strings.h" // for strcasecmp +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "types.h" // for tile, video_frame, video_frame_callbacks +#include "utils/color_out.h" // for color_printf, TBOLD +#include "utils/macros.h" // for to_fourcc +#include "video_frame.h" // for VIDEO_FRAME_DISPOSE, vf_alloc_desc, vf_... + +struct module; + +#define MAGIC to_fourcc('c', 'f', 't', '3') + +struct state_temporal_3d { + uint32_t magic; + struct video_frame *f; +}; + +static void usage() { + color_printf("Combines temporarily-interlaced 3D.\n\n"); + color_printf(TBOLD("temporal_3d") " usage:\n"); + printf("\ttemporal_3d\n\n"); + printf("(takes no arguments)\n"); +} + +static int init(struct module *parent, const char *cfg, void **state) +{ + (void) parent; + + if(strcasecmp(cfg, "help") == 0) { + usage(); + return 1; + } + + if(strlen(cfg) > 0) { + usage(); + return -1; + } + + struct state_temporal_3d *s = calloc(1 ,sizeof *s); + s->magic = MAGIC; + *state = s; + return 0; +} + +static void dispose_frame(struct video_frame *f) { + VIDEO_FRAME_DISPOSE((struct video_frame *) f->callbacks.dispose_udata); + free(f->tiles[0].data); // f->data_delter is not set so vf_free doesn't + // delete... + vf_free(f); +} + +static void done(void *state) +{ + struct state_temporal_3d *s = state; + assert(s->magic == MAGIC); + if (s->f) { + dispose_frame(s->f); + } + free(s); +} + +static struct video_frame *filter(void *state, struct video_frame *in) +{ + struct state_temporal_3d *s = state; + assert(s->magic == MAGIC); + + assert(in->tile_count == 1); + + if (s->f == NULL) { + struct video_desc desc = video_desc_from_frame(in); + desc.tile_count = 2; + desc.fps /= 2; + s->f = vf_alloc_desc(desc); + s->f->tiles[0].data = malloc(s->f->tiles[0].data_len); + memcpy(s->f->tiles[0].data, in->tiles[0].data, in->tiles[0].data_len); + VIDEO_FRAME_DISPOSE(in); + return NULL; + } + + struct video_frame *f = s->f; + s->f->tiles[1].data = in->tiles[0].data; + f->callbacks.dispose_udata = in; + f->callbacks.dispose = dispose_frame; + s->f = NULL; + + return f; +} + +static const struct capture_filter_info capture_filter_temporal_3d = { + .init = init, + .done = done, + .filter = filter, +}; + +REGISTER_MODULE(temporal_3d, &capture_filter_temporal_3d, LIBRARY_CLASS_CAPTURE_FILTER, CAPTURE_FILTER_ABI_VERSION); + diff --git a/src/control_socket.cpp b/src/control_socket.cpp index 019c6dd36e..f42dd26221 100644 --- a/src/control_socket.cpp +++ b/src/control_socket.cpp @@ -1043,6 +1043,26 @@ int control_audio_ch_report_count(struct control_state *state){ return state ? state->audio_channel_report_count : 0; } +/** + * finds control socket state from module hierarchy + * + * @param mod module in the tree (doesn't need to be root), may be nullptr (nullptr is returned then) + * @returns found state; nullptr if state not found (or mod=0) + */ +struct control_state * +get_control_state(struct module *mod) +{ + if (mod == nullptr) { + return nullptr; + } + struct module *control_mod = + get_module(get_root_module(mod), "control"); + if (control_mod == nullptr) { + return nullptr; + } + return (struct control_state *) control_mod->priv_data; +} + static void print_control_help() { color_printf("Control internal commands:\n" TBOLD("\texit") "\n" diff --git a/src/control_socket.h b/src/control_socket.h index d02f2eab57..e94520943a 100644 --- a/src/control_socket.h +++ b/src/control_socket.h @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013-2021 CESNET, z. s. p. o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,7 @@ struct module; * @retval 0 if success */ int control_init(int port, int connection_type, struct control_state **state, struct module *root_module, int force_ip_version); +struct control_state *get_control_state(struct module *mod); void control_start(struct control_state *state); void control_done(struct control_state *s); void control_report_stats(struct control_state *state, const std::string & stat_line); @@ -58,6 +59,5 @@ void control_report_event(struct control_state *state, const std::string & event bool control_stats_enabled(struct control_state *state); int control_audio_ch_report_count(struct control_state *state); - #endif // control_socket_h_ diff --git a/src/host.cpp b/src/host.cpp index d09ba9468f..cab9f48213 100644 --- a/src/host.cpp +++ b/src/host.cpp @@ -46,7 +46,7 @@ #ifdef _WIN32 #include -#elif defined(__GLIBC__) +#elif defined(__APPLE__) || defined(__GLIBC__) #include #include #endif // !defined _WIN32 @@ -60,6 +60,10 @@ #include #endif +#ifdef HAVE_LIBBACKTRACE +#include +#endif + #include // for max #include #include // for assert @@ -428,6 +432,61 @@ static void echeck_unexpected_exit(void ) { fprintf(stderr, "exit() called unexpectedly! Maybe by some library?\n"); } +#ifdef HAVE_LIBBACKTRACE +static struct backtrace_state *bt; + +static void +libbt_error_callback(void *data, const char *msg, int errnum) +{ + int fd = *reinterpret_cast(data); + char buf[STR_LEN]; + char *start = buf; + const char *const end = buf + sizeof buf; + + //fprintf(stderr, "libbacktrace error: %s (%d)\n", msg, errnum); + + strappend(&start, end, "libbacktrace error: "); + strappend(&start, end, msg); + strappend(&start, end, " ("); + append_number(&start, end, errnum); + + write_all(fd, start - buf, buf); +} + +static int +libbt_full_callback(void *data, uintptr_t pc, const char *filename, int lineno, + const char *function) +{ + int fd = *reinterpret_cast(data); + char buf[STR_LEN]; + char *start = buf; + const char *const end = buf + sizeof buf; + + // printf(" %s at %s:%d [pc=%p]\n", function ? function : "??", + // filename ? filename : "??", lineno, (void *) pc); + + strappend(&start, end, " "); + if (function == nullptr) { + function = "??"; + } + strappend(&start, end, function); + strappend(&start, end, " at "); + if (filename == nullptr) { + filename = "??"; + } + strappend(&start, end, filename); + strappend(&start, end, ":"); + append_number(&start, end, lineno); + strappend(&start, end, " [pc=0x"); + append_number(&start, end, (uintmax_t) pc); + strappend(&start, end, "]\n"); + + write_all(fd, start - buf, buf); + + return 0; // continue +} +#endif // defined HAVE_LIBBACKTRACE + struct init_data *common_preinit(int argc, char *argv[]) { uv_argc = argc; @@ -527,6 +586,12 @@ struct init_data *common_preinit(int argc, char *argv[]) fec_init(); #endif +#ifdef HAVE_LIBBACKTRACE + int fd = STDERR_FILENO; + bt = backtrace_create_state(uv_argv[0], 1 /*thread safe*/, + libbt_error_callback, &fd); +#endif + atexit(echeck_unexpected_exit); return new init_data{ std::move(init) }; @@ -1197,8 +1262,53 @@ bool running_in_debugger(){ return false; } -#if defined(__GLIBC__) -/// print stacktrace with backtrace_symbols_fd() (glibc or macOS) +#if defined(__APPLE__) || defined(__GLIBC__) +static void +perror_sig_safe(const char *s) +{ + char buf[128]; + char *start = buf; + const char *const end = buf + sizeof buf; + strappend(&start, end, s); + strappend(&start, end, ": "); + append_number(&start, end, errno); + strappend(&start, end, "\n"); + write_all(STDERR_FILENO, start - buf, buf); +} +/// dumps output of fd (from start_off offset) to stderr +/// and keep the pointer at the end of the file +/// @retval size of the file pointed by fd (current pos) +static off_t +st_glibc_flush_output(int fd, off_t start_off) +{ + if (fd == STDERR_FILENO) { + return 0; + } + + const off_t off = lseek(fd, start_off, SEEK_SET); + if (off == -1) { + perror_sig_safe("lseek"); + } + + char buf[STR_LEN]; + ssize_t rbytes = 0; + while ((rbytes = read(fd, buf, sizeof buf)) > 0) { + ssize_t written = 0; + ssize_t wbytes = 0; + while (written < rbytes && + (wbytes = write(STDERR_FILENO, buf + written, + rbytes - written)) > 0) { + written += wbytes; + } + } + return lseek(fd, 0, SEEK_CUR); +} +/** + * print stacktrace with backtrace_symbols_fd() (glibc or macOS) + * + * ideally all functions should be async-signal-safe as defined by POSIX + * (glibc deviates sligntly, see also signal-safety(7)) + */ static void print_stacktrace_glibc() { @@ -1209,11 +1319,18 @@ print_stacktrace_glibc() #else char path[MAX_PATH_SIZE]; #ifdef __APPLE__ - const unsigned long tid = pthread_mach_thread_np(pthread_self()); + unsigned long tid = pthread_mach_thread_np(pthread_self()); #else - const unsigned long tid = syscall(__NR_gettid); + unsigned long tid = syscall(__NR_gettid); #endif - snprintf(path, sizeof path, "%s/ug-%lu", get_temp_dir(), tid); + // snprintf(path, sizeof path, "%s/ug-%lu", get_temp_dir(), tid); + char *start = path; + path[sizeof path - 1] = '\0'; + const char *const end = path + sizeof path - 1; + strappend(&start, end, get_temp_dir()); + strappend(&start, end, "/ug-bt-"); + append_number(&start, end, tid); + *start = '\0'; int fd = open(path, O_CLOEXEC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); unlink(path); #endif @@ -1221,27 +1338,43 @@ print_stacktrace_glibc() fd = STDERR_FILENO; } char backtrace_msg[] = "Backtrace:\n"; - write_all(fd, sizeof backtrace_msg, backtrace_msg); + write_all(fd, sizeof backtrace_msg - 1, backtrace_msg); array addresses{}; const int num_symbols = backtrace(addresses.data(), addresses.size()); backtrace_symbols_fd(addresses.data(), num_symbols, fd); - if (fd == STDERR_FILENO) { - return; - } - lseek(fd, 0, SEEK_SET); - char buf[STR_LEN]; - ssize_t rbytes = 0; - while ((rbytes = read(fd, buf, sizeof buf)) > 0) { - ssize_t written = 0; - ssize_t wbytes = 0; - while (written < rbytes && - (wbytes = write(STDERR_FILENO, buf + written, - rbytes - written)) > 0) { - written += wbytes; + // in case that the below fails, try write at least something + off_t last_pos = st_glibc_flush_output(fd, 0); + +#ifdef HAVE_LIBBACKTRACE + char backtrace2_msg[] = "\nBacktrace symbolic:\n"; + write_all(fd, sizeof backtrace2_msg - 1, backtrace2_msg); + for (int i = 0; i < num_symbols; i++) { + // printf("%2d: ", i); + enum { NDIGITS = 2 }; + char sym_nr[] = { 'X', 'X', ':', ' ' }; + int num_tmp = i; + for (int i = 0; i < NDIGITS; ++i) { + if (num_tmp == 0 && i != 0) { + sym_nr[NDIGITS - 1 - i] = ' '; + } else { + sym_nr[NDIGITS - 1 - i] = '0' + (num_tmp % 10); + num_tmp /= 10; + } } + write_all(fd, sizeof sym_nr, sym_nr); + // backtrace_pcinfo may not be async-signal-safe + backtrace_pcinfo(bt, (uintptr_t) addresses[i], libbt_full_callback, + libbt_error_callback, &fd); + } + st_glibc_flush_output(fd, last_pos); +#else + (void) last_pos; +#endif + + if (fd != STDERR_FILENO) { + close(fd); } - close(fd); } #endif // defined(__GLIBC__) @@ -1250,7 +1383,7 @@ print_backtrace() { #ifdef _WIN32 print_stacktrace_win(); -#elif defined(__GLIBC__) +#elif defined(__APPLE__) || defined(__GLIBC__) print_stacktrace_glibc(); #else const char *msg = "Stacktrace printout not supported!\n"; diff --git a/src/lib_common.cpp b/src/lib_common.cpp index 60fb1cba5b..b4fc13f878 100644 --- a/src/lib_common.cpp +++ b/src/lib_common.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2012-2023 CESNET, z. s. p. o. + * Copyright (c) 2012-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -215,7 +215,7 @@ void open_all(const char *pattern, list &libs) { struct lib_info { const void *data; int abi_version; - bool hidden; + unsigned visibility_flag; }; // http://stackoverflow.com/questions/1801892/making-mapfind-operation-case-insensitive @@ -247,13 +247,22 @@ static auto& get_libmap(){ return libraries; } -void register_library(const char *name, const void *data, enum library_class cls, int abi_version, int hidden) +/** + * @param name module name (used in listing and on cmdline for spec) + * @param cls module class + * @param name class-specific metadata structure (with callbacks) + * @param abi_version class-specific ABI version + * @param visibility_flag usually 0 or flag from @ref enum module_flag + */ +void +register_library(const char *name, const void *info, enum library_class cls, + int abi_version, unsigned visibility_flag) { auto& map = get_libmap()[cls]; if (map.find(name) != map.end()) { LOG(LOG_LEVEL_ERROR) << "Module \"" << name << "\" (class " << cls << ") multiple initialization!\n"; } - map[name] = {data, abi_version, static_cast(hidden)}; + map[name] = {info, abi_version, visibility_flag}; } const void *load_library(const char *name, enum library_class cls, int abi_version) @@ -293,10 +302,12 @@ const void *load_library(const char *name, enum library_class cls, int abi_versi /** * Prints list of modules of given class - * @param full include hidden modules + * @param full include hidden modules and aliases */ void list_modules(enum library_class cls, int abi_version, bool full) { - const auto & class_set = get_libraries_for_class(cls, abi_version, full); + const auto &class_set = get_libraries_for_class( + cls, abi_version, + full ? MODULE_SHOW_ALL : MODULE_SHOW_VISIBLE_ONLY); for (auto && item : class_set) { col() << "\t" << SBOLD(item.first.c_str()) << "\n"; } @@ -333,7 +344,9 @@ bool list_all_modules() { return ret; } -map get_libraries_for_class(enum library_class cls, int abi_version, bool include_hidden) +map +get_libraries_for_class(enum library_class cls, int abi_version, + unsigned include_flags) { map ret; auto& libraries = get_libmap(); @@ -341,7 +354,9 @@ map get_libraries_for_class(enum library_class cls, int ab if (it != libraries.end()) { for (auto && item : it->second) { if (abi_version == item.second.abi_version) { - if (include_hidden || !item.second.hidden) { + if (item.second.visibility_flag == 0 || + (include_flags & + item.second.visibility_flag) != 0U) { ret[item.first] = item.second.data; } } else { @@ -354,4 +369,3 @@ map get_libraries_for_class(enum library_class cls, int ab return ret; } - diff --git a/src/lib_common.h b/src/lib_common.h index b73556347b..5cc326dc46 100644 --- a/src/lib_common.h +++ b/src/lib_common.h @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2023 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -85,45 +85,59 @@ enum library_class { LIBRARY_CLASS_AUDIO_FILTER, }; const void *load_library(const char *name, enum library_class, int abi_version); -void register_library(const char *name, const void *info, enum library_class, int abi_version, int hidden); +void register_library(const char *name, const void *info, enum library_class, + int abi_version, unsigned visibility_flag); void list_modules(enum library_class, int abi_version, bool full); bool list_all_modules(); #ifdef __cplusplus } #endif +enum module_flag { + MODULE_SHOW_VISIBLE_ONLY = 0, ///< display only modules w/o flag + MODULE_FLAG_HIDDEN = 1 << 0, ///< flag - do not show in listing + MODULE_FLAG_ALIAS = + 1 << 1, ///< ditto + hide for GUI, for explicit init only + MODULE_SHOW_ALL = + MODULE_FLAG_HIDDEN | MODULE_FLAG_ALIAS, ///< display all modules +}; + #ifdef __cplusplus #include #include -std::map get_libraries_for_class(enum library_class cls, int abi_version, bool include_hidden = true); +std::map +get_libraries_for_class(enum library_class cls, int abi_version, + unsigned include_flags = MODULE_FLAG_HIDDEN); #endif /** * Placeholder that installs module via constructor for every macro - * REGISTER_MODULE/REGISTER_MODULE_HIDDEN call + * REGISTER_MODULE* call * @param name non-quoted module name * @param lclass class of the module * @param abi abi version (specific for every class) * @param funcname unique function name that will be used to register * the module (as a constructor) - * @param hidden 0/1 - whether the module should be visible by eg. '-c help' - * (for technical and deprecated modules), default true + * @param flag optional flag to limit visibility (@ref module_flag; + * for technical, deprecated modules and aliases), default 0 */ -#define REGISTER_MODULE_FUNCNAME(name, info, lclass, abi, funcname, hidden) static void funcname(void) __attribute__((constructor));\ +#define REGISTER_MODULE_FUNCNAME(name, info, lclass, abi, funcname, flag) \ + static void funcname(void) __attribute__((constructor)); \ \ static void funcname(void)\ {\ - register_library(#name, info, lclass, abi, hidden);\ + register_library(#name, info, lclass, abi, flag);\ }\ struct NOT_DEFINED_STRUCT_THAT_SWALLOWS_SEMICOLON -#define REGISTER_MODULE_FUNC_FUNCNAME(name, func, lclass, abi, funcname, hidden) static void funcname(void) __attribute__((constructor));\ +#define REGISTER_MODULE_FUNC_FUNCNAME(name, func, lclass, abi, funcname, flag) \ + static void funcname(void) __attribute__((constructor)); \ \ static void funcname(void)\ {\ const void *info = func();\ if (info) {\ - register_library(#name, info, lclass, abi, hidden);\ + register_library(#name, info, lclass, abi, flag);\ }\ }\ struct NOT_DEFINED_STRUCT_THAT_SWALLOWS_SEMICOLON @@ -150,10 +164,11 @@ struct NOT_DEFINED_STRUCT_THAT_SWALLOWS_SEMICOLON #define REGISTER_MODULE_WITH_FUNC(name, func, lclass, abi) REGISTER_MODULE_FUNC_FUNCNAME(name, func, lclass, abi, UNIQUE_LABEL, 0) /** - * Similar to @ref REGISTER_MODULE but do not show the module under help - * of corresponding class (usable for technical or deprecated modules). + * Similar to @ref REGISTER_MODULE but allow @ref module_flag to be added to limit visibility. */ -#define REGISTER_HIDDEN_MODULE(name, info, lclass, abi) REGISTER_MODULE_FUNCNAME(name, info, lclass, abi, UNIQUE_LABEL, 1) +#define REGISTER_MODULE_WITH_FLAG(name, info, lclass, abi, flag) \ + REGISTER_MODULE_FUNCNAME(name, info, lclass, abi, UNIQUE_LABEL, flag) +#define REGISTER_HIDDEN_MODULE(name, info, lclass, abi) REGISTER_MODULE_WITH_FLAG(name, info, lclass, abi, MODULE_FLAG_HIDDEN) #endif // defined LIB_COMMON_H diff --git a/src/rtp/audio_decoders.cpp b/src/rtp/audio_decoders.cpp index 9a27410f47..167de4bd37 100644 --- a/src/rtp/audio_decoders.cpp +++ b/src/rtp/audio_decoders.cpp @@ -271,10 +271,7 @@ void *audio_decoder_init(char *audio_channel_map, const char *audio_scale, const s->packet_counter = packet_counter_init(0); s->audio_decompress = NULL; - - s->control = (struct control_state *) get_module( - get_root_module(parent), "control") - ->priv_data; + s->control = get_control_state(parent); if (strlen(encryption) > 0) { s->dec_funcs = static_cast(load_library("openssl_decrypt", diff --git a/src/rtp/video_decoders.cpp b/src/rtp/video_decoders.cpp index a9238ed9e2..cfccfce60e 100644 --- a/src/rtp/video_decoders.cpp +++ b/src/rtp/video_decoders.cpp @@ -335,21 +335,20 @@ struct main_msg_reconfigure { */ struct state_video_decoder { - state_video_decoder(struct module *parent) { + explicit state_video_decoder(struct module *parent) + : control(get_control_state(parent)) + { module_init_default(&mod); mod.cls = MODULE_CLASS_DECODER; mod.priv_data = this; mod.new_message = decoder_process_message; module_register(&mod, parent); - control = (struct control_state *) get_module( - get_root_module(parent), "control") - ->priv_data; } ~state_video_decoder() { module_done(&mod); } struct module mod; - struct control_state *control = {}; + struct control_state *control; thread decompress_thread_id, fec_thread_id; @@ -1238,6 +1237,7 @@ static bool reconfigure_decoder(struct state_video_decoder *decoder, struct video_desc display_desc = desc; int display_mode = DISPLAY_PROPERTY_VIDEO_MERGED; // default + enum video_mode display_video_mode = decoder->video_mode; size_t len = sizeof(int); if (!display_ctl_property(decoder->display, DISPLAY_PROPERTY_VIDEO_MODE, @@ -1254,6 +1254,7 @@ static bool reconfigure_decoder(struct state_video_decoder *decoder, display_desc.width *= get_video_mode_tiles_x(decoder->video_mode); display_desc.height *= get_video_mode_tiles_y(decoder->video_mode); display_desc.tile_count = 1; + display_video_mode = VIDEO_NORMAL; } decoder->change_il = select_il_func(desc.interlacing, decoder->disp_supported_il, @@ -1263,7 +1264,8 @@ static bool reconfigure_decoder(struct state_video_decoder *decoder, display_desc.color_spec = out_codec; if (out_codec != VIDEO_CODEC_END && !video_desc_eq(decoder->display_desc, display_desc)) { /* reconfigure VO and give it opportunity to pass us pitch */ - bool ret = display_reconfigure(decoder->display, display_desc, decoder->video_mode); + bool ret = display_reconfigure(decoder->display, display_desc, + display_video_mode); if(!ret) { return false; } diff --git a/src/rtsp/BasicRTSPOnlyServer.cpp b/src/rtsp/BasicRTSPOnlyServer.cpp index 6190bc11ca..57e04a7eec 100644 --- a/src/rtsp/BasicRTSPOnlyServer.cpp +++ b/src/rtsp/BasicRTSPOnlyServer.cpp @@ -4,6 +4,7 @@ * Gerard Castillo * * Copyright (c) 2005-2010 Fundació i2CAT, Internet I Innovació Digital a Catalunya + * Copyright (c) 2014-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -143,9 +144,9 @@ BasicRTSPOnlyServer::init_server() } void *BasicRTSPOnlyServer::start_server(void *args){ - char* watch = (char*) args; - BasicRTSPOnlyServer* instance = getInstance(); - + auto *watch = (EventLoopWatchVariable *) args; + BasicRTSPOnlyServer *instance = getInstance(); + if (instance == NULL || instance->env == NULL || instance->rtspServer == NULL){ return NULL; } diff --git a/src/rtsp/BasicRTSPOnlyServer.hh b/src/rtsp/BasicRTSPOnlyServer.hh index a1bef9debc..4d15a0784b 100644 --- a/src/rtsp/BasicRTSPOnlyServer.hh +++ b/src/rtsp/BasicRTSPOnlyServer.hh @@ -4,6 +4,7 @@ * Gerard Castillo * * Copyright (c) 2005-2010 Fundació i2CAT, Internet I Innovació Digital a Catalunya + * Copyright (c) 2019-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -50,6 +51,11 @@ #include "c_basicRTSPOnlyServer.h" // for rtsp_server_parameters +// compat +#if BASICUSAGEENVIRONMENT_LIBRARY_VERSION_INT < 1752883200 +typedef char volatile EventLoopWatchVariable; +#endif + class BasicRTSPOnlyServer { private: BasicRTSPOnlyServer(struct rtsp_server_parameters params); diff --git a/src/rtsp/c_basicRTSPOnlyServer.cpp b/src/rtsp/c_basicRTSPOnlyServer.cpp index d81b928858..1311e8f0ec 100644 --- a/src/rtsp/c_basicRTSPOnlyServer.cpp +++ b/src/rtsp/c_basicRTSPOnlyServer.cpp @@ -4,6 +4,7 @@ * Gerard Castillo * * Copyright (c) 2005-2010 Fundació i2CAT, Internet I Innovació Digital a Catalunya + * Copyright (c) 2014-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -47,6 +48,12 @@ #include "rtsp/c_basicRTSPOnlyServer.h" #include "rtsp/BasicRTSPOnlyServer.hh" +struct rtsp_serv { + struct rtsp_server_parameters params; + pthread_t server_th; + EventLoopWatchVariable watch; +}; + rtsp_serv_t * c_start_server(struct rtsp_server_parameters params) { @@ -54,10 +61,11 @@ c_start_server(struct rtsp_server_parameters params) server->params = params; server->watch = 0; - int ret; BasicRTSPOnlyServer *srv = BasicRTSPOnlyServer::initInstance(server->params); srv->init_server(); - ret = pthread_create(&server->server_th, NULL, BasicRTSPOnlyServer::start_server, &server->watch); + int ret = pthread_create(&server->server_th, NULL, + BasicRTSPOnlyServer::start_server, + (void *) &server->watch); assert(ret == 0); return server; diff --git a/src/rtsp/c_basicRTSPOnlyServer.h b/src/rtsp/c_basicRTSPOnlyServer.h index 71cba1f288..e777bef4d6 100644 --- a/src/rtsp/c_basicRTSPOnlyServer.h +++ b/src/rtsp/c_basicRTSPOnlyServer.h @@ -4,6 +4,7 @@ * Gerard Castillo * * Copyright (c) 2005-2010 Fundació i2CAT, Internet I Innovació Digital a Catalunya + * Copyright (c) 2014-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -74,11 +75,8 @@ struct rtsp_server_parameters { codec_t video_codec; }; -EXTERNC typedef struct rtsp_serv { - struct rtsp_server_parameters params; - pthread_t server_th; - uint8_t watch; -} rtsp_serv_t; +struct rtsp_serv; +typedef struct rtsp_serv rtsp_serv_t; EXTERNC rtsp_serv_t *c_start_server(struct rtsp_server_parameters params); diff --git a/src/transmit.cpp b/src/transmit.cpp index 323519d054..ac607a835e 100644 --- a/src/transmit.cpp +++ b/src/transmit.cpp @@ -263,11 +263,7 @@ struct tx *tx_init(struct module *parent, unsigned mtu, enum tx_media_type media } tx->bitrate = bitrate; - - if (parent) { - tx->control = (struct control_state *) get_module( - get_root_module(parent), "control")->priv_data; - } + tx->control = get_control_state(parent); return tx; } diff --git a/src/types.h b/src/types.h index d879b38fa1..42b3df5394 100644 --- a/src/types.h +++ b/src/types.h @@ -62,9 +62,9 @@ extern "C" { typedef enum { VIDEO_CODEC_NONE = 0, ///< dummy color spec VC_NONE = VIDEO_CODEC_NONE, // shortcut - VIDEO_CODEC_FIRST, - VC_FIRST = VIDEO_CODEC_FIRST, - RGBA = VIDEO_CODEC_FIRST, ///< RGBA 8-bit, big-endian + RGBA, ///< RGBA 8-bit, big-endian + VIDEO_CODEC_FIRST = RGBA, + VC_FIRST = VIDEO_CODEC_FIRST, UYVY, ///< YCbCr 422 8-bit - Cb Y0 Cr Y1 YUYV, ///< YCbCr 422 8-bit - Y0 Cb Y1 Cr VUYA, ///< YCbCr 444 8-bit - Cr Cb Y0 Al diff --git a/src/utils/color_out.cpp b/src/utils/color_out.cpp index 51b0f48237..17f52dc0ae 100644 --- a/src/utils/color_out.cpp +++ b/src/utils/color_out.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2018-2024 CESNET + * Copyright (c) 2018-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,7 @@ #include // for getenv #include // for strcmp, strlen #include // for back_insert_iterator, back_inserter +#include // for unique_ptr #include // for isatty #include "debug.h" diff --git a/src/utils/fs.c b/src/utils/fs.c index 3c9115e5d4..4766fbd731 100644 --- a/src/utils/fs.c +++ b/src/utils/fs.c @@ -40,16 +40,36 @@ #include "config.h" #include "config_unix.h" #include "config_win32.h" +#else +#define SRCDIR ".." #endif #include #include #include #include +#include +#include "debug.h" #include "utils/fs.h" +#include "utils/macros.h" #include "utils/string.h" +// for get_exec_path +#ifdef __APPLE__ +#include //_NSGetExecutablePath +#include +#elif defined __FreeBSD__ +#include +#include +#elif !defined(_WIN32) && !defined(__linux__) && !defined(__DragonFly__) && \ + !defined(__NetBSD__) +#include // for getcwd +#include "host.h" // for uv_argv +#endif + +#define MOD_NAME "[fs] " + /** * Returns temporary path ending with path delimiter ('/' or '\' in Windows) */ @@ -79,28 +99,22 @@ const char *get_temp_dir(void) return temp_dir; } -// see also +/** + * see also + * @param path buffer with size MAX_PATH_SIZE where function stores path to executable + */ +static bool +get_exec_path(char *path) +{ #ifdef _WIN32 -int get_exec_path(char* path) { return GetModuleFileNameA(NULL, path, MAX_PATH_SIZE) != 0; -} #elif defined __linux__ -int get_exec_path(char* path) { return realpath("/proc/self/exe", path) != NULL; -} #elif defined __DragonFly__ -int get_exec_path(char* path) { return realpath("/proc/curproc/file", path) != NULL; -} #elif defined __NetBSD__ -int get_exec_path(char* path) { return realpath("/proc/curproc/exe", path) != NULL; -} #elif defined __APPLE__ -#include //_NSGetExecutablePath -#include - -int get_exec_path(char* path) { char raw_path_name[MAX_PATH_SIZE]; uint32_t raw_path_size = (uint32_t)(sizeof(raw_path_name)); @@ -108,64 +122,58 @@ int get_exec_path(char* path) { return false; } return realpath(raw_path_name, path) != NULL; -} #elif defined __FreeBSD__ -#include -#include -int get_exec_path(char* path) { int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; size_t cb = MAX_PATH_SIZE; - return sysctl(mib, sizeof mib / sizeof mib[0], path, &cb, NULL, 0); -} + return sysctl(mib, sizeof mib / sizeof mib[0], path, &cb, NULL, 0) == 0; #else -#include // for getcwd -#include "host.h" // for uv_argv -int get_exec_path(char* path) { if (uv_argv[0][0] == '/') { // with absolute path if (snprintf(path, MAX_PATH_SIZE, "%s", uv_argv[0]) == MAX_PATH_SIZE) { - return -1; // truncated + return false; // truncated } - return 0; + return true; } if (strchr(uv_argv[0], '/') != NULL) { // or with relative path char cwd[MAX_PATH_SIZE]; if (getcwd(cwd, sizeof cwd) != cwd) { - return -1; + return false; } if (snprintf(path, MAX_PATH_SIZE, "%s/%s", cwd, uv_argv[0]) == MAX_PATH_SIZE) { - return -1; // truncated + return false; // truncated } - return 0; + return true; } // else launched from PATH char args[1024]; snprintf(args, sizeof args, "command -v %s", uv_argv[0]); FILE *f = popen(args, "r"); if (f == NULL) { - return -1; + return false; } if (fgets(path, MAX_PATH_SIZE, f) == NULL) { fclose(f); - return -1; + return false; } fclose(f); if (strlen(path) == 0 || path[strlen(path) - 1] != '\n') { - return -1; // truncated (?) + return false; // truncated (?) } path[strlen(path) - 1] = '\0'; - return 0; + return true; +} +#endif } -#endif /** * @returns installation root without trailing '/', eg. installation prefix on * Linux - default "/usr/local", Windows - top-level directory extracted * UltraGrid directory */ -const char *get_install_root(void) { - static __thread char exec_path[MAX_PATH_SIZE]; +static bool +get_install_root(char exec_path[static MAX_PATH_SIZE]) +{ if (!get_exec_path(exec_path)) { return NULL; } @@ -176,12 +184,61 @@ const char *get_install_root(void) { *last_path_delim = '\0'; // cut off executable name last_path_delim = strrpbrk(exec_path, "/\\"); if (!last_path_delim) { - return exec_path; + return true; } if (strcmp(last_path_delim + 1, "bin") == 0 || strcmp(last_path_delim + 1, "MacOS") == 0) { *last_path_delim = '\0'; // remove "bin" suffix if there is one (not in Windows builds) or MacOS in a bundle } - return exec_path; + return true; +} + +static bool +dir_exists(const char *path) +{ + struct stat sb; + if (stat(path, &sb) == -1) { + return false; + } + return S_ISDIR(sb.st_mode); +} + +/** + * returns path with UltraGrid data (path to `share/ultragrid` if run from + * source, otherwise the corresponding path in system if installed or in + * bundle/appimage/extracted dir) + * @retval the path; NULL if not found + */ +const char * +get_data_path() +{ + static __thread char path[MAX_PATH_SIZE]; + if (strlen(path) > 0) { // already set + return path; + } + + const char suffix[] = "/share/ultragrid"; + + if (get_install_root(path)) { + size_t len = sizeof path - strlen(path); + if ((size_t) snprintf(path + strlen(path), len, + suffix) >= len) { + abort(); // path truncated + } + if (dir_exists(path)) { + MSG(VERBOSE, "Using data path %s\n", path); + return path; + } + } + + snprintf_ch(path, SRCDIR "%s", suffix); + if (dir_exists(path)) { + MSG(VERBOSE, "Using data path %s\n", path); + return path; + } + + MSG(WARNING, "No data path could have been found!\n"); + path[0] = '\0'; // avoid quick cached return at start + return NULL; } /** diff --git a/src/utils/fs.h b/src/utils/fs.h index c31c8ddd30..2d8fa1898d 100644 --- a/src/utils/fs.h +++ b/src/utils/fs.h @@ -4,7 +4,7 @@ * @author Martin Bela <492789@mail.muni.cz> */ /* - * Copyright (c) 2018-2023 CESNET, z. s. p. o. + * Copyright (c) 2018-2025 CESNET, zájmové sdružení právnických osob * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,15 +59,10 @@ extern "C" { #include #endif -/** - * @param path buffer with size MAX_PATH_SIZE where function stores path to executable - * @return 1 - SUCCESS, 0 - ERROR - */ -int get_exec_path(char* path); const char *get_temp_dir(void); FILE *get_temp_file(const char **filename); -const char *get_install_root(void); - char *strdup_path_with_expansion(const char *orig_path); +const char *get_data_path(void); +char *strdup_path_with_expansion(const char *orig_path); #ifdef __cplusplus } // extern "C" diff --git a/src/utils/jpeg_reader.c b/src/utils/jpeg_reader.c index 999a88cc27..92e51ff9df 100644 --- a/src/utils/jpeg_reader.c +++ b/src/utils/jpeg_reader.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2018-2024 CESNET, z. s. p. o. + * Copyright (c) 2018-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -289,7 +289,7 @@ color_space_get_name(enum jpeg_color_spec color_space) case JPEG_COLOR_SPEC_RGB: return "RGB"; case JPEG_COLOR_SPEC_YCBCR_601: - return "YCbCr BT.601 (limtted range)"; + return "YCbCr BT.601 (limitted range)"; case JPEG_COLOR_SPEC_YCBCR_JPEG: return "YCbCr BT.601 256 Levels (YCbCr JPEG)"; case JPEG_COLOR_SPEC_YCBCR_709: diff --git a/src/utils/macros.h b/src/utils/macros.h index 007ab958ee..d096d98d71 100644 --- a/src/utils/macros.h +++ b/src/utils/macros.h @@ -107,7 +107,7 @@ do { /* NOLINT(cppcoreguidelines-avoid-do-while) */ \ if (snprintf(str, sizeof str, __VA_ARGS__) >= \ (int) sizeof str) { \ - fprintf(stderr, \ + (void) fprintf(stderr, \ "\n%s:%d: %s: snprintf truncates %s (%d B " \ "needed)!\n\n", \ __FILE__, __LINE__, __func__, #str, \ diff --git a/src/utils/opencl.c b/src/utils/opencl.c index 32d43468fa..7d97859bf6 100644 --- a/src/utils/opencl.c +++ b/src/utils/opencl.c @@ -50,6 +50,9 @@ ADD_TO_PARAM(PARAM_NAME, PARAM_HELP); #ifdef HAVE_OPENCL +#ifndef CL_TARGET_OPENCL_VERSION +#define CL_TARGET_OPENCL_VERSION 120 // for CL_DEVICE_TYPE_CUSTOM +#endif #include #include #include diff --git a/src/utils/packet_counter.cpp b/src/utils/packet_counter.cpp index e47e83e7c6..584051b2b6 100644 --- a/src/utils/packet_counter.cpp +++ b/src/utils/packet_counter.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2012-2023 CESNET, z. s. p. o. + * Copyright (c) 2012-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,15 +35,11 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif // defined HAVE_CONFIG_H - #include "utils/packet_counter.h" -#include -#include + +#include // for map, _Rb_tree_iterator +#include // for pair +#include // for vector using std::map; using std::vector; diff --git a/src/utils/pam.c b/src/utils/pam.c index 1845720f61..a5d602b7de 100644 --- a/src/utils/pam.c +++ b/src/utils/pam.c @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -227,7 +228,10 @@ bool pam_read(const char *filename, struct pam_metadata *info, unsigned char **d return true; } -bool pam_write(const char *filename, unsigned int width, unsigned int height, int ch_count, int maxval, const unsigned char *data, bool pnm) { +bool pam_write(const char *filename, unsigned int width, unsigned int pitch, + unsigned int height, int ch_count, int maxval, + const unsigned char *data, bool pnm) +{ errno = 0; #ifdef _WIN32 FILE *file = _wfopen(mbs_to_wstr(filename), L"wb"); @@ -267,9 +271,19 @@ bool pam_write(const char *filename, unsigned int width, unsigned int height, in "ENDHDR\n", width, height, ch_count, maxval, tuple_type); } - size_t len = (size_t) width * height * ch_count * (maxval <= 255 ? 1 : 2); + const size_t linesize = (size_t) height * ch_count * (maxval <= 255 ? 1 : 2); + const size_t len = width * linesize; errno = 0; - size_t bytes_written = fwrite((const char *) data, 1, len, file); + size_t bytes_written = 0; + if (pitch == PAM_PITCH_CONTINUOUS || width == pitch) { + bytes_written = fwrite((const char *) data, 1, len, file); + } else { + for (unsigned y = 0; y < height; ++y) { + bytes_written += fwrite((const char *)data + + ((size_t)y * pitch), + 1, linesize, file); + } + } if (bytes_written != len) { fprintf(stderr, "Unable to write PAM/PNM data - length %zd, written %zd: %s\n", len, bytes_written, strerror(errno)); diff --git a/src/utils/pam.h b/src/utils/pam.h index 351e3c00ef..0021ca7ea2 100644 --- a/src/utils/pam.h +++ b/src/utils/pam.h @@ -49,18 +49,53 @@ extern "C" { #endif +/// metadata read from file struct pam_metadata { - int width; - int height; - int ch_count; - int maxval; - bool bitmap_pbm; // bitmap data is stored in PBM format (1 bit per pixel, line aligned to whole byte, 1 is black /"ink on"/), - // otherwise 1 byte per pixel, 1 is white "light on"); if .depth != 1 || .maxval != 1, this value is undefined + int width; ///< image width + int height; ///< image height + int ch_count; ///< number of channels + int maxval; ///< sample maximal value (typically but not necessarily + ///< 255) + bool bitmap_pbm; ///< bitmap data is stored in PBM format (1 bit per + ///< pixel, line aligned to whole byte, 1 is black + ///< /"ink on"/), otherwise 1 byte per pixel, 1 is + ///< white "light on"); if .depth != 1 || .maxval != 1, + ///< this value is undefined }; +/** + * read PAM/PNM file + * + * @param filename file name + * @param[out] info pointer to metadata struct + * @param[out] data pointer to byte array, can be 0, in which case no data + * are written (only metadata read ) + * @param[out] allocaltor allocator to alloc @ref data; if 0, no data are + * read/allocated, only @ref info set + */ bool pam_read(const char *filename, struct pam_metadata *info, unsigned char **data, void *(*allocator)(size_t)); -bool pam_write(const char *filename, unsigned int width, unsigned int height, - int ch_count, int maxval, const unsigned char *data, bool pnm); + +enum { + PAM_PITCH_CONTINUOUS = 0, +}; + +/** + * write PAM or PNM file + * + * @param filename file name to be written to + * @param width image width + * @param pitch input line pitch in bytes; PAM_PITCH_CONTINUOUS can be used + * if input pitch == width * ch_count * (maxval <= 255 ? 1 : 2) + * @param height image height + * @param ch_count image channel count (1-4 for output PAM, 1 or 3 for PNM, see + * @ref pnm) + * @param maxval maximal sample value, typically 255 for 8-bit + * @param data bytes to be written + * @param pnm use PNM file (instead of PAM) + */ +bool pam_write(const char *filename, unsigned int width, unsigned int pitch, + unsigned int height, int ch_count, int maxval, + const unsigned char *data, bool pnm); #ifdef __cplusplus } // extern "C" diff --git a/src/utils/string.c b/src/utils/string.c index 49f460d599..3afb5ea082 100644 --- a/src/utils/string.c +++ b/src/utils/string.c @@ -49,6 +49,7 @@ #include "compat/strings.h" #include "debug.h" +#include "utils/macros.h" // for MIN #include "utils/string.h" /** @@ -154,6 +155,21 @@ void write_all(int fd, size_t len, const char *msg) { } while (len > 0); } +void +append_number(char **ptr, const char *ptr_end, uintmax_t num) +{ + char num_buf[100]; + int idx = sizeof num_buf; + num_buf[--idx] = '0' + num % 10; // write '0' if num=0 + while ((num /= 10) != 0) { + num_buf[--idx] = '0' + num % 10; + } + const size_t buflen = ptr_end - *ptr; + const size_t len = MIN(buflen, sizeof num_buf - idx); + strncpy(*ptr, num_buf + idx, len); + *ptr += len; +} + /** * Appends signal number description to ptr and moves ptr to end of the * appended string. The string is not NULL-terminated. diff --git a/src/utils/string.h b/src/utils/string.h index edf45eeccd..26bfff5bfb 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2014-2024 CESNET z.s.p.o. + * Copyright (c) 2014-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,10 +40,12 @@ #ifdef __cplusplus #include +#include // for uintmax_t extern "C" { #else #include #include +#include // for uintmax_t #endif // functions documented at definition @@ -55,6 +57,7 @@ bool is_prefix_of(const char *haystack, const char *needle); /// same as strpbrk but finds in a reverse order (last occurrence returned) char *strrpbrk(char *s, const char *accept); void strappend(char **ptr, const char *ptr_end, const char *src); +void append_number(char **ptr, const char *ptr_end, uintmax_t num); void append_sig_desc(char **ptr, const char *ptr_end, int signum); void write_all(int fd, size_t len, const char *msg); const char *pretty_print_fourcc(const void *fcc); diff --git a/src/utils/vf_split.cpp b/src/utils/vf_split.cpp index d85729b904..7bf0809075 100644 --- a/src/utils/vf_split.cpp +++ b/src/utils/vf_split.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2021 CESNET z.s.p.o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,18 +35,17 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "utils/vf_split.h" -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif // HAVE_CONFIG_H +#include // for assert +#include // for int64_t, INT64_MAX +#include // for malloc +#include // for memcpy +#include // for decay +#include "types.h" // for tile, video_frame, video_desc +#include "video_codec.h" // for get_bpp, vc_get_linesize +#include "video_frame.h" // for vf_get_tile, vf_alloc_desc, vf_copy_metadata -#include -#include -#include "utils/vf_split.h" -#include "video.h" -#include "video_codec.h" void vf_split(struct video_frame *out, struct video_frame *src, unsigned int x_count, unsigned int y_count, int preallocate) diff --git a/src/utils/wait_obj.cpp b/src/utils/wait_obj.cpp index f2b309513f..f9802a7532 100644 --- a/src/utils/wait_obj.cpp +++ b/src/utils/wait_obj.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013 CESNET z.s.p.o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,10 +35,6 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" - #include "utils/wait_obj.h" struct wait_obj *wait_obj_init() diff --git a/src/utils/windows.c b/src/utils/windows.c index 52cc1def77..04a4895691 100644 --- a/src/utils/windows.c +++ b/src/utils/windows.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2019-2023 CESNET + * Copyright (c) 2019-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -312,10 +312,29 @@ print_stacktrace_win() char backtrace_msg[] = "Backtrace:\n"; _write(STDERR_FILENO, backtrace_msg, strlen(backtrace_msg)); for (unsigned short i = 0; i < frames; i++) { - SymFromAddr(process, (DWORD64) (stack[i]), 0, symbol); + BOOL ret = SymFromAddr(process, (DWORD64) (stack[i]), 0, symbol); char buf[STR_LEN]; - snprintf_ch(buf, "%i (%p): %s - 0x%0llX\n", frames - i - 1, - stack[i], symbol->Name, symbol->Address); + snprintf_ch(buf, "%i (%p): ", frames - i - 1, stack[i]); + if (ret == TRUE) { + snprintf(buf + strlen(buf), sizeof buf - strlen(buf), + "%s - 0x%0llX\n", symbol->Name, + symbol->Address); + + IMAGEHLP_LINE64 line = { .SizeOfStruct = sizeof line }; + DWORD displacementLine = 0; + if (SymGetLineFromAddr64(process, (DWORD64) (stack[i]), + &displacementLine, &line)) { + snprintf(buf + strlen(buf), + sizeof buf - strlen(buf), + "\tFile: %s, line: %lu, displacement: " + "%lu\n", + line.FileName, line.LineNumber, + displacementLine); + } + } else { + snprintf(buf + strlen(buf), sizeof buf - strlen(buf), + "(cannot resolve)\n"); + } _write(STDERR_FILENO, buf, strlen(buf)); } diff --git a/src/video_capture/null.c b/src/video_capture/null.c index 78cc7155d8..a9ef373aa1 100644 --- a/src/video_capture/null.c +++ b/src/video_capture/null.c @@ -6,7 +6,7 @@ * hardware or do not wish to transmit. This fits the interface of the other * capture devices, but never produces any video. * - * Copyright (c) 2005-2023 CESNET + * Copyright (c) 2005-2025 CESNET * Copyright (c) 2004 University of Glasgow * Copyright (c) 2003 University of Southern California * @@ -49,13 +49,16 @@ * */ -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#include "debug.h" -#include "lib_common.h" -#include "video.h" -#include "video_capture.h" +#include // for assert +#include // for NULL, free + +#include "lib_common.h" // for REGISTER_HIDDEN_MODULE, library_class +#include "video_capture.h" // for VIDCAP_INIT_AUDIO_NOT_SUPPORTED +#include "video_capture_params.h" // for vidcap_params_get_flags, VIDCAP_FL... + +struct audio_frame; +struct device_info; +struct vidcap_params; static int capture_state = 0; diff --git a/src/video_capture/screen_linux.c b/src/video_capture/screen_linux.c index 36a2b74cbb..b306c1b93d 100644 --- a/src/video_capture/screen_linux.c +++ b/src/video_capture/screen_linux.c @@ -5,7 +5,7 @@ * X11/PipeWire screen capture abstraction */ /* - * Copyright (c) 2023 CESNET + * Copyright (c) 2023-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,18 +36,23 @@ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" +#include // for printf #include #include +#include "config.h" // for HAVE_* #include "debug.h" #include "lib_common.h" #include "utils/color_out.h" #include "utils/text.h" #include "video_capture.h" +#include "video_capture_params.h" // for vidcap_params_free_struct, vidcap_... + +struct audio_frame; +struct device_info; +struct vidcap_params; + static void vidcap_screen_linux_probe(struct device_info **cards, int *count, void (**deleter)(void *)) { diff --git a/src/video_capture/screen_osx.c b/src/video_capture/screen_osx.c index da44e93052..a6f50e5459 100644 --- a/src/video_capture/screen_osx.c +++ b/src/video_capture/screen_osx.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2012-2023 CESNET, z.s.p.o. + * Copyright (c) 2012-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,30 +35,29 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "debug.h" -#include "host.h" #include "lib_common.h" +#include "pixfmt_conv.h" +#include "tv.h" +#include "types.h" #include "utils/video_frame_pool.h" -#include "video.h" #include "video_capture.h" - -#include "tv.h" - -#include "audio/types.h" - -#include -#include -#include - -#include - -#include +#include "video_capture_params.h" +#include "video_codec.h" +#include "video_frame.h" +struct audio_frame; +struct vidcap_params; #define MAX_DISPLAY_COUNT 10 #define MOD_NAME "[screen cap mac] " diff --git a/src/video_capture/screen_win.c b/src/video_capture/screen_win.c index 3bff6d09b1..3a0f2ab303 100644 --- a/src/video_capture/screen_win.c +++ b/src/video_capture/screen_win.c @@ -9,7 +9,7 @@ * - load the dll even if working directory is not the dir with the DLL */ /* - * Copyright (c) 2019-2023 CESNET, z.s.p.o. + * Copyright (c) 2019-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,28 +41,26 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif /* HAVE_CONFIG_H */ - -#include -#include -#include -#include - -#include "audio/types.h" -#include "debug.h" -#include "host.h" -#include "lib_common.h" -#include "utils/color_out.h" -#include "utils/macros.h" -#include "utils/text.h" -#include "utils/windows.h" -#include "video.h" -#include "video_capture.h" -#include "video_capture_params.h" +#define WIN32_LEAN_AND_MEAN +#include // for INT_PTR + +#include // for assert +#include // for ShellExecuteA, ShellExecute +#include // for bool, false, true +#include // for NULL, snprintf +#include // for free, calloc, strtol +#include // for strchr, strcmp, strlen, strstr + +#include "debug.h" // for log_msg, LOG_LEVEL_ERROR, MSG, LOG... +#include "host.h" // for uv_argv, uv_argc +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "types.h" // for device_info +#include "utils/color_out.h" // for color_printf, TBOLD, TRED +#include "utils/macros.h" // for IF_NOT_NULL_ELSE, IS_KEY_PREFIX +#include "utils/text.h" // for wrap_paragraph +#include "utils/windows.h" // for get_win32_error, hresult_to_str +#include "video_capture.h" // for VIDCAP_INIT_FAIL, video_capture_info +#include "video_capture_params.h" // for vidcap_params_allocate, vidcap_par... #define MOD_NAME "[screen win] " #define FILTER_UPSTREAM_URL "https://github.com/rdp/screen-capture-recorder-to-video-windows-free/releases" @@ -387,7 +385,7 @@ static int register_screen_cap_rec_library(bool is_elevated) { if ((INT_PTR) ret > 32) { log_msg(LOG_LEVEL_NOTICE, MOD_NAME "Module installation successful.\n"); log_msg(LOG_LEVEL_NOTICE, MOD_NAME "If you want to unregister the module, run 'uv -t screen:unregister'.\n"); - sleep(2); + Sleep(2000); // 2 s return 1; } } diff --git a/src/video_capture/v4l2.c b/src/video_capture/v4l2.c index f5db365f27..89cc944d69 100644 --- a/src/video_capture/v4l2.c +++ b/src/video_capture/v4l2.c @@ -5,7 +5,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2012-2024 CESNET, z. s. p. o. + * Copyright (c) 2012-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ */ #ifdef HAVE_CONFIG_H -#include "config.h" +#include "config.h" // for HAVE_LIBV4LCONVERT #endif /* HAVE_CONFIG_H */ #ifdef HAVE_LIBV4LCONVERT @@ -55,11 +55,13 @@ #include #include #include +#include // for bool, false, true #include #include #include #include #include +#include // for gettimeofday, timeval #include enum { @@ -69,16 +71,21 @@ enum { #define MOD_NAME "[V4L cap.] " #include "debug.h" -#include "host.h" #include "lib_common.h" #include "tv.h" +#include "types.h" // for device_info, video_desc, tile, vid... #include "utils/color_out.h" #include "utils/list.h" #include "utils/macros.h" #include "utils/misc.h" // ug_strerror #include "v4l2_common.h" -#include "video.h" #include "video_capture.h" +#include "video_capture_params.h" // for vidcap_params_get_fmt, vidcap_para... +#include "video_codec.h" // for codec_is_planar, get_codec_name +#include "video_frame.h" // for get_interlacing_suffix, vf_alloc_desc + +struct audio_frame; +struct vidcap_params; /* prototypes of functions defined in this module */ static void print_fps(int fd, struct v4l2_frmivalenum *param); diff --git a/src/video_capture/ximea.c b/src/video_capture/ximea.c index 378bca0760..063f80c613 100644 --- a/src/video_capture/ximea.c +++ b/src/video_capture/ximea.c @@ -36,7 +36,7 @@ */ #ifdef HAVE_CONFIG_H -#include "config.h" +#include "config.h" // for XIMEA_RUNTIME_LINKING #endif // HAVE_CONFIG_H #include // for assert diff --git a/src/video_codec.c b/src/video_codec.c index ba096d2956..d3646d78e0 100644 --- a/src/video_codec.c +++ b/src/video_codec.c @@ -1130,7 +1130,8 @@ i444_8_to_uyvy(int width, int height, const unsigned char *in, struct pixfmt_desc get_pixfmt_desc(codec_t pixfmt) { - assert(pixfmt >= VIDEO_CODEC_FIRST && pixfmt < VIDEO_CODEC_END); + assert(pixfmt >= VIDEO_CODEC_FIRST); + assert(pixfmt <= VIDEO_CODEC_END); struct pixfmt_desc ret = { 0 }; ret.depth = codec_info[pixfmt].bits_per_channel; ret.subsampling = codec_info[pixfmt].subsampling; diff --git a/src/video_compress.cpp b/src/video_compress.cpp index 0471b79657..fb1a687c00 100644 --- a/src/video_compress.cpp +++ b/src/video_compress.cpp @@ -434,7 +434,6 @@ static shared_ptr compress_frame_tiles(struct compress_state *proxy shared_ptr frame) { struct compress_state_real *s = proxy->ptr; - const int tile_cnt = (int) proxy->ptr->state.size(); vector> separate_tiles; if (frame) { if (!check_state_count(frame->tile_count, proxy)) { @@ -442,12 +441,13 @@ static shared_ptr compress_frame_tiles(struct compress_state *proxy } separate_tiles = vf_separate_tiles(frame); } else { - separate_tiles.resize(tile_cnt); + separate_tiles.resize(proxy->ptr->state.size()); } // frame pointer may no longer be valid frame = NULL; + const int tile_cnt = (int) proxy->ptr->state.size(); vector task_handle(tile_cnt); vector data_tile(tile_cnt); @@ -463,7 +463,7 @@ static shared_ptr compress_frame_tiles(struct compress_state *proxy vector> compressed_tiles(separate_tiles.size()); bool failed = false; - for(unsigned int i = 0; i < separate_tiles.size(); ++i) { + for (int i = 0; i < tile_cnt; ++i) { struct compress_worker_data *data = (struct compress_worker_data *) wait_task(task_handle[i]); diff --git a/src/video_compress/cuda_dxt.cpp b/src/video_compress/cuda_dxt.cpp index 55b1b817c8..4aafcd9b5f 100644 --- a/src/video_compress/cuda_dxt.cpp +++ b/src/video_compress/cuda_dxt.cpp @@ -35,7 +35,7 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include // for printf, fprintf, stderr +#include // for printf, stderr #include // for free, malloc #include // for shared_ptr @@ -52,6 +52,15 @@ #include "video_compress.h" #include "video_frame.h" // for vf_get_tile, video_desc_from_f... +#define MOD_NAME "[CUDA DXT] " + +#define CHECK_CUDA(cmd, msg, action) \ + if ((cmd) != CUDA_WRAPPER_SUCCESS) { \ + MSG(ERROR, "%s: %s\n", msg, cuda_wrapper_last_error_string()); \ + action; \ + } + + using namespace std; namespace { @@ -144,25 +153,22 @@ static bool configure_with(struct state_video_compress_cuda_dxt *s, struct video codec_t supported_codecs[] = { RGB, UYVY, VIDEO_CODEC_NONE }; s->decoder = get_best_decoder_from(desc.color_spec, supported_codecs, &s->in_codec); if (!s->decoder) { - fprintf(stderr, "Unsupported codec: %s\n", get_codec_name(desc.color_spec)); + MSG(ERROR, "Unsupported codec: %s\n", + get_codec_name(desc.color_spec)); return false; } if (s->in_codec == UYVY) { - if (CUDA_WRAPPER_SUCCESS != cuda_wrapper_malloc((void **) &s->cuda_uyvy_buffer, - desc.width * desc.height * 2)) { - fprintf(stderr, "Could not allocate CUDA UYVY buffer.\n"); - return false; - } + CHECK_CUDA(cuda_wrapper_malloc((void **) &s->cuda_uyvy_buffer, + desc.width * desc.height * 2), + "Could not allocate CUDA UYVY buffer", return false); } s->in_buffer = (char *) malloc(desc.width * desc.height * 3); - if (CUDA_WRAPPER_SUCCESS != cuda_wrapper_malloc((void **) &s->cuda_in_buffer, - desc.width * desc.height * 3)) { - fprintf(stderr, "Could not allocate CUDA output buffer.\n"); - return false; - } + CHECK_CUDA(cuda_wrapper_malloc((void **) &s->cuda_in_buffer, + desc.width * desc.height * 3), + "Could not allocate CUDA output buffer", return false); struct video_desc compressed_desc = desc; compressed_desc.color_spec = s->out_codec; @@ -171,12 +177,8 @@ static bool configure_with(struct state_video_compress_cuda_dxt *s, struct video s->pool.reconfigure(compressed_desc, data_len); - if (CUDA_WRAPPER_SUCCESS != cuda_wrapper_malloc((void **) - &s->cuda_out_buffer, - data_len)) { - fprintf(stderr, "Could not allocate CUDA output buffer.\n"); - return false; - } + CHECK_CUDA(cuda_wrapper_malloc((void **) &s->cuda_out_buffer, data_len), + "Could not allocate CUDA output buffer", return false); return true; } @@ -196,7 +198,7 @@ shared_ptr cuda_dxt_compress_tile(void *state, shared_ptrsaved_desc = video_desc_from_frame(tx.get()); } else { - fprintf(stderr, "[CUDA DXT] Reconfiguration failed!\n"); + MSG(ERROR, "Reconfiguration failed!\n"); return NULL; } } @@ -218,24 +220,22 @@ shared_ptr cuda_dxt_compress_tile(void *state, shared_ptrin_codec == UYVY) { - if (cuda_wrapper_memcpy(s->cuda_uyvy_buffer, in_buffer, tx->tiles[0].width * - tx->tiles[0].height * 2, - CUDA_WRAPPER_MEMCPY_HOST_TO_DEVICE) != CUDA_WRAPPER_SUCCESS) { - fprintf(stderr, "Memcpy failed: %s\n", cuda_wrapper_last_error_string()); - return NULL; - } - if (cuda_yuv422_to_yuv444(s->cuda_uyvy_buffer, s->cuda_in_buffer, - tx->tiles[0].width * - tx->tiles[0].height, 0) != CUDA_WRAPPER_SUCCESS) { - fprintf(stderr, "Kernel failed: %s\n", cuda_wrapper_last_error_string()); - } + CHECK_CUDA(cuda_wrapper_memcpy( + s->cuda_uyvy_buffer, in_buffer, + tx->tiles[0].width * tx->tiles[0].height * 2, + CUDA_WRAPPER_MEMCPY_HOST_TO_DEVICE), + "Memcpy failed", return nullptr); + + CHECK_CUDA(cuda_yuv422_to_yuv444( + s->cuda_uyvy_buffer, s->cuda_in_buffer, + tx->tiles[0].width * tx->tiles[0].height, 0), + "Kernel failed", return nullptr); } else { - if (cuda_wrapper_memcpy(s->cuda_in_buffer, in_buffer, tx->tiles[0].width * - tx->tiles[0].height * 3, - CUDA_WRAPPER_MEMCPY_HOST_TO_DEVICE) != CUDA_WRAPPER_SUCCESS) { - fprintf(stderr, "Memcpy failed: %s\n", cuda_wrapper_last_error_string()); - return NULL; - } + CHECK_CUDA(cuda_wrapper_memcpy( + s->cuda_in_buffer, in_buffer, + tx->tiles[0].width * tx->tiles[0].height * 3, + CUDA_WRAPPER_MEMCPY_HOST_TO_DEVICE), + "Memcpy failed", return nullptr); } int (*cuda_dxt_enc_func)(const void * src, void * out, int size_x, int size_y, @@ -254,21 +254,16 @@ shared_ptr cuda_dxt_compress_tile(void *state, shared_ptrcuda_in_buffer, s->cuda_out_buffer, - s->saved_desc.width, s->saved_desc.height, 0); - if (ret != 0) { - fprintf(stderr, "Encoding failed: %s\n", cuda_wrapper_last_error_string()); - return NULL; - } + CHECK_CUDA(cuda_dxt_enc_func(s->cuda_in_buffer, s->cuda_out_buffer, + s->saved_desc.width, s->saved_desc.height, + 0), + "Encoding failed", return nullptr); shared_ptr out = s->pool.get_frame(); - if (cuda_wrapper_memcpy(out->tiles[0].data, - s->cuda_out_buffer, - out->tiles[0].data_len, - CUDA_WRAPPER_MEMCPY_DEVICE_TO_HOST) != CUDA_WRAPPER_SUCCESS) { - fprintf(stderr, "Memcpy failed: %s\n", cuda_wrapper_last_error_string()); - return NULL; - } + CHECK_CUDA(cuda_wrapper_memcpy(out->tiles[0].data, s->cuda_out_buffer, + out->tiles[0].data_len, + CUDA_WRAPPER_MEMCPY_DEVICE_TO_HOST), + "Memcpy failed", return nullptr); return out; } diff --git a/src/video_compress/libavcodec.cpp b/src/video_compress/libavcodec.cpp index c204d669b9..a068f0fa30 100644 --- a/src/video_compress/libavcodec.cpp +++ b/src/video_compress/libavcodec.cpp @@ -91,6 +91,7 @@ extern "C"{ #endif #define MOD_NAME "[lavc] " +#define MAGIC to_fourcc('l', 'a', 'v', 'c') using std::array; using std::clamp; @@ -277,6 +278,7 @@ struct state_video_compress_libav { to_lavc_vid_conv_destroy(&pixfmt_conversion); } + uint32_t magic = MAGIC; struct module module_data; struct video_desc saved_desc{}; @@ -713,7 +715,7 @@ void* libavcodec_compress_init(struct module *parent, const char *opts) return ret > 0 ? INIT_NOERR : NULL; } - return &s->module_data; + return s; } #ifdef HWACC_VULKAN @@ -1513,6 +1515,7 @@ receive_packet(state_video_compress_libav *s) static shared_ptr libavcodec_compress_tile(void *state, shared_ptr tx) { auto *s = (state_video_compress_libav *) state; + assert(s->magic == MAGIC); list> cleanup_callbacks; // at function exit handlers libavcodec_check_messages(s); @@ -1619,6 +1622,7 @@ static void cleanup(struct state_video_compress_libav *s) static void libavcodec_compress_done(void *state) { auto *s = (struct state_video_compress_libav *) state; + assert(s->magic == MAGIC); cleanup(s); diff --git a/src/video_decompress.cpp b/src/video_decompress.cpp index 879e81a040..408ae87f78 100644 --- a/src/video_decompress.cpp +++ b/src/video_decompress.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2019 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,18 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifdef HAVE_CONFIG_H -#include "config.h" -#include "config_unix.h" -#include "config_win32.h" -#endif // HAVE_CONFIG_H - -#include -#include -#include -#include "debug.h" -#include "video_codec.h" #include "video_decompress.h" -#include "lib_common.h" + +#include // for assert +#include // for uint32_t +#include // for free, calloc +#include // for strchr, strdup +#include // for basic_string, char_traits, hash, operator<< + +#include "debug.h" // for LOG, LOG_LEVEL_VERBOSE +#include "host.h" // for commandline_params, ADD_TO_PARAM +#include "lib_common.h" // for get_libraries_for_class, library_class +#include "video_codec.h" // for get_codec_from_name #define DECOMPRESS_MAGIC 0xdff34f21u diff --git a/src/video_decompress.h b/src/video_decompress.h index 946a5df000..344859a5d7 100644 --- a/src/video_decompress.h +++ b/src/video_decompress.h @@ -48,6 +48,12 @@ #ifndef __video_decompress_h #define __video_decompress_h +#ifdef __cplusplus +#include // for size_t +#else +#include // for size_t +#endif + #include "types.h" #ifdef __cplusplus diff --git a/src/video_display.c b/src/video_display.c index 8c6fd4d139..e6fa969561 100644 --- a/src/video_display.c +++ b/src/video_display.c @@ -14,7 +14,7 @@ */ /* * Copyright (c) 2001-2003 University of Southern California - * Copyright (c) 2005-2023 CESNET z.s.p.o. + * Copyright (c) 2005-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -345,6 +345,26 @@ struct video_frame *display_get_frame(struct display *d) } } +/** + * print display FPS + * + * Usually called from display_frame_helper for displays that use generic FPS + * indicator but externally linked for those that do not, like vulkan_sdl3. + */ +void +display_print_fps(const char *prefix, double seconds, int frames, + double nominal_fps) +{ + const double fps = frames / seconds; + const char *const fps_col = get_stat_color(fps / nominal_fps); + + log_msg(LOG_LEVEL_INFO, + TERM_BOLD TERM_FG_MAGENTA "%s" TERM_RESET + "%d frames in %g seconds = " TERM_BOLD + "%s%g FPS" TERM_RESET "\n", + prefix, frames, seconds, fps_col, fps); +} + static bool display_frame_helper(struct display *d, struct video_frame *frame, long long timeout_ns) { enum { @@ -364,15 +384,9 @@ static bool display_frame_helper(struct display *d, struct video_frame *frame, l long long seconds_ns = t - d->t0; if (seconds_ns > 5 * NS_IN_SEC) { const double seconds = (double) seconds_ns / NS_IN_SEC; - const double fps = d->frames / seconds; - const char *const fps_col = get_stat_color(fps / frame_fps); - - log_msg(LOG_LEVEL_INFO, - TERM_BOLD TERM_FG_MAGENTA - "%s" TERM_RESET "%d frames in %g seconds = " TERM_BOLD - "%s%g FPS" TERM_RESET "\n", - d->funcs->generic_fps_indicator_prefix, d->frames, - seconds, fps_col, fps); + display_print_fps(d->funcs->generic_fps_indicator_prefix, + seconds, d->frames, frame_fps); + d->frames = 0; d->t0 = t; } @@ -454,11 +468,12 @@ bool display_reconfigure(struct display *d, struct video_desc desc, enum video_m } struct video_desc pp_desc = desc; - if (!pp_does_change_tiling_mode) { - pp_desc.width *= get_video_mode_tiles_x(video_mode); - pp_desc.height *= get_video_mode_tiles_y(video_mode); - pp_desc.tile_count = 1; - } + /// @todo shouldn't be VO_PP_DOES_CHANGE_TILING_MODE actually removed? + // if (!pp_does_change_tiling_mode) { + // pp_desc.width *= get_video_mode_tiles_x(video_mode); + // pp_desc.height *= get_video_mode_tiles_y(video_mode); + // pp_desc.tile_count = 1; + // } if (!vo_postprocess_reconfigure(d->postprocess, pp_desc)) { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unable to reconfigure video " "postprocess.\n"); @@ -526,6 +541,22 @@ restrict_returned_codecs(struct vo_postprocess_state *postprocess, new_disp_codec_count * sizeof(codec_t)); } +static int +get_video_mode(struct display *d) +{ + int video_mode = 0; + size_t len = sizeof(video_mode); + if (d->postprocess != NULL) { + if (vo_postprocess_get_property( + d->postprocess, VO_PP_VIDEO_MODE, &video_mode, &len)) { + return video_mode; + } + } + const bool success = d->funcs->ctl_property( + d->state, DISPLAY_PROPERTY_VIDEO_MODE, &video_mode, &len); + return success ? video_mode : DISPLAY_PROPERTY_VIDEO_MERGED; +} + /** * @brief Gets property from video display. * @param[in] d video display state @@ -583,6 +614,10 @@ bool display_ctl_property(struct display *d, int property, void *val, size_t *le return false; } break; + case DISPLAY_PROPERTY_VIDEO_MODE: + assert(*len >= sizeof(int)); + *(int *) val = get_video_mode(d); + return true; default: return d->funcs->ctl_property(d->state, property, val, len); } diff --git a/src/video_display.h b/src/video_display.h index f15882c422..b7fffba4c6 100644 --- a/src/video_display.h +++ b/src/video_display.h @@ -13,7 +13,7 @@ * @ingroup display */ /* Copyright (c) 2001-2003 University of Southern California - * Copyright (c) 2005-2023 CESNET z.s.p.o. + * Copyright (c) 2005-2025 CESNET, zájmové sdružení právnických osob * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -211,6 +211,9 @@ bool display_reconfigure_audio(struct display *d, int quant_ struct video_frame *get_splashscreen(void); const char *get_audio_conn_flag_name(int audio_init_flag); +void display_print_fps(const char *prefix, double seconds, int frames, + double nominal_fps); + #ifdef __cplusplus } #endif // __cplusplus diff --git a/src/video_display/aggregate.c b/src/video_display/aggregate.c index 89df196018..cb4dbd8388 100644 --- a/src/video_display/aggregate.c +++ b/src/video_display/aggregate.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2023 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -289,6 +289,7 @@ static bool display_aggregate_get_property(void *state, int property, void *val, for(sub_codec = 0; sub_codec < lens[i] / sizeof(codec_t); ++sub_codec) { if(examined == codecs[i][sub_codec]) { ++found; + break; } } } @@ -343,6 +344,7 @@ static bool display_aggregate_get_property(void *state, int property, void *val, } case DISPLAY_PROPERTY_VIDEO_MODE: + *len = sizeof(int); if(s->devices_cnt == 1) *(int *) val = DISPLAY_PROPERTY_VIDEO_MERGED; else diff --git a/src/video_display/bluefish444.cpp b/src/video_display/bluefish444.cpp index 45cb93cc5c..b9ad94d686 100644 --- a/src/video_display/bluefish444.cpp +++ b/src/video_display/bluefish444.cpp @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2013-2023 CESNET, z. s. p. o. + * Copyright (c) 2013-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -969,6 +969,7 @@ static bool display_bluefish444_get_property(void *state, int property, void *va *len = sizeof(supported_il_modes); break; case DISPLAY_PROPERTY_VIDEO_MODE: + *len = sizeof(int); *(int *) val = DISPLAY_PROPERTY_VIDEO_SEPARATE_TILES; break; case DISPLAY_PROPERTY_AUDIO_FORMAT: diff --git a/src/video_display/gl.cpp b/src/video_display/gl.cpp index 82e20a4f4e..53b51cf48a 100644 --- a/src/video_display/gl.cpp +++ b/src/video_display/gl.cpp @@ -69,6 +69,7 @@ #ifdef HAVE_CONFIG_H #include "config.h" // for HAVE_SPOUT, HAVE_SYPHON #endif +#include "compat/strings.h" // for strcasecmp #include "debug.h" #include "gl_context.h" #include "host.h" @@ -365,6 +366,18 @@ static constexpr pair keybindings[] = { pair{K_CTRL_UP, "make window 10% bigger"} }; +#ifdef GLFW_PLATFORM +const static struct { + int platform_id; + const char *name; +} platform_map[] = { + { GLFW_PLATFORM_WIN32, "Win32" }, + { GLFW_PLATFORM_COCOA, "Cocoa" }, + { GLFW_PLATFORM_WAYLAND, "Wayland" }, + { GLFW_PLATFORM_X11, "X11" }, +}; +#endif // defined GLFW_PLATFORM + /* Prototyping */ static bool check_display_gl_version(bool print_ver); static bool display_gl_init_opengl(struct state_gl *s); @@ -528,8 +541,53 @@ static void gl_print_monitors(bool fullhelp) { if (!fullhelp) { cout << "(use \"fullhelp\" to see modes)\n"; } + printf("\n"); } +#ifdef GLFW_PLATFORM +static void +gl_print_platforms() +{ + printf("available platforms:\n"); + for (unsigned i = 0; i < ARR_COUNT(platform_map); ++i) { + if (glfwPlatformSupported(platform_map[i].platform_id)) { + color_printf("\t- " TBOLD("%s") "\n", platform_map[i].name); + } + } + color_printf("\n"); +} + +static void +gl_print_current_platform() +{ + const int platform = glfwGetPlatform(); + const char *name = "UNKNOWN/ERROR"; + for (unsigned i = 0; i < ARR_COUNT(platform_map); ++i) { + if (platform_map[i].platform_id == platform) { + name = platform_map[i].name; + break; + } + } +#ifdef __linux__ + int ll = LOG_LEVEL_NOTICE; +#else + int ll = LOG_LEVEL_VERBOSE; +#endif + log_msg(ll, MOD_NAME "Using platform: %s\n", name); +} +#else // mot defined GLFW_PLATFORM +static void +gl_print_platforms() +{ + MSG(ERROR, "platforms unsupported (old GLFW)\n"); +} +// NOOP +static void +gl_print_current_platform() +{ +} +#endif // not defined GLFW_PLATFORM + #define FEATURE_PRESENT(x) (strcmp(STRINGIFY(x), "1") == 0 ? "on" : "off") /** @@ -590,6 +648,7 @@ static void gl_show_help(bool full) { col() << TBOLD("\t--param " GL_DISABLE_10B_OPT_PARAM_NAME) << "\tdo not set 10-bit framebuffer (performance issues)\n"; col() << "\n" TBOLD( "[1]") " position doesn't work in Wayland\n"; + col() << TBOLD("\tplatform=

") << "\tuse platform (usable only in Linux)\n"; } else { color_printf( "\t(use \"" TBOLD("fullhelp") "\" to see options)\n"); @@ -607,7 +666,9 @@ static void gl_show_help(bool full) { return; } gl_print_monitors(full); - color_printf("\n"); + if (full) { + gl_print_platforms(); + } GLFWwindow *window = glfwCreateWindow(32, 32, DEFAULT_WIN_NAME, nullptr, nullptr); if (window != nullptr) { glfwMakeContextCurrent(window); @@ -704,6 +765,25 @@ parse_hints(struct state_gl *s, bool window_hints, char *hints) } } +static bool +set_platform(struct state_gl *s, const char *platform) +{ +#ifdef GLFW_PLATFORM + for (unsigned i = 0; i < ARR_COUNT(platform_map); ++i) { + if (strcasecmp(platform_map[i].name, platform) == 0) { + s->init_hints[GLFW_PLATFORM] = platform_map[i].platform_id; + return true; + } + } + MSG(ERROR, "Unknown platform: %s\n", platform); + return false; +#else + (void) s, (void) platform; + MSG(ERROR, "platforms unsupported (old GLFW)\n"); + return false; +#endif +} + static void list_hints() { @@ -721,7 +801,7 @@ list_hints() ").\n\n"); - color_printf("Available hints keys and values:\n"); + color_printf("Some of hints keys and values:\n"); for (const auto &h : hint_map) { color_printf("\t" TBOLD("%s") " - %#x\n", h.first.c_str(), h.second); @@ -732,13 +812,16 @@ list_hints() "Example usage: " TBOLD("-d gl:init_hint=platform=x11") "\n\n"); } -static void *display_gl_parse_fmt(struct state_gl *s, char *ptr) { +static bool +display_gl_parse_fmt(struct state_gl *s, char *ptr) +{ + bool ret = true; char *tok, *save_ptr = NULL; while((tok = strtok_r(ptr, ":", &save_ptr)) != NULL) { if (strcmp(tok, "help") == 0 || strcmp(tok, "fullhelp") == 0) { gl_show_help(strcmp(tok, "fullhelp") == 0); - return INIT_NOERR; + return false; } if (!strcmp(tok, "d") || !strcmp(tok, "dforce")) { s->deinterlace = !strcmp(tok, "d") ? state_gl::deint::on : state_gl::deint::force; @@ -784,7 +867,7 @@ static void *display_gl_parse_fmt(struct state_gl *s, char *ptr) { } #else log_msg(LOG_LEVEL_ERROR, MOD_NAME "Syphon/Spout support not compiled in.\n"); - return nullptr; + return false; #endif } else if (strstr(tok, "gamma=") == tok) { s->gamma = stof(strchr(tok, '=') + 1); @@ -797,9 +880,7 @@ static void *display_gl_parse_fmt(struct state_gl *s, char *ptr) { s->use_pbo = strcasecmp(tok, "pbo") == 0 ? 1 : 0; } else if (strstr(tok, "size=") == tok || strstr(tok, "fixed_size=") == tok) { - if (!set_size(s, tok)) { - return nullptr; - } + ret = ret && set_size(s, tok); } else if (strcmp(tok, "fixed_size") == 0) { s->fixed_size = true; } else if (strcmp(tok, "noresizable") == 0) { @@ -808,17 +889,19 @@ static void *display_gl_parse_fmt(struct state_gl *s, char *ptr) { parse_hints(s, true, strchr(tok, '=') + 1); } else if (strstr(tok, "init_hint=") == tok) { parse_hints(s, false, strchr(tok, '=') + 1); + } else if (IS_KEY_PREFIX(tok, "platform")) { + ret = ret && set_platform(s, strchr(tok, '=') + 1); } else if (strcmp(tok, "list_hints") == 0) { list_hints(); - return nullptr; + return false; } else { log_msg(LOG_LEVEL_ERROR, MOD_NAME "Unknown option: %s\n", tok); - return nullptr; + return false; } ptr = NULL; } - return s; + return ret; } static void * display_gl_init(struct module *parent, const char *fmt, unsigned int flags) { @@ -833,16 +916,16 @@ static void * display_gl_init(struct module *parent, const char *fmt, unsigned i if (fmt != NULL) { char *tmp = strdup(fmt); - void *ret = nullptr; + bool ret = false; try { ret = display_gl_parse_fmt(s, tmp); } catch (std::invalid_argument &e) { LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Invalid numeric value for an option!\n"; } free(tmp); - if (ret != s) { // ret is either nullptr or INIT_NOERR (help requested) + if (!ret) { delete s; - return ret; + return strstr(fmt, "help") == nullptr ? nullptr : INIT_NOERR; } } @@ -1302,7 +1385,6 @@ static void gl_process_frames(struct state_gl *s) free_message(msg, r); } - if (s->show_cursor == state_gl::SC_AUTOHIDE) { if (s->cursor_shown_from != steady_clock::time_point()) { const auto now = steady_clock::now(); @@ -1543,14 +1625,28 @@ static void display_gl_render_last(GLFWwindow *win) { #ifndef GLEW_ERROR_NO_GLX_DISPLAY #define GLEW_ERROR_NO_GLX_DISPLAY 4 #endif -static const char *glewGetError(GLenum err) { +static void printGlewError(GLenum err) { + const char *err_str = nullptr; switch (err) { - case GLEW_ERROR_NO_GL_VERSION: return "missing GL version"; - case GLEW_ERROR_GL_VERSION_10_ONLY: return "Need at least OpenGL 1.1"; - case GLEW_ERROR_GLX_VERSION_11_ONLY: return "Need at least GLX 1.2"; - case GLEW_ERROR_NO_GLX_DISPLAY: return "Need GLX display for GLX support"; - default: return (const char *) glewGetErrorString(err); + case GLEW_ERROR_NO_GL_VERSION: + err_str = "missing GL version"; + break; + case GLEW_ERROR_GL_VERSION_10_ONLY: + err_str = "Need at least OpenGL 1.1"; + break; + case GLEW_ERROR_GLX_VERSION_11_ONLY: + err_str = "Need at least GLX 1.2"; + break; + case GLEW_ERROR_NO_GLX_DISPLAY: + err_str = "Need GLX display for GLX support"; + break; + default: + err_str = (const char *) glewGetErrorString(err); + break; } + log_msg(err == GLEW_ERROR_NO_GLX_DISPLAY ? LOG_LEVEL_VERBOSE + : LOG_LEVEL_ERROR, + MOD_NAME "GLEW Error: %s (err %d)\n", err_str, err); } #endif // defined GLEW_VERSION @@ -1713,6 +1809,8 @@ static bool display_gl_init_opengl(struct state_gl *s) return false; } + gl_print_current_platform(); + if (s->req_monitor_idx != -1) { int count = 0; GLFWmonitor **mon = glfwGetMonitors(&count); @@ -1781,7 +1879,7 @@ static bool display_gl_init_opengl(struct state_gl *s) #if defined GLEW_VERSION if (GLenum err = glewInit()) { - log_msg(LOG_LEVEL_ERROR, MOD_NAME "GLEW Error: %s (err %d)\n", glewGetError(err), err); + printGlewError(err); if (err != GLEW_ERROR_NO_GLX_DISPLAY) { // do not fail on error 4 (on Wayland), which can be suppressed return false; } diff --git a/src/video_display/sdl2.c b/src/video_display/sdl2.c index fa71938f70..f818b061cd 100644 --- a/src/video_display/sdl2.c +++ b/src/video_display/sdl2.c @@ -999,5 +999,5 @@ static const struct video_display_info display_sdl2_info = { }; REGISTER_MODULE(sdl, &display_sdl2_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); -REGISTER_HIDDEN_MODULE(sdl2, &display_sdl2_info, LIBRARY_CLASS_VIDEO_DISPLAY, - VIDEO_DISPLAY_ABI_VERSION); +REGISTER_MODULE_WITH_FLAG(sdl2, &display_sdl2_info, LIBRARY_CLASS_VIDEO_DISPLAY, + VIDEO_DISPLAY_ABI_VERSION, MODULE_FLAG_ALIAS); diff --git a/src/video_display/sdl3.c b/src/video_display/sdl3.c index 0e06ad2ceb..f8cb3f2526 100644 --- a/src/video_display/sdl3.c +++ b/src/video_display/sdl3.c @@ -45,11 +45,10 @@ * @todo errata (SDL3 vs SDL2) * 1. [macOS] Vulkan renderer doesn't work (no matter if linked with MoltenVK or * loader) - * 2. [all platforms] with `renderer=vulkan` - none YCbCr texture works + * 2. [all platforms] with `renderer=vulkan` - none of YCbCr textures work * (segfaults - wrong pitch/texture?) * 3. p010 works just on macOS/Metal, crashes on Vulkan (see previous point) * 4. p010 corrupted on d3d[12] - pixfmts skipped in query*() as a workaround - * 5. see todo in @ref ../audio/capture/sdl_mixer.c */ #include @@ -605,11 +604,16 @@ get_ug_to_sdl_format(const struct fmt_data *supp_fmts, codec_t ug_codec) static int get_supported_pfs(const struct fmt_data *supp_fmts, codec_t *codecs) { - int i = 0; - for (; supp_fmts[i].ug_codec != VC_NONE; ++i) { - codecs[i] = supp_fmts[i].ug_codec; + bool codec_set[VC_COUNT] = {}; + int count = 0; + for (int i = 0; supp_fmts[i].ug_codec != VC_NONE; ++i) { + if (codec_set[supp_fmts[i].ug_codec]) { + continue; + } + codecs[count++] = supp_fmts[i].ug_codec; + codec_set[supp_fmts[i].ug_codec] = true; } - return i; + return count; } static void @@ -1391,5 +1395,5 @@ static const struct video_display_info display_sdl3_info = { REGISTER_MODULE(sdl, &display_sdl3_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); -REGISTER_HIDDEN_MODULE(sdl3, &display_sdl3_info, LIBRARY_CLASS_VIDEO_DISPLAY, - VIDEO_DISPLAY_ABI_VERSION); +REGISTER_MODULE_WITH_FLAG(sdl3, &display_sdl3_info, LIBRARY_CLASS_VIDEO_DISPLAY, + VIDEO_DISPLAY_ABI_VERSION, MODULE_FLAG_ALIAS); diff --git a/src/video_display/vulkan/vulkan_display.cpp b/src/video_display/vulkan/vulkan_display.cpp index c1bbdffe0d..6c4dbcb28d 100644 --- a/src/video_display/vulkan/vulkan_display.cpp +++ b/src/video_display/vulkan/vulkan_display.cpp @@ -51,12 +51,6 @@ #include "debug.h" #include "utils/fs.h" -#ifdef HAVE_CONFIG_H -#include "config.h" // for SRCDIR -#else -#define SRCDIR ".." -#endif // HAVE_CONFIG_H - #define MOD_NAME "[vulkan] " using namespace vulkan_display_detail; @@ -663,19 +657,19 @@ void VulkanDisplay::window_parameters_changed(WindowParameters new_parameters) { std::string get_shader_path() { - constexpr char suffix[] = "/share/ultragrid/vulkan_shaders"; + constexpr char suffix[] = "/vulkan_shaders"; // note that get_install_root returns bin/.. if run from build, // which will not contain the shaders for out-of-tree builds - const char *path = get_install_root(); - if (path != nullptr) { - std::string path_to_shaders = std::string(path) + suffix; + const char *data_path = get_data_path(); + if (data_path != nullptr) { + std::string path_to_shaders = std::string(data_path) + suffix; std::filesystem::directory_entry dir{ std::filesystem::path( path_to_shaders) }; if (dir.exists()) { return path_to_shaders; } } - return std::string(SRCDIR) + suffix; + return {}; } } //namespace vulkan_display diff --git a/src/video_display/vulkan/vulkan_sdl2.cpp b/src/video_display/vulkan/vulkan_sdl2.cpp index eca64ddc53..36a0965919 100644 --- a/src/video_display/vulkan/vulkan_sdl2.cpp +++ b/src/video_display/vulkan/vulkan_sdl2.cpp @@ -986,5 +986,7 @@ const video_display_info display_vulkan_info = { REGISTER_MODULE(vulkan_sdl2, &display_vulkan_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); -REGISTER_HIDDEN_MODULE(vulkan, &display_vulkan_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); +REGISTER_MODULE_WITH_FLAG(vulkan, &display_vulkan_info, + LIBRARY_CLASS_VIDEO_DISPLAY, + VIDEO_DISPLAY_ABI_VERSION, MODULE_FLAG_ALIAS); diff --git a/src/video_display/vulkan/vulkan_sdl3.cpp b/src/video_display/vulkan/vulkan_sdl3.cpp index 07fe68d290..ee427c639e 100644 --- a/src/video_display/vulkan/vulkan_sdl3.cpp +++ b/src/video_display/vulkan/vulkan_sdl3.cpp @@ -394,9 +394,9 @@ void display_vulkan_run(void* state) { auto now = chrono::steady_clock::now(); double seconds = chrono::duration{ now - s->time }.count(); if (seconds > 5) { - double fps = s->frames / seconds; - log_msg(LOG_LEVEL_INFO, MOD_NAME "%llu frames in %g seconds = %g FPS\n", - static_cast(s->frames), seconds, fps); + display_print_fps(MOD_NAME, seconds, (int) s->frames, + s->current_desc.fps); + s->time = now; s->frames = 0; } @@ -999,5 +999,6 @@ const video_display_info display_vulkan_info = { REGISTER_MODULE(vulkan, &display_vulkan_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); -REGISTER_HIDDEN_MODULE(vulkan_sdl3, &display_vulkan_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION); - +REGISTER_MODULE_WITH_FLAG(vulkan_sdl3, &display_vulkan_info, + LIBRARY_CLASS_VIDEO_DISPLAY, + VIDEO_DISPLAY_ABI_VERSION, MODULE_FLAG_ALIAS); diff --git a/src/video_frame.c b/src/video_frame.c index f2eb1cdffa..a3a56e69ed 100644 --- a/src/video_frame.c +++ b/src/video_frame.c @@ -12,7 +12,7 @@ * @brief This file contains video frame manipulation functions. */ /* - * Copyright (c) 2005-2023 CESNET z.s.p.o. + * Copyright (c) 2005-2025 CESNET * * Redistribution and use in source and binary forms, with or without * modification, is permitted provided that the following conditions @@ -54,14 +54,15 @@ #include #include #include +#include // for uint16_t #include #include #ifdef __linux__ #include #endif -#include "config_unix.h" -#include "config_win32.h" +#include "compat/aligned_malloc.h" +#include "compat/endian.h" // for htobe16 #include "debug.h" #include "pixfmt_conv.h" #include "utils/pam.h" @@ -447,7 +448,7 @@ static unsigned char *get_16_bit_pnm_data(struct video_frame *frame) { for (unsigned i = 0; i < frame->tiles[0].width * 3; ++i) { uint16_t tmp = *dstline; tmp >>= 16U - depth; - *dstline++ = htons(tmp); + *dstline++ = htobe16(tmp); } } return tmp; @@ -481,7 +482,9 @@ bool save_video_frame_as_pnm(struct video_frame *frame, const char *name) return false; } - pam_write(name, tile->width, tile->height, 3, (1<color_spec)) - 1, data, true); + pam_write(name, tile->width, PAM_PITCH_CONTINUOUS, tile->height, 3, + (1 << get_bits_per_component(frame->color_spec)) - 1, data, + true); free(tmp_data); return true; diff --git a/src/video_rxtx/ultragrid_rtp.cpp b/src/video_rxtx/ultragrid_rtp.cpp index b0d1eb663e..2d83bd7f29 100644 --- a/src/video_rxtx/ultragrid_rtp.cpp +++ b/src/video_rxtx/ultragrid_rtp.cpp @@ -92,8 +92,7 @@ ultragrid_rtp_video_rxtx::ultragrid_rtp_video_rxtx(const map &p throw ug_no_error(); } - m_control = (struct control_state *) get_module( - get_root_module(m_common.parent), "control")->priv_data; + m_control = get_control_state(m_common.parent); } ultragrid_rtp_video_rxtx::~ultragrid_rtp_video_rxtx() diff --git a/src/vo_postprocess.h b/src/vo_postprocess.h index b3dfbab3d4..c26d5e8166 100644 --- a/src/vo_postprocess.h +++ b/src/vo_postprocess.h @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2023 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,9 +46,13 @@ extern "C" { #include #endif -/* property type default */ +/* property type default (if false returned) */ #define VO_PP_PROPERTY_CODECS 0 /* codec_t[] all uncompressed */ +/// @todo should be possibly removed - for PP input, VO_PP_VIDEO_MODE is used, +/// the output (display input) desc is obeyed with vo_postprocess_get_out_desc() #define VO_PP_DOES_CHANGE_TILING_MODE 1 /* bool false */ +/// accepeted input video mode, should process all if false returned +#define VO_PP_VIDEO_MODE 2 /* int (enum video_mode) any (should process both) */ #define VO_PP_ABI_VERSION 7 @@ -68,8 +72,11 @@ typedef void *(*vo_postprocess_init_t)(const char *cfg); */ typedef bool (*vo_postprocess_reconfigure_t)(void *state, struct video_desc desc); typedef struct video_frame * (*vo_postprocess_getf_t)(void *state); -/* - * Returns various information about postprocessor format not only output (legacy name). +/** + * Returns information about postprocessor output format + * + * @todo @c in_tile_mode should be perhaps removed - unused and supersedded by + * @ref VO_PP_VIDEO_MODE * * @param s postprocessor state * @param out output video description according to input parameters diff --git a/src/vo_postprocess/3d-interlaced.c b/src/vo_postprocess/3d-interlaced.c index f154f42285..d9e4da3b81 100644 --- a/src/vo_postprocess/3d-interlaced.c +++ b/src/vo_postprocess/3d-interlaced.c @@ -3,7 +3,7 @@ * @author Martin Pulec */ /* - * Copyright (c) 2011-2015 CESNET, z. s. p. o. + * Copyright (c) 2011-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ #include "debug.h" #include "lib_common.h" +#include "utils/color_out.h" #include "video.h" #include "video_display.h" /* DISPLAY_PROPERTY_VIDEO_SEPARATE_FILES */ #include "vo_postprocess.h" @@ -66,6 +67,13 @@ static bool interlaced_3d_get_property(void *state, int property, void *val, siz static void * interlaced_3d_init(const char *config) { if (strcmp(config, "help") == 0) { + color_printf( + TBOLD("interlaced_3d") " postprocessor merges left and " + "right eye into one single where " + "the eyes are line-interleaved.\n\n"); + printf("Usage:\n\t-p interlaced_3d\n\n"); + } + if (strlen(config) > 0) { printf("3d-interlaced takes no parameters.\n"); return NULL; } diff --git a/src/vo_postprocess/capture_filter_wrapper.h b/src/vo_postprocess/capture_filter_wrapper.h index d5d990bd75..1be217d99e 100644 --- a/src/vo_postprocess/capture_filter_wrapper.h +++ b/src/vo_postprocess/capture_filter_wrapper.h @@ -8,7 +8,7 @@ * output buffer to decode to. */ /* - * Copyright (c) 2020 CESNET, z. s. p. o. + * Copyright (c) 2020-2025 CESNET * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,7 @@ #include "capture_filter.h" #include "utils/macros.h" +#include "video_codec.h" // for vc_get_linesize #include "video_display.h" #include "vo_postprocess.h" diff --git a/src/vo_postprocess/temporal_3d.c b/src/vo_postprocess/temporal_3d.c new file mode 100644 index 0000000000..caa93fcd70 --- /dev/null +++ b/src/vo_postprocess/temporal_3d.c @@ -0,0 +1,220 @@ +/** + * @file vo_postprocess/temporal_3d.c + * @author Martin Pulec + */ +/* + * Copyright (c) 2025 CESNET + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include // for assert +#include // for bool, true, false +#include // for uint32_t +#include // for printf, NULL, size_t +#include // for free, malloc, calloc +#include // for strcmp, strlen + +#include "compat/usleep.h" // for usleep +#include "debug.h" // for MSG +#include "lib_common.h" // for REGISTER_MODULE, library_class +#include "tv.h" // for time_ns_t +#include "types.h" // for tile, video_desc, video_frame +#include "utils/color_out.h" // for color_printf, TBOLD +#include "utils/macros.h" // for to_fourcc +#include "video_codec.h" // for vc_get_linesize +#include "video_display.h" // for display_prop_vid_mode +#include "video_frame.h" // for vf_get_tile, vf_free +#include "vo_postprocess.h" // for VO_PP_ABI_VERSION, vo_postprocess_info + +// #include "vo_postprocess.h" + +#define MAGIC to_fourcc('v', 'p', 't', '3') +#define MOD_NAME "[temporal_3d] " +#define TIMEOUT "20ms" + +struct state_temporal_3d { + uint32_t magic; + struct video_frame *in; + time_ns_t first_tile_time; + bool disable_timing; ///< issue the second eye right after the first +}; + +static void temporal_3d_done(void *state); + +static bool +temporal_3d_get_property(void *state, int property, void *val, size_t *len) +{ + (void) state; + if (property == VO_PP_VIDEO_MODE) { + assert(*len >= sizeof(int)); + *len = sizeof(int); + *(int *) val = DISPLAY_PROPERTY_VIDEO_SEPARATE_3D; + return true; + } + + return false; +} + +static void +usage() +{ + color_printf( + TBOLD("temporal_3d") " postprocessor interleaves left and " + "right eye temporarily into a single stream " + "with double FPS.\n\n"); + color_printf( + "Usage:\n\t" TBOLD(TRED("-p temporal_3d") "[:nodelay]") "\n\n"); + printf("Parameters:\n"); + printf( + "\tnodelay - disable timing, pass right eye right after first\n"); + printf("\t (may help performance)\n"); +} + +static void * +temporal_3d_init(const char *config) +{ + if (strcmp(config, "help") == 0) { + usage(); + return NULL; + } + struct state_temporal_3d *s = calloc(1, sizeof *s); + s->magic = MAGIC; + if (strcmp(config, "nodelay") == 0) { + s->disable_timing = true; + if (get_commandline_param("decoder-drop-policy") == NULL) { + MSG(NOTICE, + "nodelay option used, setting drop policy to %s " + "timeout.\n", + TIMEOUT); + set_commandline_param("decoder-drop-policy", TIMEOUT); + } + } else { + MSG(ERROR, "Unknown option: %s!\n", config); + temporal_3d_done(s); + return NULL; + } + + return s; +} + +static bool +temporal_3d_postprocess_reconfigure(void *state, struct video_desc desc) +{ + struct state_temporal_3d *s = state; + assert(s->magic == MAGIC); + assert(desc.tile_count == 2); + s->in = vf_alloc_desc_data(desc); + + return true; +} + +static struct video_frame * +temporal_3d_getf(void *state) +{ + struct state_temporal_3d *s = state; + + return s->in; +} + +/** + * Creates from 2 tiles (left and right eye) one in interlaced format. + * + * @param[in] state postprocessor state + * @param[in] in input frame. Must contain exactly 2 tiles + * @param[out] out output frame to be written to. Should have only one + * tile + * @param[in] req_pitch requested pitch in buffer + */ +static bool +temporal_3d_postprocess(void *state, struct video_frame *in, + struct video_frame *out, int req_pitch) +{ + struct state_temporal_3d *s = state; + assert(in == NULL || in == s->in); + assert(out->tile_count == 1); + + if (in != NULL) { + s->first_tile_time = get_time_in_ns(); + } + + struct tile *in_tile = &s->in->tiles[in == NULL ? 1 : 0]; + const int linesize = vc_get_linesize(in_tile->width, s->in->color_spec); + for (size_t y = 0; y < out->tiles[0].height; ++y) { + memcpy(out->tiles[0].data + (y * req_pitch), + in_tile->data + (y * linesize), linesize); + } + + // delay the other tile for correct timing + if (!s->disable_timing && in == NULL) { + time_ns_t t1 = get_time_in_ns(); + long long since_first_tile_us = + NS_TO_US(t1 - s->first_tile_time); + long long sleep_us = + (US_IN_SEC / s->in->fps) - (double) since_first_tile_us; + if (sleep_us > 0) { + usleep(sleep_us); + } + } + + return true; +} + +static void +temporal_3d_done(void *state) +{ + struct state_temporal_3d *s = state; + vf_free(s->in); + free(state); +} + +static void +temporal_3d_get_out_desc(void *state, struct video_desc *out, + int *in_display_mode, int *out_frames) +{ + struct state_temporal_3d *s = (struct state_temporal_3d *) state; + + *out = video_desc_from_frame(s->in); + out->fps *= 2; + out->tile_count = 1; + + *in_display_mode = DISPLAY_PROPERTY_VIDEO_SEPARATE_TILES; + *out_frames = 2; +} + +static const struct vo_postprocess_info vo_pp_temporal_3d_info = { + temporal_3d_init, temporal_3d_postprocess_reconfigure, + temporal_3d_getf, temporal_3d_get_out_desc, + temporal_3d_get_property, temporal_3d_postprocess, + temporal_3d_done, +}; + +REGISTER_MODULE(temporal_3d, &vo_pp_temporal_3d_info, + LIBRARY_CLASS_VIDEO_POSTPROCESS, VO_PP_ABI_VERSION); diff --git a/tools/ipc_frame_ug.cpp b/tools/ipc_frame_ug.cpp index b79776ad35..3429b6fe39 100644 --- a/tools/ipc_frame_ug.cpp +++ b/tools/ipc_frame_ug.cpp @@ -104,6 +104,15 @@ bool ipc_frame_from_ug_frame_hq(struct Ipc_frame *dst, return true; } +Ipc_frame_color_spec ipc_frame_color_spec_from_ug(codec_t codec){ + switch(codec){ + case RGBA: return IPC_FRAME_COLOR_RGBA; + case RGB: return IPC_FRAME_COLOR_RGB; + case UYVY: return IPC_FRAME_COLOR_UYVY; + default: return IPC_FRAME_COLOR_NONE; + } +} + bool ipc_frame_from_ug_frame(struct Ipc_frame *dst, const struct video_frame *src, codec_t codec, @@ -125,7 +134,7 @@ bool ipc_frame_from_ug_frame(struct Ipc_frame *dst, dst->header.width = src->tiles[0].width; dst->header.height = src->tiles[0].height; - dst->header.color_spec = static_cast(codec); + dst->header.color_spec = ipc_frame_color_spec_from_ug(codec); int dst_frame_to_allocate = 0; diff --git a/tools/ipc_frame_ug.h b/tools/ipc_frame_ug.h index f46582327a..d4f6d7a85b 100644 --- a/tools/ipc_frame_ug.h +++ b/tools/ipc_frame_ug.h @@ -33,4 +33,6 @@ bool ipc_frame_write_to_fd(const struct Ipc_frame *f, int fd); int ipc_frame_get_scale_factor(int src_w, int src_h, int target_w, int target_h); +Ipc_frame_color_spec ipc_frame_color_spec_from_ug(codec_t codec); + #endif //IPC_FRAME_UG_32ee5c748f3e